. * * You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, * California 94120-7775, or email info@knowledgetree.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU General Public License version 3. * * In accordance with Section 7(b) of the GNU General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * KnowledgeTree" logo and retain the original copyright notice. If the display of the * logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices * must display the words "Powered by KnowledgeTree" and retain the original * copyright notice. * Contributor( s): ______________________________________ * */ require_once(KT_LIB_DIR . '/workflow/workflow.inc.php'); require_once(KT_LIB_DIR . '/workflow/workflowstate.inc.php'); require_once(KT_LIB_DIR . '/workflow/workflowtransition.inc.php'); require_once(KT_LIB_DIR . '/workflow/workflowtrigger.inc.php'); require_once(KT_LIB_DIR . '/workflow/workflowtriggerinstance.inc.php'); require_once(KT_LIB_DIR . '/permissions/permissionutil.inc.php'); require_once(KT_LIB_DIR . '/groups/GroupUtil.php'); require_once(KT_LIB_DIR . '/documentmanagement/DocumentTransaction.inc'); require_once(KT_LIB_DIR . '/search/searchutil.inc.php'); require_once(KT_LIB_DIR . '/roles/roleallocation.inc.php'); require_once(KT_LIB_DIR . '/groups/Group.inc'); require_once(KT_LIB_DIR . '/users/User.inc'); require_once(KT_LIB_DIR . '/roles/Role.inc'); require_once(KT_LIB_DIR . '/dashboard/Notification.inc.php'); class KTWorkflowUtil { // {{{ saveTransitionsFrom /** * Saves 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. */ function saveTransitionsFrom($oState, $aTransitionIds) { $sTable = KTUtil::getTableName('workflow_state_transitions'); $aQuery = array( "DELETE FROM $sTable WHERE state_id = ?", array($oState->getId()), ); $res = DBUtil::runQuery($aQuery); if (PEAR::isError($res)) { return $res; } $aOptions = array('noid' => true); if (empty($aTransitionIds)) { return; // don't fail if there are no transitions. } foreach ($aTransitionIds as $iTransitionId) { $res = DBUtil::autoInsert($sTable, array( 'state_id' => $oState->getId(), 'transition_id' => $iTransitionId, ), $aOptions); if (PEAR::isError($res)) { return $res; } } return; } // }}} // {{{ getTransitionsFrom /** * 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; } // }}} /* WILL NOT RESET THE WORKFLOW if changing to the -same- workflow */ function changeWorkflowOnDocument($oWorkflow, $oDocument) { $oDocument =& KTUtil::getObject('Document', $oDocument); // fix for 1049: workflows reset on document move. // this was the original purpose of "changeWorkflowOnDocument". if (is_null($oWorkflow)) { if ($oDocument->getWorkflowId() == null) { return true; // no definition. } } else { if ($oDocument->getWorkflowId() == $oWorkflow->getId()) { return true; // bail out, essentially. } } return KTWorkflowUtil::startWorkflowOnDocument($oWorkflow, $oDocument); } // {{{ startWorkflowOnDocument /** * Starts the workflow process on a document, placing it into the * starting workflow state for the given workflow. */ function startWorkflowOnDocument ($oWorkflow, $oDocument) { $oDocument =& KTUtil::getObject('Document', $oDocument); $iDocumentId = $oDocument->getId(); $oUser = User::get($_SESSION['userID']); $iPreviousMetadataVersion = $oDocument->getMetadataVersionId(); $oDocument->startNewMetadataVersion($oUser); KTDocumentUtil::copyMetadata($oDocument, $iPreviousMetadataVersion); if (!empty($oWorkflow)) { $oWorkflow =& KTUtil::getObject('KTWorkflow', $oWorkflow); $iWorkflowId = $oWorkflow->getId(); // null workflow == remove workflow. if (is_null($oWorkflow) || PEAR::isError($oWorkflow) || ($oWorkflow == false)) { return true; // delete and no-act. } $iStartStateId = $oWorkflow->getStartStateId(); if (empty($iStartStateId)) { return PEAR::raiseError(_kt('Cannot assign workflow with no starting state set')); } $oDocument->setWorkflowId($iWorkflowId); $oDocument->setWorkflowStateId($iStartStateId); $sTransactionComments = sprintf(_kt("Workflow \"%s\" started."), $oWorkflow->getHumanName()); } else { $oDocument->setWorkflowId(null); $oDocument->setWorkflowStateId(null); $sTransactionComments = _kt('Workflow removed from document.'); } $res = $oDocument->update(); if (PEAR::isError($res)) { return $res; } // create the document transaction record $oDocumentTransaction = new DocumentTransaction($oDocument, $sTransactionComments, 'ktcore.transactions.workflow_state_transition'); $oDocumentTransaction->create(); // FIXME does this function as expected? KTPermissionUtil::updatePermissionLookup($oDocument); if (isset($iStartStateId)) { $oTargetState = KTWorkflowState::get($iStartStateId); KTWorkflowUtil::informUsersForState($oTargetState, KTWorkflowUtil::getInformedForState($oTargetState), $oDocument, $oUser, ''); } return $res; } // }}} // {{{ getControlledActionsForWorkflow /** * Gets the actions that are controlled by a workflow. * * A controlled action is one that can be enabled or disabled by a * workflow state attached to this workflow. This allows for * actions such as "Delete" to not be allowed to occur during the * workflow, or for special actions such as "Publish" to only occur * when a particular workflow state is reached. */ function getControlledActionsForWorkflow($oWorkflow) { $iWorkflowId = KTUtil::getId($oWorkflow); $sTable = KTUtil::getTableName('workflow_actions'); $aQuery = array( "SELECT action_name FROM $sTable WHERE workflow_id = ?", array($iWorkflowId), ); return DBUtil::getResultArrayKey($aQuery, 'action_name'); } // }}} // {{{ setControlledActionsForWorkflow /** * Sets the actions that are controlled by a workflow. * * A controlled action is one that can be enabled or disabled by a * workflow state attached to this workflow. This allows for * actions such as "Delete" to not be allowed to occur during the * workflow, or for special actions such as "Publish" to only occur * when a particular workflow state is reached. */ function setControlledActionsForWorkflow($oWorkflow, $aActions) { $iWorkflowId = KTUtil::getId($oWorkflow); $sTable = KTUtil::getTableName('workflow_actions'); $aQuery = array( "DELETE FROM $sTable WHERE workflow_id = ?", array($iWorkflowId), ); $res = DBUtil::runQuery($aQuery); if (PEAR::isError($res)) { return $res; } $aOptions = array('noid' => true); if (!empty($aActions)) { foreach ($aActions as $sAction) { $res = DBUtil::autoInsert($sTable, array( 'workflow_id' => $iWorkflowId, 'action_name' => $sAction, ), $aOptions); if (PEAR::isError($res)) { return $res; } } } return; } // }}} // FIXME DEPRECATED // {{{ setEnabledActionsForState /** * Sets the actions that are enabled by this workflow state. * * A controlled action is one that can be enabled or disabled by a * workflow state attached to this workflow. This allows for * actions such as "Delete" to not be allowed to occur during the * workflow, or for special actions such as "Publish" to only occur * when a particular workflow state is reached. * * Only the enabled actions are tracked. Any actions controlled by * the workflow but not explicitly enabled are disabled. */ function setEnabledActionsForState($oState, $aActions) { $iStateId = KTUtil::getId($oState); $sTable = KTUtil::getTableName('workflow_state_actions'); $aQuery = array( "DELETE FROM $sTable WHERE state_id = ?", array($iStateId), ); $res = DBUtil::runQuery($aQuery); if (PEAR::isError($res)) { return $res; } if(!is_array($aActions)) return; $aOptions = array('noid' => true); foreach ($aActions as $sAction) { $res = DBUtil::autoInsert($sTable, array( 'state_id' => $iStateId, 'action_name' => $sAction, ), $aOptions); if (PEAR::isError($res)) { return $res; } } return; } // }}} function setDisabledActionsForState($oState, $aActions) { $iStateId = KTUtil::getId($oState); $sTable = KTUtil::getTableName('workflow_state_disabled_actions'); $aQuery = array( "DELETE FROM $sTable WHERE state_id = ?", array($iStateId), ); $res = DBUtil::runQuery($aQuery); if (PEAR::isError($res)) { return $res; } if(!is_array($aActions)) return; $aOptions = array('noid' => true); foreach ($aActions as $sAction) { $res = DBUtil::autoInsert($sTable, array( 'state_id' => $iStateId, 'action_name' => $sAction, ), $aOptions); if (PEAR::isError($res)) { return $res; } } return; } // {{{ getEnabledActionsForState /** * Gets the actions that are enabled by this workflow state. * * A controlled action is one that can be enabled or disabled by a * workflow state attached to this workflow. This allows for * actions such as "Delete" to not be allowed to occur during the * workflow, or for special actions such as "Publish" to only occur * when a particular workflow state is reached. * * Only the enabled actions are tracked. Any actions controlled by * the workflow but not explicitly enabled are disabled. */ function getEnabledActionsForState($oState) { $iStateId = KTUtil::getId($oState); $sTable = KTUtil::getTableName('workflow_state_actions'); $aQuery = array( "SELECT action_name FROM $sTable WHERE state_id = ?", array($iStateId), ); return DBUtil::getResultArrayKey($aQuery, 'action_name'); } // }}} function getDisabledActionsForState($oState) { $iStateId = KTUtil::getId($oState); $sTable = KTUtil::getTableName('workflow_state_disabled_actions'); $aQuery = array( "SELECT action_name FROM $sTable WHERE state_id = ?", array($iStateId), ); return DBUtil::getResultArrayKey($aQuery, 'action_name'); } // {{{ actionEnabledForDocument /** * Checks if a particular action is enabled to occur on a document * by virtue of its workflow and workflow state. * * A controlled action is one that can be enabled or disabled by a * workflow state attached to this workflow. This allows for * actions such as "Delete" to not be allowed to occur during the * workflow, or for special actions such as "Publish" to only occur * when a particular workflow state is reached. * * Only the enabled actions are tracked. Any actions controlled by * the workflow but not explicitly enabled are disabled. */ function actionEnabledForDocument($oDocument, $sName) { $oWorkflow =& KTWorkflow::getByDocument($oDocument); if (is_null($oWorkflow)) { return true; } // FIXME: The workflow_actions table that the method below uses is always empty! // It seems the new method was never followed though to completion. //if (!in_array($sName, KTWorkflowUtil::getControlledActionsForWorkflow($oWorkflow))) { // return true; //} $oState =& KTWorkflowState::getByDocument($oDocument); if (in_array($sName, KTWorkflowUtil::getDisabledActionsForState($oState))) { return false; } return true; } // }}} // {{{ getWorkflowForDocument /** * Gets the workflow that applies to the given document, returning * null if there is no workflow assigned. */ function getWorkflowForDocument ($oDocument, $aOptions = null) { $ids = KTUtil::arrayGet($aOptions, 'ids', false); if (is_a($oDocument, 'KTDocumentCore')) { $oDocument = $oDocument->getId(); } $oDocument = KTUtil::getObject('Document', $oDocument); $iWorkflowId = $oDocument->getWorkflowId(); if (PEAR::isError($iWorkflowId)) { return $iWorkflowId; } if (is_null($iWorkflowId)) { return $iWorkflowId; } if ($ids) { return $iWorkflowId; } return KTWorkflow::get($iWorkflowId); } // }}} // {{{ getWorkflowStateForDocument /** * Gets the workflow state that applies to the given document, * returning null if there is no workflow assigned. */ function getWorkflowStateForDocument ($oDocument, $aOptions = null) { $ids = KTUtil::arrayGet($aOptions, 'ids', false); if (is_a($oDocument, 'KTDocumentCore')) { $oDocument = $oDocument->getId(); } $oDocument = KTUtil::getObject('Document', $oDocument); $iWorkflowStateId = $oDocument->getWorkflowStateId(); if (PEAR::isError($iWorkflowStateId)) { return $iWorkflowStateId; } if (is_null($iWorkflowStateId)) { return $iWorkflowStateId; } if ($ids) { return $iWorkflowStateId; } return KTWorkflowState::get($iWorkflowStateId); } // }}} // {{{ getTransitionsForDocumentUser /** * Gets the transitions that are available for a document by virtue * of its workflow state, and also by virtue of the user that wishes * to perform the transition. * * In other words, ensures that the guard permission, role, group, * and/or user are met for the given user. */ function getTransitionsForDocumentUser($oDocument, $oUser) { $oState = KTWorkflowUtil::getWorkflowStateForDocument($oDocument); if (is_null($oState) || PEAR::isError($oState)) { return $oState; } $aTransitions = KTWorkflowUtil::getTransitionsFrom($oState); $aEnabledTransitions = array(); foreach ($aTransitions as $oTransition) { // keeping this around to make coding the replacements easier. /* $iPermissionId = $oTransition->getGuardPermissionId(); if ($iPermissionId) { $oPermission =& KTPermission::get($iPermissionId); if (!KTPermissionUtil::userHasPermissionOnItem($oUser, $oPermission, $oDocument)) { continue; } } $iGroupId = $oTransition->getGuardGroupId(); if ($iGroupId) { $oGroup =& Group::get($iGroupId); $res = GroupUtil::getMembershipReason($oUser, $oGroup); if (!is_string($res)) { continue; } } $iRoleId = $oTransition->getGuardRoleId(); if ($iRoleId) { $oRoleAllocation = RoleAllocation::getAllocationsForFolderAndRole($oDocument->getFolderID(), $iRoleId); if ($oRoleAllocation == null) { // no role allocation, no fulfillment. continue; } if (!$oRoleAllocation->hasMember($oUser)) { continue; } } $iConditionId = $oTransition->getGuardConditionId(); if ($iConditionId) { if (!KTSearchUtil::testConditionOnDocument($iConditionId, $oDocument)) { continue; } } */ $aGuardTriggers = KTWorkflowUtil::getGuardTriggersForTransition($oTransition); if (PEAR::isError($aGuardTriggers)) { return $aGuardTriggers; // error out? } $bBreak = false; foreach ($aGuardTriggers as $oTrigger) { if (!$oTrigger->allowTransition($oDocument, $oUser)) { $bBreak = true; break; } } if ($bBreak) { continue; } $aEnabledTransitions[] = $oTransition; } return $aEnabledTransitions; } // }}} // {{{ performTransitionOnDocument /** * Performs a workflow transition on a document, changing it from * one workflow state to another, with potential side effects (user * scripts, and so forth). * * This function currently assumes that the user in question is * allowed to perform the transition and that all the guard * functionality on the transition has passed. */ function performTransitionOnDocument($oTransition, $oDocument, $oUser, $sComments) { $oWorkflow =& KTWorkflow::getByDocument($oDocument); if (empty($oWorkflow)) { return PEAR::raiseError(_kt("Document has no workflow")); } if (PEAR::isError($oWorkflow)) { return $oWorkflow; } $oSourceState =& KTWorkflowUtil::getWorkflowStateForDocument($oDocument); // walk the action triggers. $aActionTriggers = KTWorkflowUtil::getActionTriggersForTransition($oTransition); if (PEAR::isError($aActionTriggers)) { return $aActionTriggers; // error out? } foreach ($aActionTriggers as $oTrigger) { $res = $oTrigger->precheckTransition($oDocument, $oUser); if (PEAR::isError($res)) { return $res; } } $iPreviousMetadataVersion = $oDocument->getMetadataVersionId(); $oDocument->startNewMetadataVersion($oUser); KTDocumentUtil::copyMetadata($oDocument, $iPreviousMetadataVersion); $iStateId = $oTransition->getTargetStateId(); $oDocument->setWorkflowStateId($iStateId); $res = $oDocument->update(); if (PEAR::isError($res)) { return $res; } $oTargetState =& KTWorkflowState::get($iStateId); $sSourceState = $oSourceState->getName(); $sTargetState = $oTargetState->getName(); // create the document transaction record $sTransactionComments = sprintf(_kt("Workflow state changed from %s to %s"), $sSourceState, $sTargetState); if ($sComments) { $sTransactionComments .= _kt("; Reason given was: ") . $sComments; } $oDocumentTransaction = new DocumentTransaction($oDocument, $sTransactionComments, 'ktcore.transactions.workflow_state_transition'); $oDocumentTransaction->create(); // walk the action triggers. foreach ($aActionTriggers as $oTrigger) { $res = $oTrigger->performTransition($oDocument, $oUser); if (PEAR::isError($res)) { return $res; } } KTPermissionUtil::updatePermissionLookup($oDocument); KTWorkflowUtil::informUsersForState($oTargetState, KTWorkflowUtil::getInformedForState($oTargetState), $oDocument, $oUser, $sComments); return true; } // }}} // {{{ informUsersForState function informUsersForState($oState, $aInformed, $oDocument, $oUser, $sComments) { // say no to duplicates. KTWorkflowNotification::clearNotificationsForDocument($oDocument); $aUsers = array(); $aGroups = array(); $aRoles = array(); foreach (KTUtil::arrayGet($aInformed,'user',array()) as $iUserId) { $oU = User::get($iUserId); if (PEAR::isError($oU) || ($oU == false)) { continue; } else { $aUsers[$oU->getId()] = $oU; } } foreach (KTUtil::arrayGet($aInformed,'group',array()) as $iGroupId) { $oG = Group::get($iGroupId); if (PEAR::isError($oG) || ($oG == false)) { continue; } else { $aGroups[$oG->getId()] = $oG; } } foreach (KTUtil::arrayGet($aInformed,'role',array()) as $iRoleId) { $oR = Role::get($iRoleId); if (PEAR::isError($oR) || ($oR == false)) { continue; } else { $aRoles[] = $oR; } } // FIXME extract this into a util - I see us using this again and again. // start with roles ... roles _only_ ever contain groups. foreach ($aRoles as $oRole) { // do NOT alert anonymous or Everyone roles - that would be very scary. $iRoleId = KTUtil::getId($oRole); if (($iRoleId == -3) || ($iRoleId == -4)) { continue; } // first try on the document, then the folder above it. $oRoleAllocation = DocumentRoleAllocation::getAllocationsForDocumentAndRole($oDocument->getId(), $iRoleId); if (is_null($oRoleAllocation)) { // if we don't get a document role, try folder role. $oRoleAllocation = RoleAllocation::getAllocationsForFolderAndRole($oDocument->getFolderID(), $oRole->getId()); } if (is_null($oRoleAllocation) || PEAR::isError($oRoleAllocation)) { continue; } $aRoleUsers = $oRoleAllocation->getUsers(); $aRoleGroups = $oRoleAllocation->getGroups(); foreach ($aRoleUsers as $id => $oU) { $aUsers[$id] = $oU; } foreach ($aRoleGroups as $id => $oGroup) { $aGroups[$id] = $oGroup; } } // we now have a (potentially overlapping) set of groups, which may // have subgroups. // // what we need to do _now_ is build a canonical set of groups, and then // generate the singular user-base. $aGroupMembershipSet = GroupUtil::buildGroupArray(); $aAllIds = array_keys($aGroups); foreach ($aGroups as $id => $oGroup) { $aAllIds = kt_array_merge($aGroupMembershipSet[$id], $aAllIds); } foreach ($aAllIds as $id) { if (!array_key_exists($id, $aGroups)) { $aGroups[$id] = Group::get($id); } } // now, merge this (again) into the user-set. foreach ($aGroups as $oGroup) { $aNewUsers = $oGroup->getMembers(); foreach ($aNewUsers as $oU) { $id = $oU->getId(); if (!array_key_exists($id, $aUsers)) { $aUsers[$id] = $oU; } } } // and done. foreach ($aUsers as $oU) { if (!PEAR::isError($oU)) { KTWorkflowNotification::newNotificationForDocument($oDocument, $oU, $oState, $oUser, $sComments); } } } // }}} // {{{ setInformedForState /** * Sets which users/groups/roles are to be informed when a state is * arrived at. */ function setInformedForState(&$oState, $aInformed) { $oDescriptor =& KTPermissionUtil::getOrCreateDescriptor($aInformed); if (PEAR::isError($oDescriptor)) { return $oDescriptor; } $iOldDescriptorId = $oState->getInformDescriptorId(); $oState->setInformDescriptorId($oDescriptor->getId()); $res = $oState->update(); if (PEAR::isError($res)) { $oState->setInformDescriptorId($iOldDescriptorId); return $res; } return $res; } // }}} // {{{ getInformedForState /** * Gets which users/groups/roles are to be informed when a state is * arrived at. */ function getInformedForState($oState) { $iDescriptorId = $oState->getInformDescriptorId(); if (empty($iDescriptorId)) { return array(); } return KTPermissionUtil::getAllowedForDescriptor($iDescriptorId); } // }}} // retrieves the triggers for a given transition in their WorkflowTrigger form. function getTriggersForTransition($oTransition) { $oKTWorkflowTriggerRegistry =& KTWorkflowTriggerRegistry::getSingleton(); $aTriggers = array(); $aTriggerInstances = KTWorkflowTriggerInstance::getByTransition($oTransition); foreach ($aTriggerInstances as $oTriggerInstance) { $oTrigger = $oKTWorkflowTriggerRegistry->getWorkflowTrigger($oTriggerInstance->getNamespace()); if (PEAR::isError($oTrigger)) { return $oTrigger; } $oTrigger->loadConfig($oTriggerInstance); $aTriggers[] = $oTrigger; } return $aTriggers; } function getGuardTriggersForTransition($oTransition) { $aTriggers = KTWorkflowUtil::getTriggersForTransition($oTransition); if (PEAR::isError($aTriggers)) { return $aTriggers; } $aGuards = array(); foreach ($aTriggers as $oTrigger) { $aInfo = $oTrigger->getInfo(); if ($aInfo['guard']) { $aGuards[] = $oTrigger; } } return $aGuards; } function getActionTriggersForTransition($oTransition) { $aTriggers = KTWorkflowUtil::getTriggersForTransition($oTransition); if (PEAR::isError($aTriggers)) { return $aTriggers; } $aGuards = array(); foreach ($aTriggers as $oTrigger) { $aInfo = $oTrigger->getInfo(); if ($aInfo['action']) { $aGuards[] = $oTrigger; } } return $aGuards; } function replaceState($oState, $oReplacement) { $state_id = KTUtil::getId($oState); $replacement_id = KTUtil::getId($oReplacement); // we need to convert: // - documents // - transitions // before we do a delete. $doc = KTUtil::getTableName('document_metadata_version'); $aDocQuery = array( "UPDATE $doc SET workflow_state_id = ? WHERE workflow_state_id = ?", array($replacement_id, $state_id), ); $res = DBUtil::runQuery($aDocQuery); if (PEAR::isError($res)) { return $res; } $wf = KTUtil::getTableName('workflow_transitions'); $aTransitionQuery = array( "UPDATE $wf SET target_state_id = ? WHERE target_state_id = ?", array($replacement_id, $state_id), ); $res = DBUtil::runQuery($aTransitionQuery); if (PEAR::isError($res)) { return $res; } $wf = KTUtil::getTableName('workflow_state_transitions'); $aTransitionQuery = array( "DELETE FROM $wf WHERE state_id = ?", array($state_id), ); $res = DBUtil::runQuery($aTransitionQuery); if (PEAR::isError($res)) { return $res; } Document::clearAllCaches(); } } class KTWorkflowTriggerRegistry { var $triggers; function KTWorkflowTriggerRegistry() { $this->triggers = array(); } static function &getSingleton () { if (!KTUtil::arrayGet($GLOBALS['_KT_PLUGIN'], 'oKTWorkflowTriggerRegistry')) { $GLOBALS['_KT_PLUGIN']['oKTWorkflowTriggerRegistry'] = new KTWorkflowTriggerRegistry; } return $GLOBALS['_KT_PLUGIN']['oKTWorkflowTriggerRegistry']; } function registerWorkflowTrigger($sNamespace, $sClassname, $sPath) { $this->triggers[$sNamespace] = array('class' => $sClassname, 'path' => $sPath); } function getWorkflowTrigger($sNamespace) { $aInfo = KTUtil::arrayGet($this->triggers, $sNamespace, null); if (is_null($aInfo)) { return PEAR::raiseError(sprintf(_kt("Unable to find workflow trigger: %s"), $sNamespace)); } require_once($aInfo['path']); return new $aInfo['class']; } // get a keyed list of workflow triggers function listWorkflowTriggers() { $triggerlist = array(); foreach ($this->triggers as $sNamespace => $aTrigInfo) { $oTriggerObj = $this->getWorkflowTrigger($sNamespace); $triggerlist[$sNamespace] = $oTriggerObj->getInfo(); } // FIXME do we want to order this alphabetically? return $triggerlist; } }