Array(), 'nextActivity' => Array(), 'nextUser' => Array());
/**
* @var array $properties Instance properties
* @access protected
*/
var $properties = Array();
/**
* @var array $cleared Used to detect conflicts on sync with the database
* @access protected
*/
var $cleared = Array();
/**
* @var string $owner Instance owner
* @access protected
*/
var $owner = '';
/**
* @var string $status Instance status
* @access protected
*/
var $status = '';
/**
* @var bool $started
* @access protected
*/
var $started;
/**
* @var array $nextActivity
* @access protected
*/
var $nextActivity=Array();
/**
* @var string $nextUser
* @access protected
*/
var $nextUser;
/**
* @var bool $ended
* @access protected
*/
var $ended;
/**
* @var string $name Instance name
* @access protected
*/
var $name='';
/**
* @var string $category Instance category
* @access protected
*/
var $category;
/**
* @var int $prioryty Instance priority
* @access protected
*/
var $priority = 1;
/**
* @var bool $isChildInstance Indicates wether the current instance is a child instance or not
* @access public
*/
var $isChildInstance = false;
/**
* @var bool $parentLock Indicates wether the parent instance depends on the child instance or not
* @access public
*/
var $parentLock = false;
/**
* @var int $parentActivityId The activity ID of the parent instance
* @access public
*/
var $parentActivityId;
/**
* @var int $parentInstanceId The instance ID of the parent instance
* @access public
*/
var $parentInstanceId;
/**
* @var array $activities Array of assocs(activityId, status, started, ended, user, name, interactivity, autorouting)
* @access protected
*/
var $activities = Array();
/**
* @var int $pIdProcess id
* @access protected
*/
var $pId;
/**
* @var int $instanceId Instance id
* @access protected
*/
var $instanceId = 0;
/**
* @var array $workitems An array of workitem ids, date, duration, activity name, user, activity type and interactivity
* @access protected
*/
var $workitems = Array();
/**
* @var object $security Performs some tests and locks
* @access protected
*/
var $security;
/**
* @var bool $__activity_completed Internal reminder
* @access protected
*/
var $__activity_completed=false;
/**
* @var bool $unsynch indicator, if true we are not synchronised in the memory object with the database
* @see sync()
* @access protected
*/
var $unsynch=false;
/**
* Constructor
*
* @param object $db ADOdb object
* @access public
*/
var $activityID = null;
function Instance()
{
$this->child_name = 'Instance';
parent::Base();
}
/**
* Method used to load an instance data from the database.
* This function will load/initialize members of the instance object from the database
* it will populatae all members and will by default populate the related activities array
* and the workitems (history) array.
*
* @param int $instanceId
* @param bool $load_activities true by default, do we need to reload activities from the database?
* @param bool $load_workitems true by default, do we need to reload workitems from the database?
* @return bool
* @access protected
*/
function getInstance($instanceId, $load_activities=true, $load_workitems=true)
{
if (!($instanceId)) return true; //start activities for example - pseudo instances
// Get the instance data
$query = "select * from `".GALAXIA_TABLE_PREFIX."instances` where `wf_instance_id`=?";
$result = $this->query($query,array((int)$instanceId));
if( empty($result) || (!$result->numRows())) return false;
$res = $result->fetchRow();
//Populate
$this->properties = unserialize(base64_decode($res['wf_properties']));
$this->status = $res['wf_status'];
$this->pId = $res['wf_p_id'];
$this->instanceId = $res['wf_instance_id'];
$this->priority = $res['wf_priority'];
$this->owner = $res['wf_owner'];
$this->started = $res['wf_started'];
$this->ended = $res['wf_ended'];
$this->nextActivity = unserialize(base64_decode($res['wf_next_activity']));
$this->nextUser = unserialize(base64_decode($res['wf_next_user']));
$this->name = $res['wf_name'];
$this->category = $res['wf_category'];
// Get the activities where the instance is (nothing for start activities)
if ($load_activities)
{
$this->_populate_activities($instanceId);
}
// Get the workitems where the instance is
if ($load_workitems)
{
$query = "select wf_item_id, wf_order_id, gw.wf_instance_id, gw.wf_activity_id, wf_started, wf_ended, gw.wf_user,
ga.wf_name, ga.wf_type, ga.wf_is_interactive
from ".GALAXIA_TABLE_PREFIX."workitems gw
INNER JOIN ".GALAXIA_TABLE_PREFIX."activities ga ON ga.wf_activity_id = gw.wf_activity_id
where wf_instance_id=? order by wf_order_id ASC";
$result = $this->query($query,array((int)$instanceId));
if (!(empty($result)))
{
while($res = $result->fetchRow())
{
$this->workitems[]=$res;
}
}
return true;
}
}
/**
* Loads all activities related to the insance given in parameter in the activities array
*
* @access private
* @param int $instanceId
*/
function _populate_activities($instanceId)
{
$this->activities=Array();
$query = "select gia.wf_activity_id, gia.wf_instance_id, wf_started, wf_ended, wf_started, wf_user, wf_status,
ga.wf_is_autorouted, ga.wf_is_interactive, ga.wf_name, ga.wf_type
from ".GALAXIA_TABLE_PREFIX."instance_activities gia
INNER JOIN ".GALAXIA_TABLE_PREFIX."activities ga ON ga.wf_activity_id = gia.wf_activity_id
where wf_instance_id=?";
$result = $this->query($query,array((int)$instanceId));
if (!(empty($result)))
{
while($res = $result->fetchRow())
{
$this->activities[] = $res;
}
}
}
/**
* Performs synchronization on an instance member
*
* @param bool $changed
* @param array $init
* @param array $actual
* @param string $name
* @param string $fieldname
* @param array $namearray
* @param array $vararray
* @return void
* @access private
*/
function _synchronize_member(&$changed,&$init,&$actual,$name,$fieldname,&$namearray,&$vararray)
{
//if we work with arrays then it's more complex
//echo "
$name is_array?".(is_array($changed)); _debug_array($changed);
if (!(is_array($changed)))
{
if (isset($changed))
{
//detect unsynchro
if (!($actual==$init))
{
$this->error[] = tra('Instance: unable to modify %1, someone has changed it before us', $name);
}
else
{
$namearray[] = $fieldname;
$vararray[] = $changed;
$actual = $changed;
}
unset ($changed);
}
}
else //we are working with arrays (properties for example)
{
$modif_done = false;
foreach ($changed as $key => $value)
{
//detect unsynchro
if (!($actual[$key]==$init[$key]))
{
$this->error[] = tra('Instance: unable to modify %1 [%2], someone has changed it before us', $name, $key);
}
else
{
$actual[$key] = $value;
$modif_done = true;
}
}
if ($modif_done) //at least one modif
{
$namearray[] = $fieldname;
//no more serialize, done by the core security_cleanup
$vararray[] = $actual; //serialize($actual);
}
$changed=Array();
}
}
/**
* Synchronize thes instance object with the database. All change smade will be recorded except
* conflicting ones (changes made on members or properties that has been changed by another source
* --could be another 'instance' of this instance or an admin form-- since the last call of sync() )
* the unsynch private member is used to test if more heavy tests should be done or not
* pseudo instances (start, standalone) are not synchronised since there is no record on database
*
* @access public
* @return bool
*/
function sync()
{
if ( (!($this->instanceId)) || (!($this->unsynch)) )
{
//echo "
nothing to do ".$this->unsynch;
return true;
}
//echo "
synch!";_debug_array($this->changed);
//do it in a transaction, can have several activities running
$this->db->StartTrans();
//we need to make a row lock now,
$where = 'wf_instance_id='.(int)$this->instanceId;
if (!($this->db->RowLock(GALAXIA_TABLE_PREFIX.'instances', $where)))
{
$this->error[] = 'sync: '.tra('failed to obtain lock on %1 table', 'instances');
$this->db->FailTrans();
}
else
{
//wf_p_id and wf_instance_id are set in creation only.
//we remember initial values
$init_properties = $this->properties;
$init_status = $this->status;
$init_priority = $this->priority;
$init_owner = $this->owner;
$init_started = $this->started;
$init_ended = $this->ended;
$init_nextUser = $this->nextUser;
$init_nextActivity = $this->nextActivity;
$init_name = $this->name;
$init_category = $this->category;
// we re-read instance members to detect conflicts, changes made while we were unsynchronised
$this->getInstance($this->instance_id, false, false);
// Now for each modified field we'll change the database vale if nobody has changed
// the database value before us
// (Drovetto) Inclusion of the nextUser field in the synchronization
$bindvars = Array();
$querysets = Array();
$queryset = '';
$this->_synchronize_member($this->changed['status'],$init_status,$this->status,tra('status'),'wf_status',$querysets,$bindvars);
$this->_synchronize_member($this->changed['priority'],$init_priority,$this->priority,tra('priority'),'wf_priority',$querysets,$bindvars);
$this->_synchronize_member($this->changed['owner'],$init_owner,$this->owner,tra('owner'),'wf_owner',$querysets,$bindvars);
$this->_synchronize_member($this->changed['started'],$init_started,$this->started,tra('started'),'wf_started',$querysets,$bindvars);
$this->_synchronize_member($this->changed['ended'],$init_ended,$this->ended,tra('ended'),'wf_ended',$querysets,$bindvars);
$this->_synchronize_member($this->changed['name'],$init_name,$this->name,tra('name'),'wf_name',$querysets,$bindvars);
$this->_synchronize_member($this->changed['category'],$init_category,$this->category,tra('category'),'wf_category',$querysets,$bindvars);
$this->_synchronize_member($this->changed['properties'],$init_properties,$this->properties,tra('property'),'wf_properties',$querysets,$bindvars);
$this->_synchronize_member($this->changed['nextUser'],$init_nextUser,$this->nextUser,tra('next user'),'wf_next_user',$querysets,$bindvars);
$this->_synchronize_member($this->changed['nextActivity'],$init_nextActivity,$this->nextActivity,tra('next activity'),'wf_next_activity',$querysets,$bindvars);
/* remove unsetted properties */
if (($propertiesIndex = array_search("wf_properties", $querysets)) !== false)
foreach ($this->cleared as $clearedName)
unset($bindvars[$propertiesIndex][$clearedName]);
/* remove unsetted nextUser */
if (($nextUserIndex = array_search("wf_next_user", $querysets)) !== false)
foreach ($bindvars[$nextUserIndex] as $nextUserKey => $nextUserValue)
if ($bindvars[$nextUserIndex][$nextUserKey] == '__UNDEF__')
{
unset($bindvars[$nextUserIndex][$nextUserKey]);
unset($this->nextUser[$nextUserKey]);
}
if (!(empty($querysets)))
{
$queryset = implode(' = ?,', $querysets). ' = ?';
$query = 'update '.GALAXIA_TABLE_PREFIX.'instances set '.$queryset
.' where wf_instance_id=?';
$bindvars[] = $this->instanceId;
//echo "
query $query"; _debug_array($bindvars);
$this->query($query,$bindvars);
}
}
if (!($this->db->CompleteTrans()))
{
$this->error[] = tra('failed to synchronize instance data with the database');
return false;
}
//we are not unsynchronized anymore.
$this->unsynch = false;
return true;
}
/**
* Sets the next activity to be executed, if the current activity is
* a switch activity the complete() method will use the activity setted
* in this method as the next activity for the instance.
* The object records an array of transitions, as the instance can be splitted in several
* running activities, transition from the current activity to the given activity will
* be recorded and all previous recorded transitions starting from the current activity
* will be deleted
*
* @param int $activityId Running activity Id
* @param string $actname Activity name as argument (not an id)
* @return bool
* @access public
*/
function setNextActivity($activityId, $actname)
{
$pId = $this->pId;
$actname=trim($actname);
$aid = $this->getOne('select wf_activity_id from '.GALAXIA_TABLE_PREFIX.'activities where wf_p_id=? and wf_name=?',array($pId,$actname));
if (!($aid))
{
$this->error[] = tra('setting next activity to an unexisting activity');
return false;
}
$this->changed['nextActivity'][$activityId]=$aid;
$this->unsynch = true;
return true;
}
/**
* This method can be used to set the user that must perform the next
* activity of the process. this effectively "assigns" the instance to
* some user
*
* @param mixed $user The user which will execute the next activity
* @param mixed $activityID The ID of the activity that the user will be able to execute. '*' means any next activity
* @return bool true in case of success or false otherwise
* @access public
*/
function setNextUser($user, $activityID = '*')
{
if ($activityID == '*')
{
$candidates = $this->getActivityCandidates($this->activityID);
foreach ($candidates as $candidate)
$this->changed['nextUser'][$candidate] = $user;
}
else
$this->changed['nextUser'][$activityID] = $user;
$this->unsynch = true;
return true;
}
/**
* Sets next instance user role
*
* @param string $roleName The name of the role
* @param mixed $activityID The ID of the activity that the role will be able to execute. '*' means any next activity
* @return bool true in case of success or false otherwise
* @access public
*/
function setNextRole($roleID, $activityID = '*')
{
return $this->setNextUser('p' . $roleID, $activityID);
}
/**
* Removes the user/role of the provided activity ID
*
* @param mixed $activityID The ID of the activity from which the users/roles will be removed.
* @return void
* @access public
*/
function unsetNextUser($activityID = '*')
{
$this->setNextUser('__UNDEF__', $activityID);
}
/**
* This method can be used to get the user that must perform the next
* activity of the process. This can be empty if no setNextUser was done before.
* It wont return the default user but inly the user which was assigned by a setNextUser
*
* @param mixed $activityID The ID of the activity from which we want the user/role that will execute it.
* @return string
* @access public
*/
function getNextUser($activityID = '*')
{
if ($activityID == '*')
$order = array('*' . $this->activityID);
else
$order = array($activityID, '*' . $this->activityID);
foreach ($order as $currentOrder)
{
if (isset($this->changed['nextUser'][$currentOrder]) && ($this->changed['nextUser'][$currentOrder] == '__UNDEF__'))
continue;
if (isset($this->changed['nextUser'][$currentOrder]))
return $this->changed['nextUser'][$currentOrder];
if (isset($this->nextUser[$currentOrder]))
return $this->nextUser[$currentOrder];
}
return '';
}
/**
* Creates a new instance.
* This method is called in start activities when the activity is completed
* to create a new instance representing the started process.
*
* @param int $activityId start activity id
* @param int $user current user id
* @return bool
* @access private
*/
function _createNewInstance($activityId,$user) {
// Creates a new instance setting up started, ended, user, status and owner
$pid = $this->getOne('select wf_p_id from '.GALAXIA_TABLE_PREFIX.'activities where wf_activity_id=?',array((int)$activityId));
$this->pId = $pid;
$this->setStatus('active');
//$this->setNextUser('');
$now = date("U");
$this->setStarted($now);
$this->setOwner($user);
$query = 'insert into '.GALAXIA_TABLE_PREFIX.'instances
(wf_started,wf_ended,wf_status,wf_p_id,wf_owner,wf_properties)
values(?,?,?,?,?,?)';
$this->query($query,array($now,0,'active',$pid,$user,$this->security_cleanup(Array(),false)));
$this->instanceId = $this->getOne('select max(wf_instance_id) from '.GALAXIA_TABLE_PREFIX.'instances
where wf_started=? and wf_owner=?',array((int)$now,$user));
$iid=$this->instanceId;
// Then add in ".GALAXIA_TABLE_PREFIX."instance_activities an entry for the
// activity the user and status running and started now
$query = 'insert into '.GALAXIA_TABLE_PREFIX.'instance_activities (wf_instance_id,wf_activity_id,wf_user,
wf_started,wf_status) values(?,?,?,?,?)';
$this->query($query,array((int)$iid,(int)$activityId,$user,(int)$now,'running'));
if (($this->isChildInstance) && (!is_null($this->parentInstanceId)))
{
$query = 'INSERT INTO '.GALAXIA_TABLE_PREFIX.'interinstance_relations(wf_parent_instance_id, wf_parent_activity_id, wf_child_instance_id, wf_parent_lock) VALUES(?,?,?,?)';
$this->query($query,array((int) $this->parentInstanceId, (int) $this->parentActivityId, (int) $iid, (int) (($this->parentLock) ? 1 : 0)));
}
//update database with other datas stored in the object
return $this->sync();
}
/**
* Sets the name of this instance
*
* @param string $value
* @return bool
* @access public
*/
function setName($value)
{
$this->changed['name'] = substr($value,0,120);
$this->unsynch = true;
return true;
}
/**
* Get the name of this instance
*
* @return string
* @access public
*/
function getName()
{
if (!(isset($this->changed['name'])))
{
return $this->name;
}
else
{
return $this->changed['name'];
}
}
/**
* Sets the category of this instance
*
* @param string $value
* @return bool
* @access public
*/
function setCategory($value)
{
$this->changed['category'] = $value;
$this->unsynch = true;
return true;
}
/**
* Get the category of this instance
*
* @return string
* @access public
*/
function getCategory()
{
if (!(isset($this->changed['category'])))
{
return $this->category;
}
else
{
return $this->changed['category'];
}
}
/**
* Normalizes a property name
*
* @param string $name name you want to normalize
* @return string property name
* @access private
*/
function _normalize_name($name)
{
$name = trim($name);
$name = str_replace(" ","_",$name);
$name = preg_replace("/[^0-9A-Za-z\_]/",'',$name);
return $name;
}
/**
* Sets a property in this instance. This method is used in activities to
* set instance properties.
* all property names are normalized for security reasons and to avoid localisation
* problems (A->z, digits and _ for spaces). If you have several set to call look
* at the setProperties function. Each call to this function has an impact on database
*
* @param string $name property name (it will be normalized)
* @param mixed $value value you want for this property
* @return bool
* @access public
*/
function set($name,$value)
{
$name = $this->_normalize_name($name);
unset($this->cleared[$name]);
$this->changed['properties'][$name] = $this->security_cleanup($value);
if (is_array($value))
$this->changed['properties'][$name] = "__ARRAY__" . $this->changed['properties'][$name];
$this->unsynch = true;
return true;
}
/**
* Unsets a property in this instance. This method is used in activities to
* unset instance properties.
* All property names are normalized for security reasons and to avoid localisation
* problems (A->z, digits and _ for spaces). Each call to this function has an impact on database
*
* @param string $name the property name (it will be normalized)
* @return bool
* @access public
*/
function clear($name)
{
$this->set($name, '');
$this->cleared[$name] = $name;
$this->unsynch = true;
return true;
}
/**
* Checks if a property in this instance exists. This method is used in activities to
* check the existance of instance properties.
* All property names are normalized for security reasons and to avoid localisation
* problems (A->z, digits and _ for spaces).
*
* @param string $name property name (it will be normalized)
* @return bool
* @access public
*/
function exists($name)
{
$name = $this->_normalize_name($name);
return ((isset($this->changed['properties'][$name]) || isset($this->properties[$name])) && !isset($this->cleared[$name]));
}
/**
* Sets several properties in this instance. This method is used in activities to
* set instance properties. Use this method if you have several properties to set
* as it will avoid to re-call the SQL engine for each property.
* all property names are normalized for security reasons and to avoid localisation
* problems (A->z, digits and _ for spaces).
*
* @param array $properties_array associative array containing for each record the
* property name as the key and the property value as the value
* @return bool
* @access public
*/
function setProperties($properties_array)
{
$backup_values = $this->properties;
foreach ($properties_array as $key => $value)
{
$name = $this->_normalize_name($key);
$this->changed['properties'][$name] = $this->security_cleanup($value);
}
$this->unsynch = true;
return true;
}
/**
* Gets the value of an instance property
*
* @param string $name name of the property
* @param mixed $defaultValue
* @return bool
* @access public
*/
function get($name, $defaultValue = "__UNDEF__")
{
$name = $this->_normalize_name($name);
$output = "";
/* select the value of the current property */
if (isset($this->changed['properties'][$name]))
{
$output = $this->changed['properties'][$name];
}
else
{
if (isset($this->properties[$name]))
{
$output = $this->properties[$name];
}
}
/* if the property doesn't exist return the default value or throw an error */
if (isset($this->cleared[$name]) || ((!isset($this->properties[$name])) && (!isset($this->changed['properties'][$name]))))
{
if ($defaultValue != "__UNDEF__")
{
$output = $defaultValue;
}
else
{
$this->error[] = tra('property %1 not found', $name);
$output = false;
}
}
/* if the requested value is an enconded/serialized array, change it back to its original type */
if (@strcmp(@substr($output, 0, 9), "__ARRAY__") == 0)
if ( ($tmp = base64_decode(substr($output, 9))) !== false )
if ( ($tmp = unserialize($tmp)) !== false )
$output = $tmp;
return $output;
}
/**
* Returns an array of assocs describing the activities where the instance
* is present, can be more than one activity if the instance was "splitted"
*
* @return array
* @access public
*/
function getActivities() {
return $this->activities;
}
/**
* Gets the instance status can be
* 'completed', 'active', 'aborted' or 'exception'
*
* @return string
* @access public
*/
function getStatus() {
if (!(isset($this->changed['status'])))
{
return $this->status;
}
else
{
return $this->changed['status'];
}
}
/**
* Sets the instance status
*
* @param string_type $status it can be: 'completed', 'active', 'aborted' or 'exception'
* @return bool
* @access public
*/
function setStatus($status)
{
if (!(($status=='completed') || ($status=='active') || ($status=='aborted') || ($status=='exception')))
{
$this->error[] = tra('unknown status');
return false;
}
$this->changed['status'] = $status;
$this->unsynch = true;
return true;
}
/**
* Gets the instance priority
*
* @return int
* @access public
*/
function getPriority()
{
if (!(isset($this->changed['priority'])))
{
return $this->priority;
}
else
{
return $this->changed['priority'];
}
}
/**
* Sets the instance priority
*
* @param int $priority
* @return bool
* @access public
*/
function setPriority($priority)
{
$mypriority = (int)$priority;
$this->changed['priority'] = $mypriority;
$this->unsynch = true;
return true;
}
/**
* Returns the instanceId
*
* @return int
* @access public
*/
function getInstanceId()
{
return $this->instanceId;
}
/**
* Returns the processId for this instance
*
* @return int
* @access public
*/
function getProcessId()
{
return $this->pId;
}
/**
* Returns the user that created the instance
*
* @return int
* @access public
*/
function getOwner()
{
if (!(isset($this->changed['owner'])))
{
return $this->owner;
}
else
{
return $this->changed['owner'];
}
}
/**
* Sets the instance creator user
*
* @param int $user new owner id, musn't be false, 0 or empty
* @return bool
* @access public
*/
function setOwner($user)
{
if (empty($user))
{
return false;
}
$this->changed['owner'] = $user;
$this->unsynch = true;
return true;
}
/**
* Sets the user that must execute the activity indicated by the activityId.
* Note that the instance MUST be present in the activity to set the user,
* you can't program who will execute an activity.
* If the user is empty then the activity user is setted to *, allowing any
* authorised user to take the token later concurrent access to this function
* is normally handled by WfRuntime and WfSecurity theses objects are the only ones
* which should call this function. WfRuntime is handling the current transaction and
* WfSecurity is Locking the instance and instance_activities table on a 'run' action
* which is the action leading to this setActivityUser call (could be a release
* as well on auto-release)
*
* @param int $activityId
* @param int $theuser user id or '*' (or 0, '' or null which will be set to '*')
* @return bool
* @access public
*/
function setActivityUser($activityId,$theuser) {
if(empty($theuser)) $theuser='*';
$found = false;
for($i=0;$iactivities);$i++) {
if($this->activities[$i]['wf_activity_id']==$activityId) {
// here we are in the good activity
$found = true;
// prepare queries
$where = ' where wf_activity_id=? and wf_instance_id=?';
$bindvars = array((int)$activityId,(int)$this->instanceId);
if(!($theuser=='*'))
{
$where .= ' and (wf_user=? or wf_user=? or wf_user LIKE ?)';
$bindvars[]= $theuser;
$bindvars[]= '*';
$bindvars[]= 'p%';
}
// update the user
$query = 'update '.GALAXIA_TABLE_PREFIX.'instance_activities set wf_user=?';
$query .= $where;
$bindvars_update = array_merge(array($theuser),$bindvars);
$this->query($query,$bindvars_update);
$this->activities[$i]['wf_user']=$theuser;
return true;
}
}
// if we didn't find the activity it will be false
return $found;
}
/**
* Returns the user that must execute or is already executing an activity
* wherethis instance is present
*
* @param int $activityId
* @return bool
* @access public
*/
function getActivityUser($activityId) {
for($i=0;$iactivities);$i++) {
if($this->activities[$i]['wf_activity_id']==$activityId) {
return $this->activities[$i]['wf_user'];
}
}
return false;
}
/**
* Sets the status of the instance in some activity
*
* @param int $activityId
* @param string $status new status, it can be 'running' or 'completed'
* @return bool
* @access public
*/
function setActivityStatus($activityId,$status)
{
if (!(($status=='running') || ($status=='completed')))
{
$this->error[] = tra('unknown status');
return false;
}
for($i=0;$iactivities);$i++)
{
if($this->activities[$i]['wf_activity_id']==$activityId)
{
$query = 'update '.GALAXIA_TABLE_PREFIX.'instance_activities set wf_status=? where wf_activity_id=? and wf_instance_id=?';
$this->query($query,array($status,(int)$activityId,(int)$this->instanceId));
return true;
}
}
$this->error[] = tra('new status not set, no corresponding activity was found.');
return false;
}
/**
* Gets the status of the instance in some activity, can be
* 'running' or 'completed'
*
* @param int $activityId
* @return mixed
* @access public
*/
function getActivityStatus($activityId) {
for($i=0;$iactivities);$i++) {
if($this->activities[$i]['wf_activity_id']==$activityId) {
return $this->activities[$i]['wf_status'];
}
}
$this->error[] = tra('activity status not avaible, no corresponding activity was found.');
return false;
}
/**
* Resets the start time of the activity indicated to the current time
*
* @param int $activityId
* @return bool
* @access public
*/
function setActivityStarted($activityId) {
$now = date("U");
for($i=0;$iactivities);$i++) {
if($this->activities[$i]['wf_activity_id']==$activityId) {
$this->activities[$i]['wf_started']=$now;
$query = "update `".GALAXIA_TABLE_PREFIX."instance_activities` set `wf_started`=? where `wf_activity_id`=? and `wf_instance_id`=?";
$this->query($query,array($now,(int)$activityId,(int)$this->instanceId));
return true;
}
}
$this->error[] = tra('activity start not set, no corresponding activity was found.');
return false;
}
/**
* Gets the Unix timstamp of the starting time for the given activity
*
* @param int $activityId
* @return mixed
* @access public
*/
function getActivityStarted($activityId) {
for($i=0;$iactivities);$i++) {
if($this->activities[$i]['wf_activity_id']==$activityId) {
return $this->activities[$i]['wf_started'];
}
}
$this->error[] = tra('activity start not avaible, no corresponding activity was found.');
return false;
}
/**
* Gets an activity from the list of activities of the instance
* the result is an array describing the instance
*
* @param int $activityId
* @return mixed
* @access private
*/
function _get_instance_activity($activityId)
{
for($i=0;$iactivities);$i++) {
if($this->activities[$i]['wf_activity_id']==$activityId) {
return $this->activities[$i];
}
}
$this->error[] = tra('no corresponding activity was found.');
return false;
}
/**
* Sets the time where the instance was started
*
* @param int $time
* @return bool
* @access public
*/
function setStarted($time)
{
$this->changed['started'] = $time;
$this->unsynch = true;
return true;
}
/**
* Gets the time where the instance was started (Unix timestamp)
*
* @return int
* @access public
*/
function getStarted()
{
if (!(isset($this->changed['started'])))
{
return $this->started;
}
else
{
return $this->changed['started'];
}
}
/**
* Sets the end time of the instance (when the process was completed)
*
* @param int $time
* @return bool
* @access public
*/
function setEnded($time)
{
$this->changed['ended']=$time;
$this->unsynch = true;
return true;
}
/**
* Gets the end time of the instance (when the process was completed)
*
* @return int
* @access public
*/
function getEnded()
{
if (!(isset($this->changed['ended'])))
{
return $this->ended;
}
else
{
return $this->changed['ended'];
}
}
/**
* This set to true or false the 'Activity Completed' status which will
* be important to know if the user code has completed the current activity
*
* @param bool $bool true by default, it will be the next status of the 'Activity Completed' indicator
* @access private
* @return void
*/
function setActivityCompleted($bool)
{
$this->__activity_completed = $bool;
}
/**
* Gets the 'Activity Completed' status
*
* @return mixed
* @access public
*/
function getActivityCompleted()
{
return $this->__activity_completed;
}
/**
* This function can be called by the instance object himself (for automatic activities)
* or by the WfRuntime object. In interactive activities code users use complete() --without args--
* which refer to the WfRuntime->complete() function which call this one.
* In non-interactive activities a call to a complete() will generate errors because the engine
* does it his own way as I said first.
* Particularity of this Complete is that it is Transactional, i.e. it it done completely
* or not and row locks are ensured
*
* @param int $activityId activity that is being completed
* @param bool $addworkitem indicates if a workitem should be added for the completed
* activity (true by default)
* @return bool false it means the complete was not done for some internal reason
* consult $instance->get_error() for more informations
* @access public
*/
function complete($activityId,$addworkitem=true)
{
//$this->db->
$result = $this->query("SELECT 1 FROM " . GALAXIA_TABLE_PREFIX . "instances i, " . GALAXIA_TABLE_PREFIX . "interinstance_relations ir WHERE (ir.wf_child_instance_id = i.wf_instance_id) AND (i.wf_status IN ('active', 'exception')) AND (ir.wf_parent_lock = 1) AND (ir.wf_parent_instance_id = ?) AND (ir.wf_parent_activity_id = ?)", array($this->instanceId, $activityId));
if ($result->numRows() > 0)
die("Esta instância está aguardando que outras instâncias, das quais depende, sejam finalizadas.");
//ensure it's false at first
$this->setActivityCompleted(false);
//The complete() is in a transaction, it will be completly done or not at all
$this->db->StartTrans();
//lock rows and ensure access is granted
if (!(isset($this->security))) $this->security = &Factory::getInstance('WfSecurity');
if (!($this->security->checkUserAction($activityId,$this->instanceId,'complete')))
{
$this->error[] = tra('you were not allowed to complete the activity');
$this->db->FailTrans();
}
else
{
if (!($this->_internalComplete($activityId,$addworkitem)))
{
$this->error[] = tra('The activity was not completed');
$this->db->FailTrans();
}
}
//we mark completion with result of the transaction wich will be false if any error occurs
//this is the end of the transaction
$this->setActivityCompleted($this->db->CompleteTrans());
//we return the completion state.
return $this->getActivityCompleted();
}
/**
* YOU MUST NOT CALL _internalComplete() directly, use Complete() instead
*
* @param int $activityId activity that is being completed
* @param bool $addworkitem indicates if a workitem should be added for the completed
* activity (true by default)
* @return bool false it means the complete was not done for some internal reason
* consult $instance->get_error() for more informations
* @access private
*/
function _internalComplete($activityId,$addworkitem=true) {
global $user;
if(empty($user))
{
$theuser='*';
}
else
{
$theuser=$user;
}
if(!($activityId))
{
$this->error[] = tra('it was impossible to complete, no activity was given.');
return false;
}
$now = date("U");
// If we are completing a start activity then the instance must
// be created first!
$type = $this->getOne('select wf_type from '.GALAXIA_TABLE_PREFIX.'activities where wf_activity_id=?',array((int)$activityId));
if($type=='start')
{
if (!($this->_createNewInstance((int)$activityId,$theuser)))
{
return false;
}
}
else
{
// Now set ended
$query = 'update '.GALAXIA_TABLE_PREFIX.'instance_activities set wf_ended=? where wf_activity_id=? and wf_instance_id=?';
$this->query($query,array((int)$now,(int)$activityId,(int)$this->instanceId));
}
//Set the status for the instance-activity to completed
//except for start activities
if (!($type=='start'))
{
if (!($this->setActivityStatus($activityId,'completed')))
{
return false;
}
}
//If this and end actt then terminate the instance
if($type=='end')
{
if (!($this->terminate($now)))
{
return false;
}
}
//now we synchronise instance with the database
if (!($this->sync())) return false;
//Add a workitem to the instance
if ($addworkitem)
{
return $this->addworkitem($type,$now, $activityId);
}
else
{
return true;
}
}
/**
* This function will add a workitem in the workitems table, the instance MUST be synchronised before
* calling this function.
*
* @param string $activity_type activity type, needed because internals are different for start activities
* @param int $ended ending time
* @param int $activityId finishing activity id
* @return mixed
* @access private
*/
function addworkitem($activity_type, $ended, $activityId)
{
$iid = $this->instanceId;
$max = $this->getOne('select max(wf_order_id) from '.GALAXIA_TABLE_PREFIX.'workitems where wf_instance_id=?',array((int)$iid));
if(!$max)
{
$max=1;
}
else
{
$max++;
}
if($activity_type=='start')
{
//Then this is a start activity ending
$started = $this->getStarted();
//at this time owner is the creator
$putuser = $this->getOwner();
}
else
{
$act = $this->_get_instance_activity($activityId);
if(!$act)
{
//this will abort the function
$this->error[] = tra('failed to create workitem');
return false;
}
else
{
$started = $act['wf_started'];
$putuser = $act['wf_user'];
}
}
//no more serialize, done by the core security_cleanup
$properties = $this->security_cleanup($this->properties, false); //serialize($this->properties);
$query='insert into '.GALAXIA_TABLE_PREFIX.'workitems
(wf_instance_id,wf_order_id,wf_activity_id,wf_started,wf_ended,wf_properties,wf_user) values(?,?,?,?,?,?,?)';
$this->query($query,array((int)$iid,(int)$max,(int)$activityId,(int)$started,(int)$ended,$properties,$putuser));
return true;
}
/**
* Send autorouted activities to the next one(s)
* YOU MUST NOT CALL sendAutorouted() for non-interactive activities since
* the engine does automatically complete and send automatic activities after
* executing them
* This function is in fact a Private function runned by the engine
* You should never use it without knowing very well what you're doing
*
* @param int $activityId activity that is being completed, when this is not
* passed the engine takes it from the $_REQUEST array,all activities
* are executed passing the activityId in the URI
* @param bool $force indicates that the instance must be routed no matter if the
* activity is auto-routing or not. This is used when "sending" an
* instance from a non-auto-routed activity to the next activity
* @return mixed
* @access private
*/
function sendAutorouted($activityId,$force=false)
{
$returned_value = Array();
$type = $this->getOne("select `wf_type` from `".GALAXIA_TABLE_PREFIX."activities` where `wf_activity_id`=?",array((int)$activityId));
//on a end activity we have nothing to do
if ($type == 'end')
{
return true;
}
//If the activity ending is not autorouted then we have nothing to do
if (!(($force) || ($this->getOne("select `wf_is_autorouted` from `".GALAXIA_TABLE_PREFIX."activities` where `wf_activity_id`=?",array($activityId)) == 'y')))
{
$returned_value['transition']['status'] = 'not autorouted';
return $returned_value;
}
//If the activity ending is autorouted then send to the activity
// Now determine where to send the instance
$candidates = $this->getActivityCandidates($activityId);
if($type == 'split')
{
$erase_from = false;
$num_candidates = count($candidates);
$returned_data = Array();
$i = 1;
foreach ($candidates as $cand)
{
// only erase split activity in instance when all the activities comming from the split have been set up
if ($i == $num_candidates)
{
$erase_from = true;
}
$returned_data[$i] = $this->sendTo($activityId,$cand,$erase_from);
$this->unsetNextUser($cand);
$this->sync();
$i++;
}
$this->unsetNextUser('*' . $activityId);
$this->sync();
return $returned_data;
}
elseif($type == 'switch')
{
if (in_array($this->nextActivity[$activityId],$candidates))
{
$selectedActivity = $this->nextActivity[$activityId];
foreach ($candidates as $candidate)
if ($candidate != $selectedActivity)
$this->unsetNextUser($candidate);
$output = $this->sendTo((int)$activityId,(int)$selectedActivity);
$this->unsetNextUser($selectedActivity);
$this->unsetNextUser('*' . $activityId);
$this->sync();
return $output;
}
else
{
$returned_value['transition']['failure'] = tra('Error: nextActivity does not match any candidate in autorouting switch activity');
return $returned_value;
}
}
else
{
if (count($candidates)>1)
{
$returned_value['transition']['failure'] = tra('Error: non-deterministic decision for autorouting activity');
return $returned_value;
}
else
{
$output = $this->sendTo((int)$activityId,(int)$candidates[0]);
$this->unsetNextUser($candidates[0]);
$this->unsetNextUser('*' . $activityId);
$this->sync();
return $output;
}
}
}
/**
* This is a semi-private function, use GUI's abort function
* Aborts an activity and terminates the whole instance. We still create a workitem to keep track
* of where in the process the instance was aborted
*
* @param bool $addworkitem
* @return bool
* @access public
* @todo review, reuse of completed code
*/
function abort($addworkitem=true)
{
// If we are aborting a start activity then the instance must
// be created first!
// ==> No, there's no reason to have an uncompleted start activity to abort
// load all the activities of the current instance
if ($addworkitem)
{
$activities = array();
$query = 'SELECT a.wf_type AS wf_type, ia.wf_activity_id AS wf_activity_id FROM '.GALAXIA_TABLE_PREFIX.'activities a, '.GALAXIA_TABLE_PREFIX.'instance_activities ia WHERE (ia.wf_activity_id = a.wf_activity_id) AND (ia.wf_instance_id = ?)';
$result = $this->query($query,array((int)$this->instanceId));
while ($row = $result->fetchRow())
$activities[] = $row;
}
// Now set ended on instance_activities
$now = date("U");
// terminate the instance with status 'aborted'
if (!($this->terminate($now,'aborted')))
return false;
//now we synchronise instance with the database
if (!($this->sync()))
return false;
//Add a workitem to the instance
if ($addworkitem)
{
foreach ($activities as $activity)
if (!($this->addworkitem($activity['wf_type'], $now, $activity['wf_activity_id'])))
return false;
return true;
}
else
{
return true;
}
}
/**
* Terminates the instance marking the instance and the process
* as completed. This is the end of a process
* Normally you should not call this method since it is automatically
* called when an end activity is completed
* object is synched at the end of this function
*
* @param int $time terminating time
* @param string $status
* @return bool
* @access private
*/
function terminate($time, $status = 'completed') {
//Set the status of the instance to completed
if (!($this->setEnded((int)$time))) return false;
if (!($this->setStatus($status))) return false;
$query = "delete from `".GALAXIA_TABLE_PREFIX."instance_activities` where `wf_instance_id`=?";
$this->query($query,array((int)$this->instanceId));
return $this->sync();
}
/**
* Sends the instance from some activity to another activity. (walk on a transition)
* You should not call this method unless you know very very well what
* you are doing
*
* @param int $from activity id at the start of the transition
* @param int $activityId activity id at the end of the transition
* @param bool $erase_from true by default, if true the coming activity row will be erased from
* instance_activities table. You should set it to false for example with split activities while
* you still want to re-call this function
* @return mixed false if anything goes wrong, true if we are at the end of the execution tree and an array
* if a part of the process was automatically runned at the end of the transition. this array contains
* 2 keys 'transition' is the transition we walked on, 'activity' is the result of the run part if it was an automatic activity.
* 'activity' value is an associated array containing several usefull keys:
* 'completed' is a boolean indicating that the activity was completed or not
* 'debug contains debug messages
* 'info' contains some usefull infos about the activity-instance running (like names)
* 'next' is the result of a SendAutorouted part which could in fact be the result of a call to this function, etc
* @access public
*/
function sendTo($from,$activityId,$erase_from=true)
{
//we will use an array for return value
$returned_data = Array();
//1: if we are in a join check
//if this instance is also in
//other activity if so do
//nothing
$query = 'select wf_type, wf_name from '.GALAXIA_TABLE_PREFIX.'activities where wf_activity_id=?';
$result = $this->query($query,array($activityId));
if (empty($result))
{
$returned_data['transition']['failure'] = tra('Error: trying to send an instance to an activity but it was impossible to get this activity');
return $returned_data;
}
while ($res = $result->fetchRow())
{
$type = $res['wf_type'];
$targetname = $res['wf_name'];
}
$returned_data['transition']['target_id'] = $activityId;
$returned_data['transition']['target_name'] = $targetname;
// Verify the existence of a transition
if(!$this->getOne("select count(*) from `".GALAXIA_TABLE_PREFIX."transitions` where `wf_act_from_id`=? and `wf_act_to_id`=?",array($from,(int)$activityId))) {
$returned_data['transition']['failure'] = tra('Error: trying to send an instance to an activity but no transition found');
return $returned_data;
}
//init
$putuser=0;
//try to determine the user or *
//Use the nextUser
$the_next_user = $this->getNextUser($activityId);
if($the_next_user)
{
//we check rights for this user on the next activity
if (!(isset($this->security))) $this->security = &Factory::getInstance('WfSecurity');
if ($this->security->checkUserAccess($the_next_user,$activityId))
{
$putuser = $the_next_user;
}
}
if ($putuser===0)
{
// then check to see if there is a default user
$activity_manager = &Factory::newInstance('ActivityManager');
//get_default_user will give us '*' if there is no default_user or if the default user has no role
//mapped anymore
$default_user = $activity_manager->get_default_user($activityId,true);
unset($activity_manager);
// if they were no nextUser, no unique user avaible, no default_user then we'll have '*'
// which will let user having the good role mapping grab this activity later
$putuser = $default_user;
}
if ($the_next_user)
$this->setNextUser($the_next_user, '*' . $activityId);
//update the instance_activities table
//if not splitting delete first
//please update started,status,user
if (($erase_from) && (!empty($this->instanceId)))
{
$query = "delete from `".GALAXIA_TABLE_PREFIX."instance_activities` where `wf_instance_id`=? and `wf_activity_id`=?";
$this->query($query,array((int)$this->instanceId,$from));
}
if ($type == 'join') {
if (count($this->activities)>1) {
// This instance will have to wait!
$returned_data['transition']['status'] = 'waiting';
return $returned_data;
}
}
//create the new instance-activity
$returned_data['transition']['target_id'] = $activityId;
$returned_data['transition']['target_name'] = $targetname;
$now = date("U");
$iid = $this->instanceId;
$query="delete from `".GALAXIA_TABLE_PREFIX."instance_activities` where `wf_instance_id`=? and `wf_activity_id`=?";
$this->query($query,array((int)$iid,(int)$activityId));
$query="insert into `".GALAXIA_TABLE_PREFIX."instance_activities`(`wf_instance_id`,`wf_activity_id`,`wf_user`,`wf_status`,`wf_started`) values(?,?,?,?,?)";
$this->query($query,array((int)$iid,(int)$activityId,$putuser,'running',(int)$now));
//record the transition walk
$returned_data['transition']['status'] = 'done';
//we are now in a new activity
$this->_populate_activities($iid);
//if the activity is not interactive then
//execute the code for the activity and
//complete the activity
$isInteractive = $this->getOne("select `wf_is_interactive` from `".GALAXIA_TABLE_PREFIX."activities` where `wf_activity_id`=?",array((int)$activityId));
if ($isInteractive=='n')
{
//first we sync actual instance because the next activity could need it
if (!($this->sync()))
{
$returned_data['activity']['failure'] = true;
return $returned_data;
}
// Now execute the code for the activity
$this->activityID = $activityId;
$returned_data['activity'] = $this->executeAutomaticActivity($activityId, $iid);
$this->activityID = $from;
}
else
{
// we sync actual instance
if (!($this->sync()))
{
$returned_data['failure'] = true;
return $returned_data;
}
}
return $returned_data;
}
/**
* This is a public method only because the GUI can ask this action for the admin
* on restart failed automated activities, but in fact it's quite an internal function,
* This function handle the execution of automatic activities (and the launch of transitions
* which can be related to this activity)
*
* @param int $activityId activity id at the end of the transition
* @param int $iid instance id
* @return array
* @access public
*/
function executeAutomaticActivity($activityId, $iid)
{
$returned_data = Array();
// Now execute the code for the activity (function defined in galaxia's config.php)
$returned_data =& galaxia_execute_activity($activityId, $iid , 1);
//we should have some info in $returned_data now. if it is false there's a problem
if ((!(is_array($returned_data))) && (!($returned_data)) )
{
$this->error[] = tra('failed to execute automatic activity');
//record the failure
$returned_data['failure'] = true;
return $returned_data;
}
else
{
//ok, we have an array, but it can still be a bad result
//this one is just for debug info
if (isset($returned_data['debug']))
{
//we retrieve this info here, in this object
$this->error[] = $returned_data['debug'];
}
//and this really test if it worked, if not we have a nice failure message (better than just failure=true)
if (isset($returned_data['failure']))
{
$this->error[] = tra('failed to execute automatic activity');
$this->error[] = $returned_data['failure'];
//record the failure
return $returned_data;
}
}
// Reload in case the activity did some change, last sync was done just before calling this function
//TODO: check if this sync is really needed
$this->getInstance($this->instanceId, false, false);
//complete the automatic activity----------------------------
if ($this->Complete($activityId))
{
$returned_data['completed'] = true;
//and send the next autorouted activity if any
$returned_data['next'] = $this->sendAutorouted($activityId);
}
else
{
$returned_data['failure'] = $this->get_error();
}
return $returned_data;
}
/**
* Gets a comment for this instance
*
* @param int $cId
* @return mixed
* @access public
*/
function get_instance_comment($cId) {
$iid = $this->instanceId;
$query = "select * from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_instance_id`=? and `wf_c_id`=?";
$result = $this->query($query,array((int)$iid,(int)$cId));
$res = $result->fetchRow();
return $res;
}
/**
* Inserts or updates an instance comment
*
* @param int $cId
* @param int $activityId
* @param object $activity
* @param int $user
* @param string $title
* @param mixed $comment
* @return bool
* @access public
*/
function replace_instance_comment($cId, $activityId, $activity, $user, $title, $comment) {
if (!$user) {
$user = 'Anonymous';
}
$iid = $this->instanceId;
//no need on pseudo-instance
if (!!($this->instanceId))
{
if ($cId)
{
$query = "update `".GALAXIA_TABLE_PREFIX."instance_comments` set `wf_title`=?,`wf_comment`=? where `wf_instance_id`=? and `wf_c_id`=?";
$this->query($query,array($title,$comment,(int)$iid,(int)$cId));
}
else
{
$hash = md5($title.$comment);
if ($this->getOne("select count(*) from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_instance_id`=? and `wf_hash`=?",array($iid,$hash)))
{
return false;
}
$now = date("U");
$query ="insert into `".GALAXIA_TABLE_PREFIX."instance_comments`(`wf_instance_id`,`wf_user`,`wf_activity_id`,`wf_activity`,`wf_title`,`wf_comment`,`wf_timestamp`,`wf_hash`) values(?,?,?,?,?,?,?,?)";
$this->query($query,array((int)$iid,$user,(int)$activityId,$activity,$title,$comment,(int)$now,$hash));
}
}
return true;
}
/**
* Removes an instance comment
*
* @param int $cId
* @access public
* @return void
*/
function remove_instance_comment($cId) {
$iid = $this->instanceId;
$query = "delete from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_c_id`=? and `wf_instance_id`=?";
$this->query($query,array((int)$cId,(int)$iid));
}
/**
* Lists instance comments
*
* @return array
* @access public
*/
function get_instance_comments() {
$iid = $this->instanceId;
$query = "select * from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_instance_id`=? order by ".$this->convert_sortmode("timestamp_desc");
$result = $this->query($query,array((int)$iid));
$ret = Array();
while($res = $result->fetchRow()) {
$ret[] = $res;
}
return $ret;
}
/**
* Get the activity candidates (more than one in split and switch)
*
* @param int $activityID The activity from which we want to obtain the candidate activities
* @return array List of activities that can be reached from one given activity
* @access public
*/
function getActivityCandidates($activityID)
{
$query = 'SELECT wf_act_to_id FROM ' . GALAXIA_TABLE_PREFIX . 'transitions WHERE (wf_act_from_id = ?)';
$result = $this->query($query, array((int) $activityID));
$candidates = Array();
while ($res = $result->fetchRow())
$candidates[] = $res['wf_act_to_id'];
return $candidates;
}
}
?>