diff --git a/config/tableMappings.inc b/config/tableMappings.inc index 9d66533..aed0a40 100644 --- a/config/tableMappings.inc +++ b/config/tableMappings.inc @@ -146,4 +146,6 @@ $default->role_allocations_table = "role_allocations"; $default->plugins_table = "plugins"; $default->document_metadata_version_table = "document_metadata_version"; $default->document_content_version_table = "document_content_version"; +$default->trigger_selection_table = "trigger_selection"; +$default->type_workflow_map_table = "type_workflow_map"; ?> diff --git a/edit.php b/edit.php index 76698e1..bf42bdd 100644 --- a/edit.php +++ b/edit.php @@ -29,6 +29,8 @@ require_once(KT_LIB_DIR . "/widgets/FieldsetDisplayRegistry.inc.php"); require_once(KT_LIB_DIR . "/actions/documentaction.inc.php"); require_once(KT_LIB_DIR . "/browse/browseutil.inc.php"); +require_once(KT_LIB_DIR . '/triggers/triggerregistry.inc.php'); + class KTEditDocumentDispatcher extends KTStandardDispatcher { var $bAutomaticTransaction = true; @@ -317,7 +319,7 @@ class KTEditDocumentDispatcher extends KTStandardDispatcher { // FIXME handle md versions. //return '
' . print_r($field_values, true) . ''; $this->startTransaction(); - + $iPreviousMetadataVersionId = $oDocument->getMetadataVersionId(); $oDocument->startNewMetadataVersion($this->oUser); if (PEAR::isError($res)) { $this->errorRedirectToMain('Unable to create a metadata version of the document.'); @@ -427,6 +429,21 @@ class KTEditDocumentDispatcher extends KTStandardDispatcher { } else { + + // post-triggers. + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('edit', 'postValidate'); + + foreach ($aTriggers as $aTrigger) { + $sTrigger = $aTrigger[0]; + $oTrigger = new $sTrigger; + $aInfo = array( + "document" => $oDocument, + ); + $oTrigger->setInfo($aInfo); + $ret = $oTrigger->postValidate(); + } + $this->commitTransaction(); // now we need to say we're ok. diff --git a/lib/documentmanagement/documentutil.inc.php b/lib/documentmanagement/documentutil.inc.php index 6670703..a6f8014 100644 --- a/lib/documentmanagement/documentutil.inc.php +++ b/lib/documentmanagement/documentutil.inc.php @@ -43,6 +43,9 @@ require_once(KT_LIB_DIR . "/subscriptions/subscriptions.inc.php"); require_once(KT_LIB_DIR . '/triggers/triggerregistry.inc.php'); require_once(KT_LIB_DIR . "/foldermanagement/Folder.inc"); +// WORKFLOW +require_once(KT_LIB_DIR . '/workflow/workflowutil.inc.php'); + class KTDocumentUtil { function createMetadataVersion($oDocument) { if (is_numeric($oDocument)) { @@ -76,7 +79,7 @@ class KTDocumentUtil { $oVersionDFL->setDocumentID($iVersionDocumentID); $res = $oVersionDFL->create(); } - + return $oVersionDocument; } @@ -317,6 +320,7 @@ class KTDocumentUtil { $aRow['metadata_version_id'] = $iNewMetadataVersion; DBUtil::autoInsert($sTable, $aRow); } + } // {{{ setIncomplete @@ -445,6 +449,20 @@ class KTDocumentUtil { $oSubscriptionEvent = new SubscriptionEvent(); $oFolder = Folder::get($oDocument->getFolderID()); $oSubscriptionEvent->AddDocument($oDocument, $oFolder); + + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('add', 'postValidate'); + + foreach ($aTriggers as $aTrigger) { + $sTrigger = $aTrigger[0]; + $oTrigger = new $sTrigger; + $aInfo = array( + "document" => $oDocument, + ); + $oTrigger->setInfo($aInfo); + $ret = $oTrigger->postValidate(); + + } $oUploadChannel->sendMessage(new KTUploadGenericMessage(_("All done..."))); diff --git a/lib/workflow/workflowutil.inc.php b/lib/workflow/workflowutil.inc.php index ee2effc..88e3886 100644 --- a/lib/workflow/workflowutil.inc.php +++ b/lib/workflow/workflowutil.inc.php @@ -87,6 +87,10 @@ class KTWorkflowUtil { $iDocumentId = KTUtil::getId($oDocument); $iWorkflowId = KTUtil::getId($oWorkflow); $oWorkflow =& KTWorkflow::get($iWorkflowId); + // 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('Cannot assign workflow with no starting state set'); @@ -101,6 +105,46 @@ class KTWorkflowUtil { return DBUtil::autoInsert($sTable, $aValues, $aOptions); } // }}} + + + // {{{ changeWorkflowOnDocument + /** + * Starts the workflow process on a document, placing it into the + * starting workflow state for the given workflow. + */ + function changeWorkflowOnDocument ($oWorkflow, $oDocument) { + $iDocumentId = KTUtil::getId($oDocument); + $iWorkflowId = KTUtil::getId($oWorkflow); + $oWorkflow =& KTWorkflow::get($iWorkflowId); + + if (empty($iStartStateId)) { + return PEAR::raiseError('Cannot assign workflow with no starting state set'); + } + $oOldWorkflow = KTWorkflowUtil::getWorkflowForDocument($oDocument); + if ((!(PEAR::isError($oOldWorkflow) || ($oOldWorkflow == false))) && ($oOldWorkflow->getId() == $oWorkflow->getId())) { + return true; // all fine - no change required. + } + + $sQuery = 'DELETE FROM ' . KTUtil::getTableName('workflow_documents'); + $sQuery .= ' WHERE document_id = ?'; + $aParams = array($iDocumentId); + DBUtil::runQuery(array($sQuery, $aParams)); + + if (is_null($oWorkflow) || PEAR::isError($oWorkflow) || ($oWorkflow == false)) { + return true; // delete and no-act. + } + + $iStartStateId = $oWorkflow->getStartStateId(); + $aOptions = array('noid' => true); + $aValues = array( + 'document_id' => $iDocumentId, + 'workflow_id' => $iWorkflowId, + 'state_id' => $iStartStateId, + ); + $sTable = KTUtil::getTableName('workflow_documents'); + return DBUtil::autoInsert($sTable, $aValues, $aOptions); + } + // }}} // {{{ getControlledActionsForWorkflow /** diff --git a/plugins/ktcore/KTDocumentActions.php b/plugins/ktcore/KTDocumentActions.php index 8d836ea..6c79b34 100644 --- a/plugins/ktcore/KTDocumentActions.php +++ b/plugins/ktcore/KTDocumentActions.php @@ -70,6 +70,12 @@ class KTDocumentCheckOutAction extends KTDocumentAction { if ($res !== true) { return $res; } + // since we actually check the doc out, then download it ... + if (($_REQUEST[$this->event_var] == 'checkout_final') && ($this->oDocument->getCheckedOutUserID() == $_SESSION['userID'])) { + return true; + } + + // "normal". if ($this->oDocument->getIsCheckedOut()) { $_SESSION['KTErrorMessage'][] = _("This document is already checked out"); controllerRedirect('viewDocument', 'fDocumentId=' . $this->oDocument->getId()); diff --git a/plugins/ktstandard/KTStandardPlugin.php b/plugins/ktstandard/KTStandardPlugin.php index 5e19ed1..dbb79ff 100644 --- a/plugins/ktstandard/KTStandardPlugin.php +++ b/plugins/ktstandard/KTStandardPlugin.php @@ -6,3 +6,4 @@ require_once('KTDiscussion.php'); require_once('KTEmail.php'); require_once('KTIndexer.php'); +require_once('KTWorkflowAssociation.php'); \ No newline at end of file diff --git a/plugins/ktstandard/KTWorkflowAssociation.php b/plugins/ktstandard/KTWorkflowAssociation.php new file mode 100644 index 0000000..d6f26b9 --- /dev/null +++ b/plugins/ktstandard/KTWorkflowAssociation.php @@ -0,0 +1,113 @@ +registerTrigger('add', 'postValidate', 'KTWADAddTrigger', + 'ktstandard.triggers.workflowassociation.addDocument'); + $this->registerTrigger('moveDocument', 'postValidate', 'KTWADMoveTrigger', + 'ktstandard.triggers.workflowassociation.moveDocument'); + $this->registerTrigger('edit', 'postValidate', 'KTWADEditTrigger', + 'ktstandard.triggers.workflowassociation.editDocument'); + $this->registerAdminPage('workflow_allocation', 'WorkflowAllocationSelection', + 'documents', _('Automatic Workflow Assignments'), + _('Configure how documents are allocated to workflows.'), 'workflow/adminpage.php'); + $this->registeri18n('knowledgeTree', KT_DIR . '/i18n'); + } +} + +// base class for delegation. +class KTWorkflowAssociationDelegator { + var $_handler; + var $_document; + + function KTWorkflowAssociationDelegator() { + + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('workflow', 'objectModification'); + + // if we have _some_ triggers. + if (!empty($aTriggers)) { + $sQuery = 'SELECT selection_ns FROM ' . KTUtil::getTableName('trigger_selection'); + $sQuery .= ' WHERE event_ns = ?'; + $aParams = array('ktstandard.workflowassociation.handler'); + $res = DBUtil::getOneResultKey(array($sQuery, $aParams), 'selection_ns'); + + if (PEAR::isError($res)) { $this->_handler = new KTWorkflowAssociationHandler(); } + + if (array_key_exists($res, $aTriggers)) { + $this->_handler = new $aTriggers[$res][0]; + } + else { + global $default; + $default->log->debug('KTWorkflowAssociationDelegator failed to acquire trigger selection'); + $this->_handler = new KTWorkflowAssociationHandler(); + } + } + else { + $this->_handler = new KTWorkflowAssociationHandler(); + } + } + + function applyWorkflow($oWorkflow) { + return true; + } + + function setInfo($aOptions) { + $this->_document = $aOptions['document']; + } + + function postValidate() { + return KTWorkflowUtil::getWorkflowForDocument($this->_document); + } +} + +// Add +class KTWADAddTrigger extends KTWorkflowAssociationDelegator { + function postValidate() { + $oWorkflow = $this->_handler->addTrigger($this->_document); + $ret = KTWorkflowUtil::startWorkflowOnDocument($oWorkflow, $this->_document); + } +} + +// Edit +class KTWADEditTrigger extends KTWorkflowAssociationDelegator { + function postValidate() { + $oWorkflow = $this->_handler->editTrigger($this->_document); + $ret = KTWorkflowUtil::changeWorkflowOnDocument($oWorkflow, $this->_document); + } +} + +// Move +class KTWADMoveTrigger extends KTWorkflowAssociationDelegator { + function postValidate() { + $oWorkflow = $this->_handler->moveTrigger($this->_document); + $ret = KTWorkflowUtil::changeWorkflowOnDocument($oWorkflow, $this->_document); + } +} + +// "base class" for fallback. should be subclassed and cross-referenced, otherwise +// has no impact when called. +class KTWorkflowAssociationHandler { + function addTrigger($oDocument) { return KTWorkflowUtil::getWorkflowForDocument($oDocument); } + function editTrigger($oDocument) { return KTWorkflowUtil::getWorkflowForDocument($oDocument); } + function moveTrigger($oDocument) { return KTWorkflowUtil::getWorkflowForDocument($oDocument); } +} + + +$oPluginRegistry =& KTPluginRegistry::getSingleton(); +$oPluginRegistry->registerPlugin('KTWorkflowAssociationPlugin', 'ktstandard.workflowassociation.plugin', __FILE__); + + +/* include others */ + +require_once('workflow/TypeAssociator.php'); + +?> diff --git a/plugins/ktstandard/workflow/TypeAssociator.php b/plugins/ktstandard/workflow/TypeAssociator.php new file mode 100644 index 0000000..81bed7f --- /dev/null +++ b/plugins/ktstandard/workflow/TypeAssociator.php @@ -0,0 +1,128 @@ +registerTrigger('workflow', 'objectModification', 'DocumentTypeWorkflowAssociator', + 'ktstandard.triggers.workflowassociation.documenttype.handler'); + + $sQuery = 'SELECT selection_ns FROM ' . KTUtil::getTableName('trigger_selection'); + $sQuery .= ' WHERE event_ns = ?'; + $aParams = array('ktstandard.workflowassociation.handler'); + $res = DBUtil::getOneResultKey(array($sQuery, $aParams), 'selection_ns'); + + if ($res == 'ktstandard.triggers.workflowassociation.documenttype.handler') { + $this->registerAdminPage('workflow_type_allocation', 'WorkflowTypeAllocationDispatcher', + 'documents', _('Workflow Allocation by Document Types'), + _('This installation assigns workflows by Document Type. Configure this process here.'), __FILE__); + $this->registeri18n('knowledgeTree', KT_DIR . '/i18n'); + } + + } +} + +class DocumentTypeWorkflowAssociator extends KTWorkflowAssociationHandler { + function addTrigger($oDocument) { + return $oW = $this->getWorkflowForType($oDocument->getDocumentTypeID()); + } + + function editTrigger($oDocument) { + return $oW = $this->getWorkflowForType($oDocument->getDocumentTypeID()); + } + + function getWorkflowForType($iDocTypeId) { + if (is_null($iDocTypeId)) { return null; } + + $sQuery = 'SELECT `workflow_id` FROM ' . KTUtil::getTableName('type_workflow_map'); + $sQuery .= ' WHERE `document_type_id` = ?'; + $aParams = array($iDocTypeId); + $res = DBUtil::getOneResultKey(array($sQuery, $aParams), 'workflow_id'); + if (PEAR::isError($res)) { + return null; + } + return KTWorkflow::get($res); + } +} + +class WorkflowTypeAllocationDispatcher extends KTAdminDispatcher { + var $bAutomaticTransaction = true; + var $sSection = 'administration'; + + function check() { + $res = parent::check(); + if (!$res) { return false; } + + $this->aBreadcrumbs[] = array('url' => $_SERVER['PHP_SELF'], 'name'=>_('Workflow Allocation by Document Types')); + + return true; + } + + function do_main() { + $sQuery = 'SELECT document_type_id, workflow_id FROM ' . KTUtil::getTableName('type_workflow_map'); + $aParams = array(); + $res = DBUtil::getResultArray(array($sQuery, $aParams)); + $aWorkflows = KTWorkflow::getList(); + $aTypes = DocumentType::getList(); + + $aTypeMapping = array(); + if (PEAR::isError($res)) { + $this->oPage->addError(_('Failed to get type mapping: ') . $res->getMessage()); + } else { + foreach ($res as $aRow) { + $aTypeMapping[$aRow['document_type_id']] = $aRow['workflow_id']; + } + } + + $oTemplate =& $this->oValidator->validateTemplate('ktstandard/workflow/type_allocation'); + $oTemplate->setData(array( + 'context' => $this, + 'types_mapping' => $aTypeMapping, + 'types' => $aTypes, + 'workflows' => $aWorkflows, + )); + return $oTemplate->render(); + } + + function isActiveWorkflow($oType, $oWorkflow, $types_mapping) { + if (!array_key_exists($oType->getId(), $types_mapping)) { return false; } + else { + return $types_mapping[$oType->getId()] == $oWorkflow->getId(); + } + } + + function do_update() { + $types_mapping = (array) KTUtil::arrayGet($_REQUEST, 'fDocumentTypeAssignment'); + + $aWorkflows = KTWorkflow::getList(); + $aTypes = DocumentType::getList(); + + $sQuery = 'DELETE FROM ' . KTUtil::getTableName('type_workflow_map'); + $aParams = array(); + DBUtil::runQuery(array($sQuery, $aParams)); + + $aOptions = array('noid' => true); + $sTable = KTUtil::getTableName('type_workflow_map'); + foreach ($aTypes as $oType) { + $t = $types_mapping[$oType->getId()]; + if ($t == null) { $t = null; } + $res = DBUtil::autoInsert($sTable, array( + 'document_type_id' => $oType->getId(), + 'workflow_id' => $t, + ), $aOptions); + } + + $this->successRedirectToMain(_('Type mapping updated.')); + } +} + + +$oPluginRegistry =& KTPluginRegistry::getSingleton(); +$oPluginRegistry->registerPlugin('KTDocTypeWorkflowAssociationPlugin', 'ktstandard.workflowassociation.documenttype.plugin', __FILE__); + + +?> \ No newline at end of file diff --git a/plugins/ktstandard/workflow/adminpage.php b/plugins/ktstandard/workflow/adminpage.php new file mode 100644 index 0000000..e6ba062 --- /dev/null +++ b/plugins/ktstandard/workflow/adminpage.php @@ -0,0 +1,85 @@ +aBreadcrumbs[] = array('url' => $_SERVER['PHP_SELF'], 'name'=> _('Automatic Workflow Assignments')); + + return true; + } + + function do_main() { + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('workflow', 'objectModification'); + + $aFields = array(); + $aVocab = array(); + $aVocab[] = 'No automatic assignment'; + foreach ($aTriggers as $aTrigger) { + $aVocab[$aTrigger[2]] = $aTrigger[0]; + } + $aFields[] = new KTLookupWidget(_('Workflow Plugins'), _('Plugins providing workflow allocators.'),'selection_ns', $this->getHandler(), $this->oPage, true, null, null, array('vocab' => $aVocab)); + + $oTemplate =& $this->oValidator->validateTemplate('ktstandard/workflow/allocator_selection'); + $oTemplate->setData(array( + 'context' => $this, + 'trigger_fields' => $aFields, + )); + return $oTemplate->render(); + } + + function getHandler() { + $sQuery = 'SELECT selection_ns FROM ' . KTUtil::getTableName('trigger_selection'); + $sQuery .= ' WHERE event_ns = ?'; + $aParams = array('ktstandard.workflowassociation.handler'); + $res = DBUtil::getOneResultKey(array($sQuery, $aParams), 'selection_ns'); + return $res; + + } + + function do_assign_handler() { + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('workflow', 'objectModification'); + + $selection_ns = KTUtil::arrayGet($_REQUEST, 'selection_ns'); + if (empty($selection_ns)) { + $sQuery = 'DELETE FROM ' . KTUtil::getTableName('trigger_selection'); + $sQuery .= ' WHERE event_ns = ?'; + $aParams = array('ktstandard.workflowassociation.handler'); + DBUtil::runQuery(array($sQuery, $aParams)); + $this->successRedirectToMain(_('Handler removed.')); + } else { + if (!array_key_exists($selection_ns, $aTriggers)) { + $this->errorRedirectToMain(_('Invalid assignment')); + } + + + // clear + $sQuery = 'DELETE FROM ' . KTUtil::getTableName('trigger_selection'); + $sQuery .= ' WHERE event_ns = ?'; + $aParams = array('ktstandard.workflowassociation.handler'); + DBUtil::runQuery(array($sQuery, $aParams)); + + // set + $sQuery = 'INSERT INTO ' . KTUtil::getTableName('trigger_selection'); + $sQuery .= ' (event_ns, selection_ns)'; + $sQuery .= ' VALUES ("ktstandard.workflowassociation.handler",?)'; + $aParams = array($selection_ns); + DBUtil::runQuery(array($sQuery, $aParams)); + $this->successRedirectToMain(_('Handler set.')); + } + } +} + +?> diff --git a/sql/mysql/install/structure.sql b/sql/mysql/install/structure.sql index 7bbeef6..fd8822b 100644 --- a/sql/mysql/install/structure.sql +++ b/sql/mysql/install/structure.sql @@ -1091,6 +1091,29 @@ CREATE TABLE `time_unit_lookup` ( -- -------------------------------------------------------- -- +-- Table structure for table `trigger_selection` +-- + +CREATE TABLE `trigger_selection` ( + `event_ns` varchar(255) not null default '' UNIQUE, + PRIMARY KEY (`event_ns`), + `selection_ns` varchar(255) not null default '' +) TYPE=InnoDB; + +CREATE TABLE `type_workflow_map` ( + `document_type_id` INT(11) NOT NULL DEFAULT 0 UNIQUE, + PRIMARY KEY (`document_type_id`), + `workflow_id` INT UNSIGNED -- can be null. + ) TYPE=InnoDB; + +CREATE TABLE `folder_workflow_map` ( + `folder_id` INT(11) NOT NULL DEFAULT 0 UNIQUE, + PRIMARY KEY (`folder_id`), + `workflow_id` INT(11) -- can be null. + ) TYPE=InnoDB; + +-- -------------------------------------------------------- +-- -- Table structure for table `units_lookup` -- diff --git a/sql/mysql/upgrade/2.99.8/trigger_selection.sql b/sql/mysql/upgrade/2.99.8/trigger_selection.sql new file mode 100644 index 0000000..ad6bd26 --- /dev/null +++ b/sql/mysql/upgrade/2.99.8/trigger_selection.sql @@ -0,0 +1,5 @@ +CREATE TABLE `trigger_selection` ( + `event_ns` varchar(255) not null default '' UNIQUE, + PRIMARY KEY (`event_ns`), + `selection_ns` varchar(255) not null default '' +) TYPE=InnoDB;; diff --git a/sql/mysql/upgrade/2.99.8/type_workflow_map.sql b/sql/mysql/upgrade/2.99.8/type_workflow_map.sql new file mode 100644 index 0000000..4a14d59 --- /dev/null +++ b/sql/mysql/upgrade/2.99.8/type_workflow_map.sql @@ -0,0 +1,11 @@ +CREATE TABLE `type_workflow_map` ( + `document_type_id` INT(11) NOT NULL DEFAULT 0 UNIQUE, + PRIMARY KEY (`document_type_id`), + `workflow_id` INT UNSIGNED -- can be null. + ) TYPE=InnoDB; + +CREATE TABLE `folder_workflow_map` ( + `folder_id` INT(11) NOT NULL DEFAULT 0 UNIQUE, + PRIMARY KEY (`folder_id`), + `workflow_id` INT(11) -- can be null. + ) TYPE=InnoDB; \ No newline at end of file diff --git a/templates/ktstandard/workflow/allocator_selection.smarty b/templates/ktstandard/workflow/allocator_selection.smarty new file mode 100644 index 0000000..a8b9d78 --- /dev/null +++ b/templates/ktstandard/workflow/allocator_selection.smarty @@ -0,0 +1,18 @@ +
{i18n}Workflow types are allocated by Document Type in this +KnowledgeTree installation. Documents will be assigned workflow based on their document +type, and will have their allocated workflows changed if their types change. Naturally, +if the workflows change then the documents will lose any "progress" in the old +workflow.{/i18n}
+ +{i18n}Document Types with no pre-allocated workflow will +either have no workflow set (for new documents) or keep their old workflow (for +documents which have had their type changed.{/i18n}
+ + \ No newline at end of file