Wiki : Workflow
Documentation Home :: Categories :: Index :: Recent Changes :: Comments :: Search :: Help :: Login/RegisterWorkflow System
The PostNuke Application Framework has a state-based workflow engine with internal object mapping. This allows developers to write workflow based applications or functions with relative ease. This guide will explain the structure and API commands you need to use. We also have an example module called HowtoWorkflow which demonstrates implementation.
Structure
To make use of the workflow system we simply need to add an extra directory tree to our module folder with a few configuration files. Here is an example:- pnworkflows (contains all your workflow related components).
- standard.xml
- function.standard_permissioncheck.php
- operations (contains the workflow operations functions, one per file).
- createNews.php
- updateNews.php
- deleteNews.php
States and Actions
The workflow system is based around the concept that any given workflow for a task will have a number of predefined states, and in each state, certain action can be performed. This goes for simple news articles to more complex processes like invoicing and product delivery. Whatever the task, you must plan your workflow carefully. Once you know the states, and the actions that will be possible in each state, you can create a simple workflow XML file.
XML Structure
At the heart of the workflow is the XML file, we have a few simple tags to define your workflow. The entire workflow should be enclosed in <workflow> tags with <title> and <description> as shown below.
<workflow>
<title>Sample</title>
<description>Short description of the sample worklfow</description>
</workflow>
<title>Sample</title>
<description>Short description of the sample worklfow</description>
</workflow>
States
Within the workflow tags we can now define the possible states, enclosing all definitions in <states> tags like this:
<states>
<state id="initial">
<title>Initial</title>
<description>Initial State</description>
</state>
<state id="waiting">
<title>Waiting</title>
<description>Content has been submitted and is waiting for acceptance</description>
</state>
<state id="approved">
<title>Approved</title>
<description>Content has been approved is available online</description>
</state>
</states>
<state id="initial">
<title>Initial</title>
<description>Initial State</description>
</state>
<state id="waiting">
<title>Waiting</title>
<description>Content has been submitted and is waiting for acceptance</description>
</state>
<state id="approved">
<title>Approved</title>
<description>Content has been approved is available online</description>
</state>
</states>
As you can see, each state is defined in between a <state> enclosure with a title and description. Easy as pie.
Actions
Next comes the actions. This is where the magic is. Here we define all the actions, the state they can be performed in, and the next state the workflow will move to on completion of the action. All <action> definitions are enclosed by one <actions> tag.
In short an <action> tag should enclose the following information
- <title> title
- <description> description
- <state> the state the action can be performed in
- <permission> the permission level required
- <operation> the operation(s) to perform
- <nextState> the state to change to after the action has been performed
We will explain the actions after showing an example:
<actions>
<!-- begin actions for initial state -->
<action id="submit">
<title>Submit</title>
<description>Submit new content for acceptance by the local moderator</description>
<permission>add</permission>
<state>initial</state>
<nextState>waiting</nextState>
<operation online='0'>createNews</operation>
</action>
<action id="approve">
<title>Approve</title>
<description>Approve publication for immediate publishing</description>
<permission>comment</permission>
<state>waiting</state>
<nextState>approved</nextState>
<operation online='1'>updateNews</operation>
</action>
</actions>
<!-- begin actions for initial state -->
<action id="submit">
<title>Submit</title>
<description>Submit new content for acceptance by the local moderator</description>
<permission>add</permission>
<state>initial</state>
<nextState>waiting</nextState>
<operation online='0'>createNews</operation>
</action>
<action id="approve">
<title>Approve</title>
<description>Approve publication for immediate publishing</description>
<permission>comment</permission>
<state>waiting</state>
<nextState>approved</nextState>
<operation online='1'>updateNews</operation>
</action>
</actions>
Operations
Operations are one or more functions to be called when the action containing them is executed. The following example says, run the createNews() function.
<operation>createNews</operation>
The <operations> tag can also take multiple attributes which are parsed to the function as an associative array of values. The following example would parse ('state' => '1') to the function updateStatus()
<operation state='1'>updateStatus</operation>
You may also have multiple operations per action. They are performed in the order specified.
<operation>createNews</operation>
<operation group='admin'>notify</operation>
<operation sugar='0' milk='1'>makeTea</operation>
<operation group='admin'>notify</operation>
<operation sugar='0' milk='1'>makeTea</operation>
Each operation requires the corresponding operation file located in the operations folder. The idea is similar to pnRender plugins. In order to allow them to be used by multiple schemas however, the naming schema is as follows.
- File name: function.{$operation}.php
- Function name: {$modulename}_operation_{$operation}
The function will receive the object and any attributes parsed in the <operation> tag as an associative array of values. See below:
<?php
/**
* example workflow createNews (insert news item to DB)
*
* @param array $obj
* @param array, $params
*
* @return bool
*/
function howtoworkflow_operation_createNews(&$obj, $params)
{
// this function can be parsed an array of arguements in $param
// in this case we expect the array key 'online'
$online = isset($params['online']) ? $params['online'] : false;
$obj['online'] = $online;
return (bool)DBUtil::insertObject($obj, 'htwf_testdata', false, 'id');
}
?>
/**
* example workflow createNews (insert news item to DB)
*
* @param array $obj
* @param array, $params
*
* @return bool
*/
function howtoworkflow_operation_createNews(&$obj, $params)
{
// this function can be parsed an array of arguements in $param
// in this case we expect the array key 'online'
$online = isset($params['online']) ? $params['online'] : false;
$obj['online'] = $online;
return (bool)DBUtil::insertObject($obj, 'htwf_testdata', false, 'id');
}
?>
Operation function can just as easily be wrappers to module or module-API functions. By using operations we allow the workflow engine to do all the processing. In the end we just send in an object and magic happens - importantly this allows us to batch operations independenlty of the module. This is ideal for many application like billing, automated provision of services (and their suspension linked to billing) for example.
Permissions
The permission system used by the workflow uses roughly the same permission naming system as PostNuke: overview, moderate, read, add, comment, edit, delete, admin
The reason for this is the permission tests are performed by SecurityUtil::checkPermission() The workflow engine will map the workflow permission names to the PostNuke versions.
Each workflow schema requires it's own permission checking function located in a separate file.
- File name: function.{$schemaname}_permissioncheck.php
- Function name: ($modulename}_workflow_{$schemaname)_permissioncheck()
<?php
/**
* @param array $obj
* @param int $permLevel
* @param int $currentUser
* @return bool
*/
function howtoworkflow_workflow_standard_permissioncheck($obj, $permLevel, $currentUser)
{
// process $obj and calculate an instance
$instance = '::';
return SecurityUtil::checkPermission('HowtoWorkflow::', $instance, $permLevel, $currentUser);
}
?>
/**
* @param array $obj
* @param int $permLevel
* @param int $currentUser
* @return bool
*/
function howtoworkflow_workflow_standard_permissioncheck($obj, $permLevel, $currentUser)
{
// process $obj and calculate an instance
$instance = '::';
return SecurityUtil::checkPermission('HowtoWorkflow::', $instance, $permLevel, $currentUser);
}
?>
The permission check file is designed to allow you to potentially calculate complex permission checks because you receive the object the workflow engine is being asked to process and the permission level the action requires. The example above doesnt show such complexity in action, but the option is there.
Fitting it all together
The workflow system in intelligent in a few ways. Firstly, it knows which item of data belongs to which workflow. There is no need for you to track this in your own code. Secondly when using workflow, we simply parse a data object to the workflow system and it will automatically register items that need a workflow and dont have to worry about that. Thirly, the workflow can process data in batches - it knows how to process everything from multiple modules.
All a module developer needs to know about are a few functions grouped under the WorkflowUtil:: static class. New and existing workflows are processed through a single call, WorkflowUtil::executeAction()
executeAction()
Workflow::executeAction takes a few arguements as follows:
- name of XML worklow schema
- data object as an array
- name of the action to perform
- name of the table the data is or will be stored in (pntables key)
- name of the module the workflow belongs to (defaults to calling module)
- the id field name of the object
- Returns mixed or boolean
To create a new workflow for an item, or to put it another way, to attach a workflow to a data object just push the object with an action with an initial state. Because the action has an initial state, the workflow will register the workflow. In this example our submit action has one operation that inserts the object into the database. After the operation has been completed a workflow is registered and mapped to the database entry (automatically).
WorkflowUtil::executeAction('standard', $obj, 'submit', 'htwf_testdata');
To process an existing item in the workflow, just push the object into WorkflowUtil::executeAction() with the action to perform and the workflow will get on with the job, e.g.
WorkflowUtil::executeAction('standard', $obj, 'reject', 'htwf_testdata');
getActionsForObject()
Workflow::getActionsForObject()
This function takes the following arguements and will return all the possible actions for a given object in it's current state or bool false.
- object as an array
- name of database table object is stored in
- name of the id column in the object
- name of the module the item belongs to
- returns mixed array or bool
$actions = WorkflowUtil::getActionsForObject($obj, 'htwf_testdata');
getActionsByState()
Workflow::getActionsByState()
This function will return all the valid actions for a given state and filter according to the permissions specified in the workflow's XML schema.
- name of schema
- module (default: current module)
- state (default: initial)
- object
- return mixed or bool.
$actions = WorkflowUtil::getActionsByState('standard');
Example
In the following example extracted from the HowtoWorkflow module's pnuser.php you can see that we can use the same functions to create a new object or edit an existing one.
<?php
function howtoworkflow_user_edit()
{
// retreive $id from GET request.
$id = FormUtil::getPassedValue('id', null, 'GET');
// start pnRender instance
$pnRender = new pnRender('HowtoWorkflow', false);
if(!isset($id)) {
// new item
$obj = array();
// assign blank array so we can display empty form
$pnRender->assign('obj', $obj);
// get actions for workflow (will default to initial state)
$actions = WorkflowUtil::getActionsByState('standard');
// assign these action to pnRender
$pnRender->assign('actions', $actions);
// process and display template
return $pnRender->fetch('howtoworkflow_user_edit.htm');
}
// get item from the database
$obj = DBUtil::selectObjectByID('htwf_testdata', $id);
// get the possible actions for this object in whatever workflow state it's in
$actions = WorkflowUtil::getActionsForObject($obj, 'htwf_testdata');
// assign the action array to pnRender so template can process it
$pnRender->assign('actions', $actions);
// assign the variables received from input form to pnRender for display
$pnRender->assign('obj', $obj);
// process and display template
return $pnRender->fetch('howtoworkflow_user_edit.htm');
}
/**
* process form edit
*
* @return array
*/
function howtoworkflow_user_editaction()
{
// get form input from POST array
$obj = FormUtil::getPassedValue('obj', null, 'POST');
// get actions from form input from POST array
$action = FormUtil::getPassedValue('action', null, 'POST');
// execute the workflow for this object
WorkflowUtil::executeAction('standard', $obj, $action, 'htwf_testdata');
// redirect to view function which will display updated results if any
return pnRedirect(pnModURL('HowtoWorkflow','user','view'));
}
?>
function howtoworkflow_user_edit()
{
// retreive $id from GET request.
$id = FormUtil::getPassedValue('id', null, 'GET');
// start pnRender instance
$pnRender = new pnRender('HowtoWorkflow', false);
if(!isset($id)) {
// new item
$obj = array();
// assign blank array so we can display empty form
$pnRender->assign('obj', $obj);
// get actions for workflow (will default to initial state)
$actions = WorkflowUtil::getActionsByState('standard');
// assign these action to pnRender
$pnRender->assign('actions', $actions);
// process and display template
return $pnRender->fetch('howtoworkflow_user_edit.htm');
}
// get item from the database
$obj = DBUtil::selectObjectByID('htwf_testdata', $id);
// get the possible actions for this object in whatever workflow state it's in
$actions = WorkflowUtil::getActionsForObject($obj, 'htwf_testdata');
// assign the action array to pnRender so template can process it
$pnRender->assign('actions', $actions);
// assign the variables received from input form to pnRender for display
$pnRender->assign('obj', $obj);
// process and display template
return $pnRender->fetch('howtoworkflow_user_edit.htm');
}
/**
* process form edit
*
* @return array
*/
function howtoworkflow_user_editaction()
{
// get form input from POST array
$obj = FormUtil::getPassedValue('obj', null, 'POST');
// get actions from form input from POST array
$action = FormUtil::getPassedValue('action', null, 'POST');
// execute the workflow for this object
WorkflowUtil::executeAction('standard', $obj, $action, 'htwf_testdata');
// redirect to view function which will display updated results if any
return pnRedirect(pnModURL('HowtoWorkflow','user','view'));
}
?>
Download Example
To download the latest example, visit http://www.dordrak.com/downloads/HowtoWorkflow.zip∞
Please note that this documentation is now out of sync with PostNuke 0.8.0.0-MS1 because there have been some changes to the manual overrides system which is now 0.8 complaint. The HowtoWorkflow? module above works with 0.8.0.0-MS1. For the current version that is in sync with these documents, please refer to the HowtoModule? in the trunk/ValueAddons folder in SVN.
CategoryDeveloperDocs
