function do_performquicktransition() { $oForm = $this->form_quicktransition(); $res = $oForm->validate(); if (!empty($res['errors'])) { return $oForm->handleError(); } $this->startTransaction(); $data = $res['results']; $oTransition = KTWorkflowTransition::get($_REQUEST['fTransitionId']); $res = KTWorkflowUtil::performTransitionOnDocument($oTransition, $this->oDocument, $this->oUser, $data['reason']); if (!Permission::userHasDocumentReadPermission($this->oDocument)) { $this->commitTransaction(); $_SESSION['KTInfoMessage'][] = _kt('Transition performed') . '. ' . _kt('You no longer have permission to view this document'); controllerRedirect('browse', sprintf('fFolderId=%d', $this->oDocument->getFolderId())); } else { $this->commitTransaction(); $_SESSION['KTInfoMessage'][] = _kt('Transition performed'); controllerRedirect('viewDocument', sprintf('fDocumentId=%d', $this->oDocument->getId())); } }
function generateWorkflowTriggers() { require_once KT_LIB_DIR . '/workflow/workflowutil.inc.php'; // get all the transitions, and add a trigger to the util with the appropriate settings. $KTWFTriggerReg =& KTWorkflowTriggerRegistry::getSingleton(); $aTransitions = KTWorkflowTransition::getList(); foreach ($aTransitions as $oTransition) { // guard perm $iGuardPerm = $oTransition->getGuardPermissionId(); if (!is_null($iGuardPerm)) { $sNamespace = 'ktcore.workflowtriggers.permissionguard'; $oPerm = KTPermission::get($iGuardPerm); $oTrigger = $KTWFTriggerReg->getWorkflowTrigger($sNamespace); $oTriggerConfig = KTWorkflowTriggerInstance::createFromArray(array('transitionid' => KTUtil::getId($oTransition), 'namespace' => $sNamespace, 'config' => array('perms' => array($oPerm->getName())))); } // guard group $iGuardGroup = $oTransition->getGuardGroupId(); if (!is_null($iGuardGroup)) { $sNamespace = 'ktcore.workflowtriggers.groupguard'; $oTrigger = $KTWFTriggerReg->getWorkflowTrigger($sNamespace); $oTriggerConfig = KTWorkflowTriggerInstance::createFromArray(array('transitionid' => KTUtil::getId($oTransition), 'namespace' => $sNamespace, 'config' => array('group_id' => $iGuardGroup))); } // guard role $iGuardRole = $oTransition->getGuardRoleId(); if (!is_null($iGuardRole)) { $sNamespace = 'ktcore.workflowtriggers.roleguard'; $oTrigger = $KTWFTriggerReg->getWorkflowTrigger($sNamespace); $oTriggerConfig = KTWorkflowTriggerInstance::createFromArray(array('transitionid' => KTUtil::getId($oTransition), 'namespace' => $sNamespace, 'config' => array('role_id' => $iGuardRole))); } // guard condition $iGuardCondition = $oTransition->getGuardConditionId(); if (!is_null($iGuardCondition)) { $sNamespace = 'ktcore.workflowtriggers.conditionguard'; $oTrigger = $KTWFTriggerReg->getWorkflowTrigger($sNamespace); $oTriggerConfig = KTWorkflowTriggerInstance::createFromArray(array('transitionid' => KTUtil::getId($oTransition), 'namespace' => $sNamespace, 'config' => array('condition_id' => $iGuardCondition))); } } }
function finalise() { $fWizardKey = KTUtil::arrayGet($_REQUEST, 'fWizardKey'); if (!empty($fWizardKey)) { $this->errorRedirectToMain(_kt("Could not create workflow.")); exit; } $wiz_data = $_SESSION['_wiz_data'][$fWizardKey]; // gather all our data. we're sure this is all good and healthy. $states = $wiz_data['states']; $transitions = $wiz_data['transitions']; $from = $wiz_data['from']; $to = $wiz_data['to']; $initial_state = $wiz_data['initial_state']; $workflow_name = $wiz_data['workflow_name']; $this->startTransaction(); // create the initial workflow $oWorkflow = KTWorkflow::createFromArray(array('name' => $workflow_name, 'humanname' => $workflow_name, 'enabled' => true)); if (PEAR::isError($oWorkflow)) { $this->errorRedirectToMain(sprintf(_kt("Failed to create workflow: %s"), $oWorkflow->getMessage())); } $iWorkflowId = $oWorkflow->getId(); // create the states. $aStates = array(); foreach ($states as $state_name) { $oState = KTWorkflowState::createFromArray(array('workflowid' => $iWorkflowId, 'name' => $state_name, 'humanname' => $state_name)); if (PEAR::isError($oState)) { $this->errorRedirectToMain(sprintf(_kt("Failed to create state: %s"), $oState->getMessage())); } $aStates[$state_name] = $oState; } // update the initial state on workflow $oInitialState = $aStates[$initial_state]; $oWorkflow->setStartStateId($oInitialState->getId()); $res = $oWorkflow->update(); if (PEAR::isError($res)) { $this->errorRedirectToMain(sprintf(_kt("Failed to update workflow: %s"), $res->getMessage())); } // next, we create and hook up the transitions. $aTransitions = array(); foreach ($transitions as $transition) { $dest_name = $to[$transition]; $oDestState = $aStates[$dest_name]; $oTransition = KTWorkflowTransition::createFromArray(array("WorkflowId" => $iWorkflowId, "Name" => $transition, "HumanName" => $transition, "TargetStateId" => $oDestState->getId(), "GuardPermissionId" => null, "GuardGroupId" => null, "GuardRoleId" => null, "GuardConditionId" => null)); if (PEAR::isError($oTransition)) { $this->errorRedirectToMain(sprintf(_kt("Failed to create transition: %s"), $oTransition->getMessage())); } // hook up source states. $state_ids = array(); $sources = (array) $from[$transition]; foreach ($sources as $state_name) { // must exist. $oState = $aStates[$state_name]; $state_ids[] = $oState->getId(); } $res = KTWorkflowAdminUtil::saveTransitionSources($oTransition, $state_ids); if (PEAR::isError($res)) { $this->errorRedirectToMain(sprintf(_kt("Failed to set transition origins: %s"), $res->getMessage())); } } $this->commitTransaction(); // finally, we want to redirect the user to the parent dispatcher somehow. // FIXME nbm: how do you recommend we do this? $base = $_SERVER['PHP_SELF']; $qs = sprintf("action=view&fWorkflowId=%d", $oWorkflow->getId()); $url = KTUtil::addQueryString($base, $qs); $this->addInfoMessage(_kt("Your new workflow has been created. You may want to configure security and notifications from the menu on the left.")); redirect($url); }
/** * This performs a transition to a new state of the workflow on the document * * @author KnowledgeTree Team * @access public * @param string $transition The transition to perform * @param string $reason The reason for transitioning the document to a new state * @return void|PEAR_Error Returns nothing on success | a PEAR_Error on failure */ function perform_workflow_transition($transition, $reason) { $user = $this->can_user_access_object_requiring_permission($this->document, KTAPI_PERMISSION_WORKFLOW); if (PEAR::isError($user)) { return $user; } $workflowid = $this->document->getWorkflowId(); if (empty($workflowid)) { return new PEAR_Error(KTAPI_ERROR_WORKFLOW_NOT_IN_PROGRESS); } $transition =& KTWorkflowTransition::getByName($transition); if (is_null($transition) || PEAR::isError($transition)) { return new KTAPI_Error(KTAPI_ERROR_WORKFLOW_INVALID, $transition); } DBUtil::startTransaction(); $result = KTWorkflowUtil::performTransitionOnDocument($transition, $this->document, $user, $reason); if (is_null($result) || PEAR::isError($result)) { DBUtil::rollback(); return new KTAPI_Error(KTAPI_ERROR_WORKFLOW_INVALID, $transition); } DBUtil::commit(); }
/** * Gets which workflow transitions are available to be chosen from * this workflow state. * * Workflow transitions have only destination workflow states, and * it is up to the workflow state to decide which workflow * transitions it wants to allow to leave its state. * * This function optionally will return the database id numbers of * the workflow transitions using the 'ids' option. */ function getTransitionsFrom($oState, $aOptions = null) { $bIds = KTUtil::arrayGet($aOptions, 'ids'); $sTable = KTUtil::getTableName('workflow_state_transitions'); $aQuery = array("SELECT transition_id FROM {$sTable} WHERE state_id = ?", array($oState->getId())); $aTransitionIds = DBUtil::getResultArrayKey($aQuery, 'transition_id'); if (PEAR::isError($aTransitionIds)) { return $aTransitionIds; } if ($bIds) { return $aTransitionIds; } $aRet = array(); foreach ($aTransitionIds as $iId) { $aRet[] =& KTWorkflowTransition::get($iId); } return $aRet; }
function get_graph($oWorkflow) { $fontsize = 11.0; $fontname = "Times-Roman"; $opts = array('fontsize' => $fontsize, 'fontname' => $fontname); $graph = new Image_GraphViz(true, $opts); $graph->dotCommand = $this->dotCommand; // we need all states & transitions // FIXME do we want guards? // we want to enable link-editing, and indicate that transitions "converge" // so we use a temporary "node" for transitions // we also use a "fake" URL which we catch later // so we can give good "alt" tags. $states = KTWorkflowState::getByWorkflow($oWorkflow); $transitions = KTWorkflowTransition::getByWorkflow($oWorkflow); $this->state_names = array(); $this->transition_names = array(); $state_opts = array('shape' => 'box', 'fontsize' => $fontsize, 'fontname' => $fontname); $transition_opts = array('shape' => 'box', 'color' => '#ffffff', 'fontsize' => $fontsize, 'fontname' => $fontname); $finaltransition_opts = array('color' => '#333333'); $sourcetransition_opts = array('color' => '#999999'); // to make this a little more useful, we want to cascade our output from // start to end states - this will tend to give a better output. // // to do this, we need to order our nodes in terms of "nearness" to the // initial node. $processing_nodes = array(); $sorted_ids = array(); $availability = array(); $sources = array(); $destinations = array(); $states = KTUtil::keyArray($states); $transitions = KTUtil::keyArray($transitions); foreach ($transitions as $tid => $oTransition) { $sources[$tid] = KTWorkflowAdminUtil::getSourceStates($oTransition, array('ids' => true)); $destinations[$tid] = $oTransition->getTargetStateId(); foreach ($sources[$tid] as $sourcestateid) { $av = (array) KTUtil::arrayGet($availability, $sourcestateid, array()); $av[] = $tid; $availability[$sourcestateid] = $av; } } //var_dump($sources); exit(0); //var_dump($availability); exit(0); $processing = array($oWorkflow->getStartStateId()); while (!empty($processing)) { $active = array_shift($processing); if (!$processing_nodes[$active]) { // mark that we've seen this node $processing_nodes[$active] = true; $sorted[] = $active; // now add all reachable nodes to the *end* of the queue. foreach ((array) $availability[$active] as $tid) { $next = $destinations[$tid]; if (!$processing_nodes[$next]) { $processing[] = $next; } } } //var_dump($processing); } //var_dump($sorted); exit(0); foreach ($sorted as $sid) { $oState = $states[$sid]; $this->state_names[$oState->getId()] = $oState->getHumanName(); $local_opts = array('URL' => sprintf("s%d", $oState->getId()), 'label' => $oState->getHumanName(), 'color' => '#666666'); if ($oState->getId() == $oWorkflow->getStartStateId()) { $local_opts['color'] = '#000000'; $local_opts['style'] = 'filled'; $local_opts['fillcolor'] = '#cccccc'; } $graph->addNode(sprintf('state%d', $oState->getId()), KTUtil::meldOptions($state_opts, $local_opts)); } foreach ($transitions as $tid => $oTransition) { $name = sprintf('transition%d', $tid); $this->transition_names[$oTransition->getId()] = $oTransition->getHumanName(); // we "cheat" and use $graph->addNode($name, KTUtil::meldOptions($transition_opts, array('URL' => sprintf("t%d", $tid), 'label' => $oTransition->getHumanName()))); $dest = sprintf("state%d", $oTransition->getTargetStateId()); $graph->addEdge(array($name => $dest), $finaltransition_opts); foreach ($sources[$tid] as $source_id) { $source_name = sprintf("state%d", $source_id); $graph->addEdge(array($source_name => $name), $sourcetransition_opts); } } // some simple analysis $errors = array(); $info = array(); $sourceless_transitions = array(); foreach ($transitions as $tid => $oTransition) { if (empty($sources[$tid])) { $sourceless_transitions[] = $oTransition->getHumanName(); } } if (!empty($sourceless_transitions)) { $errors[] = sprintf(_kt("Some transitions have no source states: %s"), implode(', ', $sourceless_transitions)); } $unlinked_states = array(); foreach ($states as $sid => $oState) { if (!$processing_nodes[$sid]) { // quick sanity check $unlinked_states[] = $oState->getHumanName(); } } if (!empty($unlinked_states)) { $errors[] = sprintf(_kt("Some states cannot be reached from the initial state (<strong>%s</strong>): %s"), $states[$oWorkflow->getStartStateId()]->getHumanName(), implode(', ', $unlinked_states)); } $data = array('graph' => $graph, 'errors' => $errors, 'info' => $info); return $data; }