Source for file WorkflowUtil.class.php
Documentation is available at WorkflowUtil.class.php
* @copyright (c) 2002, Zikula Development Team
* @link http://www.zikula.org
* @version $Id: WorkflowUtil.class.php 24342 2008-06-06 12:03:14Z markwest $
* @license GNU/LGPL - http://www.gnu.org/copyleft/lgpl.html
* @author Drak, drak@hostnuke.com Feb 2006
* Inspired by the Pagesetter workflow system by Jørn Wildt
* From a developers standpoint, we only use this class to address workflows
* as the rest is for internal use by the workflow engine.
* @subpackage WorkflowUtil
* @param string $schema name of workflow scheme
* @param string $module name of module
* @return mixed string of XML, or false
function loadSchema($schema = 'standard', $module = null)
if (!isset ($workflows)) {
// if no module specified, default to calling module
if (isset ($workflows[$module][$schema])) {
return $workflows[$module][$schema];
return pn_exit("$module module specified doesnt exist");
return pn_exit("couldnt find workflow file: $path");
// instanciate Workflow Parser
// parse workflow and return workflow object
$workflowSchema = $parser->parse($workflowXML, $schema, $module);
$workflows[$module][$schema] = $workflowSchema;
// return workflow object
return $workflows[$module][$schema];
* Find the path of the file by searching overrides and the module location
* @param string $file name of file to find (can include relative path)
* @return mixed string of path or bool false
function _findpath($file, $module= null)
// if no module specified, default to calling module
return pn_exit("$module module specified doesnt exist");
$moduledir = $modinfo['directory'];
// determine which folder to look in (system or modules)
if ($modinfo['type'] == 3) { // system module
$modulepath = "system/$moduledir";
if ($modinfo['type'] == 2) { // non system module
$modulepath = "modules/$moduledir";
return pn_exit("unsupported module type");
// ensure module is active
if (!$modinfo['state'] == 3) {
return pn_exit("module is not active");
// find the file in themes or config (for overrides), else module dir
* @param string $schema name of workflow schema
* @param array $obj data object
* @param string $actionID action to perform
* @param string $table table where data will be stored (default = null)
* @param string $module name of module (defaults calling module)
* @param int $id ID column of table
function executeAction($schema, &$obj, $actionID, $table= null, $module= null, $idcolumn= 'id')
return pn_exit('$obj needs to be an array');
return pn_exit('$schema needs to be named');
// default to calling module
return $workflow->executeAction($actionID, $obj, $stateID);
* delete workflows for module (used module uninstall time)
// this is a cheat to delete all items in table with value $module
* delete a workflow and associated data (by magic)
$table = $obj['__WORKFLOW__']['obj_table'];
$wfid = $obj['__WORKFLOW__']['id'];
* @param string $schemaName
* @param string $state default = 'initial'
* @return mixed array or bool false
function getActionsByState($schemaName, $module= null, $state= 'initial', $obj= array())
$actions = $schema['actions'][$state];
$allowedActions = array();
foreach($actions as $action) {
$allowedActions[$action['id']] = $action['id'];
* get possible actions for a given item of data in it's current workflow state
* @param mixed $idcolumn id field default = 'id'
* @param string $module module name (defaults to current module)
* @return mixed array of actions or bool false
return pn_exit('object is not an array');
return pn_exit('$dbTable not specified');
$workflow = $obj['__WORKFLOW__'];
* Load workflow for object
* will attach array '__PNWORKFLOW__' to the object
* @param string $dbTable name of table where object is or will be stored
* @param string $id name of ID column of object
* @param string $module module name (defaults to current module)
return pn_exit('$dbTable must be specified');
return pn_exit('$obj must be an array.');
// get workflow data from DB
$workflows_column = $pntables['workflows_column'];
$workflow = array('state' => 'initial',
'obj_idcolumn' => $idcolumn,
'obj_id' => $obj[$idcolumn]);
// attach workflow to object
$obj['__WORKFLOW__'] = $workflow;
* get workflow state of object
* @param string $idcolumn name of ID column
* @param string $module module name (defaults to current module)
* @return mixed string workflow state name or false
if (!isset ($obj['__WORKFLOW__'])) {
$workflow = $obj['__WORKFLOW__'];
return $workflow['state'];
* Check permission of action
function permissionCheck($module, $schema, $obj= array(), $permLevel, $actionId= null)
// translate permission to something meaningful
// test conversion worked
// no user then assume anon
if (empty($currentUser)) {
$function = "{ $module}_workflow_{ $schema}_permissioncheck ";
// function already exists
return $function($obj, $permLevel, $currentUser, $actionId);
// test operation file exists
$path = WorkflowUtil::_findpath("function.{$schema}_permissioncheck.php", $module);
return pn_exit("permission check file: function.{$schema}_permissioncheck.php : does not exist");
// load file and test if function exists
return pn_exit("permission check function: $function: not defined");
// function must be loaded so now we can execute the function
return $function($obj, $permLevel, $currentUser);
* translates workflow permission to pn permission define
* @param string $permission
* @return mixed int or false
* @subpackage WorkflowUtil
* Enter description here...
* @return object pnWorkflow
$this->id = $schema['workflow']['id'];
$this->title = $schema['workflow']['title'];
$this->description = $schema['workflow']['description'];
* register workflow by $metaId
* @param object $workflow
* @param string $state default=null;
$workflowData = $obj['__WORKFLOW__'];
$idcolumn = $workflowData['obj_idcolumn'];
$insertObj = array('obj_table' => $workflowData['obj_table'],
'obj_idcolumn' => $workflowData['obj_idcolumn'],
'obj_id' => $obj[$idcolumn],
'schemaname' => $this->id,
$obj['__WORKFLOW__'] = $insertObj;
* execute workflow action
* @param string $actionID
* @return mixed array or false
return pn_exit("STATE: $stateID not found");
// check the action exists for given state
if (!isset ($this->actionMap[$stateID][$actionID])) {
return pn_exit("Action: $actionID not available in this State: $stateID");
$action = $this->actionMap[$stateID][$actionID];
return pn_exit("no permission to execute action: $action[permission]");
// commit workflow to object
$operations = $action['operations'];
$nextState = (isset ($action['nextState']) ? $action['nextState'] : $stateID);
foreach($operations as $operation) {
$result[$operation['name']] = $this->executeOperation($operation, $obj, $nextState);
if (!$result[$operation['name']]) {
// if an operation fails here, do not process further and return false
// test if state needs to be updated
if ($nextState == $stateID) {
// if this is an initial object then we need to register with the DB
if ($stateID == 'initial') {
// change the workflow state
// return result of all operations (possibly okay to just return true here)
* execute workflow operation within action
* @param string $operation
$operationName = $operation['name'];
$operationParams = $operation['parameters'];
// test operation file exists
$path = WorkflowUtil::_findpath("operations/function.{$operationName}.php", $this->module);
return pn_exit("operation file: $operationName: does not exist");
// load file and test if function exists
$function = "{ $this->module}_operation_{ $operationName}";
if (!function_exists($function)) {
return pn_exit("operation function: $function: not defined");
// execute operation and return result
return $function($obj, $operationParams);
* @return string workflow schema name
* get workflow description
* @return string description
function getDescription()
* @return string module name
* Parse workflow schema into associative arrays
* @subpackage WorkflowUtil
* parse workflow into array format
function pnWorkflowParser()
$this->workflow = array('state' => 'initial');
$this->parser = xml_parser_create();
xml_set_object($this->parser, $this);
xml_set_element_handler($this->parser, "startElement", "endElement");
xml_set_character_data_handler($this->parser, "characterData");
* @param string $schemaName
* @param string $schemaPath
* @return mixed associative array of workflow or false
function parse($xmldata, $schemaName, $module)
if (!xml_parse($this->parser, $xmldata, true))
"Unable to parse XML workflow (line "
. xml_get_current_line_number($this->parser) . ","
. xml_get_current_column_number($this->parser) . "): "
. xml_error_string($this->parser));
xml_parser_free($this->parser);
xml_parser_free($this->parser);
if ($this->workflow['state'] == 'error') {
return LogUtil::registerError($this->workflow['errorMessage']);
if (!$this->validate()) {
$this->workflow['workflow']['module'] = $module;
$this->workflow['workflow']['id'] = $schemaName;
* marshall data in to meaningful associative arrays
$states = $this->workflow['states'];
$actions = $this->workflow['actions'];
// create associative arrays maps
foreach($states as $state) {
$stateMap[$state['id']] = array($state['id'], $state['title'], $state['description']);
foreach($actions as $action) {
if (($action['state'] == 'initial') ||
($action['state'] == null) ||
($action['state'] == $state['id'])) {
if ($action['state'] == 'initial' || $action['state'] == null) {
if (($action['state']) == $state['id']) {
// change the case of array keys for parameter variables
$operations = &$action['operations'];
$ak = array_keys($operations);
$parameters = &$operations[$key]['parameters'];
$parameters = array_change_key_case($parameters, CASE_LOWER);
$actionID = $action['id'];
$actionMap[$stateID][$actionID] = $action;
// commit new array to workflow
$this->workflow['actions'] = $actionMap;
$this->workflow['states'] = $stateMap;
* validate workflow actions
$stateMap = $this->workflow['states'];
$states = $this->workflow['actions'];
$ak = array_keys($states);
foreach ($ak as $stateID) {
$actions = $this->workflow['actions'][$stateID];
foreach ($actions as $action) {
$stateName = $action['state'];
if ($stateName != null) {
if (!isset($stateMap[$stateName]))
return LogUtil::registerError("Unknown state name '$stateName' in action '" . $action['title'] ."'");
if (isset($action['nextState'])) {
$nextStateName = $action['nextState'];
if (isset($nextStateName)) {
if (!isset($stateMap[$nextStateName]))
return LogUtil::registerError("Unknown next-state name '$nextStateName' in action '" . $action['title'] ."'");
foreach($action['operations'] as $operation) {
if (isset($operation['parameters']['NEXTSTATE'])) {
$stateName = $operation['parameters']['NEXTSTATE'];
if (!isset($stateMap[$stateName]))
return LogUtil::registerError("Unknown state name '$stateName' in action '" . $action['title'] . "' - operation '$operation[name]'");
* XML start element handler
function startElement($parser, $name, $attribs)
$state = &$this->workflow['state'];
if ($state == 'initial') {
if ($name == 'WORKFLOW') {
$this->workflow['workflow'] = array();
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
if ($state == 'workflow') {
if ($name == 'TITLE' || $name == 'DESCRIPTION') {
$this->workflow['value'] = '';
$this->workflow['states'] = array();
if ($name == 'ACTIONS') {
$this->workflow['actions'] = array();
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
if ($state == 'states') {
$this->workflow['stateValue'] = array('id' => trim($attribs['ID']));
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
if ($name == 'TITLE' || $name == 'DESCRIPTION') {
$this->workflow['value'] = '';
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
if ($state == 'actions') {
$this->workflow['action'] = array('id' => trim($attribs['ID']), 'operations' => array(), 'state' => null);
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
if ($state == 'action') {
if ($name == 'TITLE' || $name == 'DESCRIPTION' || $name == 'PERMISSION' || $name == 'STATE' || $name == 'NEXTSTATE') {
$this->workflow['value'] = '';
if ($name == 'OPERATION') {
$this->workflow['value'] = '';
$this->workflow['operationParameters'] = $attribs;
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
$this->workflow['errorMessage'] = $this->unexpectedXMLError($name, $state. " ".__LINE__ );
$this->workflow['errorMessage'] = _PNWF_STATEERROR . " '$state' " . " '$name'";
* XML end element handler
function endElement($parser, $name)
$state = &$this->workflow['state'];
if ($state == 'workflow') {
$this->workflow['workflow']['title'] = $this->workflow['value'];
if ($name == 'DESCRIPTION') {
$this->workflow['workflow']['description'] = $this->workflow['value'];
$this->workflow['stateValue']['title'] = $this->workflow['value'];
if ($name == 'DESCRIPTION') {
$this->workflow['stateValue']['description'] = $this->workflow['value'];
$this->workflow['states'][] = $this->workflow['stateValue'];
$this->workflow['stateValue'] = null;
if ($state == 'action') {
$this->workflow['action']['title'] = $this->workflow['value'];
if ($name == 'DESCRIPTION') {
$this->workflow['action']['description'] = $this->workflow['value'];
if ($name == 'PERMISSION') {
$this->workflow['action']['permission'] = trim($this->workflow['value']);
$this->workflow['action']['state'] = trim($this->workflow['value']);
if ($name == 'OPERATION') {
$this->workflow['action']['operations'][] = array('name' => trim($this->workflow['value']),
'parameters' => $this->workflow['operationParameters']);
$this->workflow['operation'] = null;
if ($name == 'NEXTSTATE') {
$this->workflow['action']['nextState'] = trim($this->workflow['value']);
$this->workflow['actions'][] = $this->workflow['action'];
$this->workflow['action'] = null;
if ($state == 'actions') {
if ($name == 'ACTIONS') {
if ($state == 'states') {
* XML data element handler
* @param object $parser parser object
function characterData($parser, $data)
$value = &$this->workflow['value'];
* hander for unexpected XML errors
function unexpectedXMLError($name, $state)
return "Unexpected $name tag in $state state";
// Declare object variables;
|