source: branches/1.2/workflow/inc/engine/src/API/Instance.php @ 1349

Revision 1349, 57.4 KB checked in by niltonneto, 15 years ago (diff)

Ticket #561 - Inclusão do módulo Workflow faltante nessa versão.

  • Property svn:executable set to *
Line 
1<?php
2require_once (GALAXIA_LIBRARY.SEP.'src'.SEP.'common'.SEP.'Base.php');
3require_once(GALAXIA_LIBRARY . SEP . 'src' . SEP . 'common' . SEP . 'WfSecurity.php');
4require_once(GALAXIA_LIBRARY . SEP . 'src' . SEP . 'ProcessManager' . SEP . 'ActivityManager.php');
5
6/**
7 * This class represents a process instance, it is used when any activity is
8 * executed. The $instance object is created representing the instance of a
9 * process being executed in the activity or even a to-be-created instance
10 * if the activity is a start activity
11 *
12 * @package Galaxia
13 * @license http://www.gnu.org/copyleft/gpl.html GPL
14 */
15class Instance extends Base {
16  /**
17   * @var array $changed Changed instance object's members
18   * @access protected
19   */
20  var $changed = Array('properties' => Array(), 'nextActivity' => Array(), 'nextUser' => Array());
21  /**
22   * @var array $properties Instance properties
23   * @access protected
24   */
25  var $properties = Array();
26  /**
27   * @var array $cleared Used to detect conflicts on sync with the database
28   * @access protected
29   */ 
30  var $cleared = Array();
31  /**
32   * @var string  $owner Instance owner
33   * @access protected
34   */ 
35  var $owner = '';
36  /**
37   * @var string $status Instance status
38   * @access protected
39   */ 
40  var $status = '';
41  /**
42   * @var bool $started
43   * @access protected
44   */ 
45  var $started;
46  /**
47   * @var array $nextActivity
48   * @access protected
49   */ 
50  var $nextActivity=Array();
51  /**
52   * @var string $nextUser
53   * @access protected
54   */ 
55  var $nextUser;
56  /**
57   * @var bool $ended
58   * @access protected
59   */ 
60  var $ended;
61  /**
62   * @var string $name Instance name
63   * @access protected
64   */
65  var $name='';
66  /**
67   * @var string $category Instance category
68   * @access protected
69   */
70  var $category;
71  /**
72   * @var int $prioryty Instance priority
73   * @access protected
74   */
75  var $priority = 1;
76
77  /**
78   * @var bool $isChildInstance Indicates wether the current instance is a child instance or not
79   * @access public
80   */
81  var $isChildInstance = false;
82  /**
83   * @var bool $parentLock Indicates wether the parent instance depends on the child instance or not
84   * @access public
85   */
86  var $parentLock = false;
87  /**
88   * @var int $parentActivityId The activity ID of the parent instance
89   * @access public
90   */
91  var $parentActivityId;
92  /**
93   * @var int $parentInstanceId The instance ID of the parent instance
94   * @access public
95   */
96  var $parentInstanceId;
97
98  /**
99   * @var array $activities Array of assocs(activityId, status, started, ended, user, name, interactivity, autorouting)
100   * @access protected
101   */
102  var $activities = Array();
103  /**
104   * @var int $pIdProcess id
105   * @access protected
106   */
107  var $pId;
108  /**
109   * @var int $instanceId Instance id
110   * @access protected
111   */
112  var $instanceId = 0;
113  /**
114   * @var array $workitems An array of workitem ids, date, duration, activity name, user, activity type and interactivity
115   * @access protected
116   */
117  var $workitems = Array();
118  /**
119   * @var object $security Performs some tests and locks
120   * @access protected
121   */
122  var $security;
123  /**
124   * @var bool $__activity_completed Internal reminder
125   * @access protected
126   */
127  var $__activity_completed=false;
128  /**
129   * @var bool $unsynch indicator, if true we are not synchronised in the memory object with the database
130   * @see sync()
131   * @access protected
132   */
133  var $unsynch=false;
134 
135  /**
136   * Constructor
137   *
138   * @param object $db ADOdb object
139   * @access public
140   */
141
142  var $activityID = null;
143  function Instance($db)
144  {
145    $this->child_name = 'Instance';
146    parent::Base($db);
147  }
148
149  /**
150   * Method used to load an instance data from the database.
151   * This function will load/initialize members of the instance object from the database
152   * it will populatae all members and will by default populate the related activities array
153   * and the workitems (history) array.
154   *
155   * @param int $instanceId
156   * @param bool $load_activities true by default, do we need to reload activities from the database?
157   * @param bool $load_workitems true by default, do we need to reload workitems from the database? 
158   * @return bool
159   * @access protected
160   */
161  function getInstance($instanceId, $load_activities=true, $load_workitems=true)
162  {
163    if (!($instanceId)) return true; //start activities for example - pseudo instances
164    // Get the instance data
165    $query = "select * from `".GALAXIA_TABLE_PREFIX."instances` where `wf_instance_id`=?";
166    $result = $this->query($query,array((int)$instanceId));
167    if( empty($result) || (!$result->numRows())) return false;
168    $res = $result->fetchRow();
169
170    //Populate
171    $this->properties = unserialize(base64_decode($res['wf_properties']));
172    $this->status = $res['wf_status'];
173    $this->pId = $res['wf_p_id'];
174    $this->instanceId = $res['wf_instance_id'];
175    $this->priority = $res['wf_priority'];
176    $this->owner = $res['wf_owner'];
177    $this->started = $res['wf_started'];
178    $this->ended = $res['wf_ended'];
179    $this->nextActivity = unserialize(base64_decode($res['wf_next_activity']));
180    $this->nextUser = unserialize(base64_decode($res['wf_next_user']));
181    $this->name = $res['wf_name'];
182    $this->category = $res['wf_category'];
183
184    // Get the activities where the instance is (nothing for start activities)
185    if ($load_activities)
186    {
187      $this->_populate_activities($instanceId);
188
189    }
190   
191    // Get the workitems where the instance is
192    if ($load_workitems)
193    {
194      $query = "select wf_item_id, wf_order_id, gw.wf_instance_id, gw.wf_activity_id, wf_started, wf_ended, gw.wf_user,
195              ga.wf_name, ga.wf_type, ga.wf_is_interactive
196              from ".GALAXIA_TABLE_PREFIX."workitems gw
197              INNER JOIN ".GALAXIA_TABLE_PREFIX."activities ga ON ga.wf_activity_id = gw.wf_activity_id
198              where wf_instance_id=? order by wf_order_id ASC";
199      $result = $this->query($query,array((int)$instanceId));
200      if (!(empty($result)))
201      {
202        while($res = $result->fetchRow())
203        {
204          $this->workitems[]=$res;
205        }
206      }
207      return true;
208    }
209   
210  }
211 
212  /**
213   * Loads all activities related to the insance given in parameter in the activities array
214   *
215   * @access private
216   * @param int $instanceId
217   */
218  function _populate_activities($instanceId)
219  {
220    $this->activities=Array();
221    $query = "select gia.wf_activity_id, gia.wf_instance_id, wf_started, wf_ended, wf_started, wf_user, wf_status,
222            ga.wf_is_autorouted, ga.wf_is_interactive, ga.wf_name, ga.wf_type
223            from ".GALAXIA_TABLE_PREFIX."instance_activities gia
224            INNER JOIN ".GALAXIA_TABLE_PREFIX."activities ga ON ga.wf_activity_id = gia.wf_activity_id
225            where wf_instance_id=?";
226    $result = $this->query($query,array((int)$instanceId));
227    if (!(empty($result)))
228    {
229      while($res = $result->fetchRow())
230      {
231        $this->activities[] = $res;
232      }
233    }
234  }
235
236  /**
237   * Performs synchronization on an instance member
238   *
239   * @param bool $changed
240   * @param array $init
241   * @param array $actual
242   * @param string $name
243   * @param string $fieldname
244   * @param array $namearray
245   * @param array $vararray
246   * @return void
247   * @access private
248   */
249  function _synchronize_member(&$changed,&$init,&$actual,$name,$fieldname,&$namearray,&$vararray)
250  {
251    //if we work with arrays then it's more complex
252    //echo "<br>$name is_array?".(is_array($changed)); _debug_array($changed);
253    if (!(is_array($changed)))
254    {
255      if (isset($changed))
256      {
257        //detect unsynchro
258        if (!($actual==$init))
259        {
260          $this->error[] = tra('Instance: unable to modify %1, someone has changed it before us', $name);
261        }
262        else
263        {
264          $namearray[] = $fieldname;
265          $vararray[] = $changed;
266          $actual = $changed;
267        }
268        unset ($changed);
269      }
270    }
271    else //we are working with arrays (properties for example)
272    {
273      $modif_done = false;
274      foreach ($changed as $key => $value)
275      {
276        //detect unsynchro
277        if (!($actual[$key]==$init[$key]))
278        {
279          $this->error[] = tra('Instance: unable to modify %1 [%2], someone has changed it before us', $name, $key);
280        }
281        else
282        {
283          $actual[$key] = $value;
284          $modif_done = true;
285        }
286      }
287      if ($modif_done) //at least one modif
288      {
289        $namearray[] = $fieldname;
290        //no more serialize, done by the core security_cleanup
291        $vararray[] = $actual; //serialize($actual);
292      }   
293      $changed=Array();
294    }
295  }
296 
297  /**
298   * Synchronize thes instance object with the database. All change smade will be recorded except
299   * conflicting ones (changes made on members or properties that has been changed by another source
300   * --could be another 'instance' of this instance or an admin form-- since the last call of sync() )
301   * the unsynch private member is used to test if more heavy tests should be done or not
302   * pseudo instances (start, standalone) are not synchronised since there is no record on database
303   *
304   * @access public
305   * @return bool 
306   */
307  function sync()
308  {
309    if ( (!($this->instanceId)) || (!($this->unsynch)) )
310    {
311      //echo "<br>nothing to do ".$this->unsynch;
312      return true;
313    }
314    //echo "<br> synch!";_debug_array($this->changed);
315    //do it in a transaction, can have several activities running
316    $this->db->StartTrans();
317    //we need to make a row lock now,
318    $where = 'wf_instance_id='.(int)$this->instanceId;
319    if (!($this->db->RowLock(GALAXIA_TABLE_PREFIX.'instances', $where)))
320    {
321      $this->error[] = 'sync: '.tra('failed to obtain lock on %1 table', 'instances');
322      $this->db->FailTrans();
323    }
324    else
325    {
326      //wf_p_id and wf_instance_id are set in creation only.
327      //we remember initial values
328      $init_properties = $this->properties;
329      $init_status = $this->status;
330      $init_priority = $this->priority;
331      $init_owner = $this->owner;
332      $init_started = $this->started;
333      $init_ended = $this->ended;
334      $init_nextUser = $this->nextUser;
335      $init_nextActivity = $this->nextActivity;
336      $init_name = $this->name;
337      $init_category = $this->category;
338      // we re-read instance members to detect conflicts, changes made while we were unsynchronised
339      $this->getInstance($this->instance_id, false, false);
340      // Now for each modified field we'll change the database vale if nobody has changed
341      // the database value before us
342      // (Drovetto) Inclusion of the nextUser field in the synchronization
343      $bindvars = Array();
344      $querysets = Array();
345      $queryset = '';
346      $this->_synchronize_member($this->changed['status'],$init_status,$this->status,tra('status'),'wf_status',$querysets,$bindvars);
347      $this->_synchronize_member($this->changed['priority'],$init_priority,$this->priority,tra('priority'),'wf_priority',$querysets,$bindvars);
348      $this->_synchronize_member($this->changed['owner'],$init_owner,$this->owner,tra('owner'),'wf_owner',$querysets,$bindvars);
349      $this->_synchronize_member($this->changed['started'],$init_started,$this->started,tra('started'),'wf_started',$querysets,$bindvars);
350      $this->_synchronize_member($this->changed['ended'],$init_ended,$this->ended,tra('ended'),'wf_ended',$querysets,$bindvars);
351      $this->_synchronize_member($this->changed['name'],$init_name,$this->name,tra('name'),'wf_name',$querysets,$bindvars);
352      $this->_synchronize_member($this->changed['category'],$init_category,$this->category,tra('category'),'wf_category',$querysets,$bindvars);
353      $this->_synchronize_member($this->changed['properties'],$init_properties,$this->properties,tra('property'),'wf_properties',$querysets,$bindvars);
354      $this->_synchronize_member($this->changed['nextUser'],$init_nextUser,$this->nextUser,tra('next user'),'wf_next_user',$querysets,$bindvars);
355      $this->_synchronize_member($this->changed['nextActivity'],$init_nextActivity,$this->nextActivity,tra('next activity'),'wf_next_activity',$querysets,$bindvars);
356      /* remove unsetted properties */
357      if (($propertiesIndex = array_search("wf_properties", $querysets)) !== false)
358        foreach ($this->cleared as $clearedName)
359          unset($bindvars[$propertiesIndex][$clearedName]);
360      /* remove unsetted nextUser */
361      if (($nextUserIndex = array_search("wf_next_user", $querysets)) !== false)
362        foreach ($bindvars[$nextUserIndex] as $nextUserKey => $nextUserValue)
363          if ($bindvars[$nextUserIndex][$nextUserKey] == '__UNDEF__')
364          {
365            unset($bindvars[$nextUserIndex][$nextUserKey]);
366            unset($this->nextUser[$nextUserKey]);
367          }
368      if (!(empty($querysets)))
369      {
370        $queryset = implode(' = ?,', $querysets). ' = ?';
371        $query = 'update '.GALAXIA_TABLE_PREFIX.'instances set '.$queryset
372              .' where wf_instance_id=?';
373        $bindvars[] = $this->instanceId;
374        //echo "<br> query $query"; _debug_array($bindvars);
375        $this->query($query,$bindvars);
376      }
377    }
378    if (!($this->db->CompleteTrans()))
379    {
380      $this->error[] = tra('failed to synchronize instance data with the database');
381      return false;
382    }
383
384    //we are not unsynchronized anymore.
385    $this->unsynch = false;
386    return true;
387  }
388 
389  /**
390   * Sets the next activity to be executed, if the current activity is
391   * a switch activity the complete() method will use the activity setted
392   * in this method as the next activity for the instance.
393   * The object records an array of transitions, as the instance can be splitted in several
394   * running activities, transition from the current activity to the given activity will
395   * be recorded and all previous recorded transitions starting from the current activity
396   * will be deleted
397   *
398   * @param int $activityId Running activity Id
399   * @param string $actname Activity name as argument (not an id)
400   * @return bool
401   * @access public 
402   */
403  function setNextActivity($activityId, $actname)
404  {
405    $pId = $this->pId;
406    $actname=trim($actname);
407    $aid = $this->getOne('select wf_activity_id from '.GALAXIA_TABLE_PREFIX.'activities where wf_p_id=? and wf_name=?',array($pId,$actname));
408    if (!($aid))
409    {
410      $this->error[] = tra('setting next activity to an unexisting activity');
411      return false;
412    }
413    $this->changed['nextActivity'][$activityId]=$aid;
414    $this->unsynch = true;
415    return true;
416  }
417
418  /**
419   * This method can be used to set the user that must perform the next
420   * activity of the process. this effectively "assigns" the instance to
421   * some user
422   *
423   * @param mixed $user The user which will execute the next activity
424   * @param mixed $activityID The ID of the activity that the user will be able to execute. '*' means any next activity
425   * @return bool true in case of success or false otherwise
426   * @access public
427   */
428  function setNextUser($user, $activityID = '*')
429  {
430    if ($activityID == '*')
431    {
432      $candidates = $this->getActivityCandidates($this->activityID);
433      foreach ($candidates as $candidate)
434        $this->changed['nextUser'][$candidate] = $user;
435    }
436    else
437      $this->changed['nextUser'][$activityID] = $user;
438    $this->unsynch = true;
439    return true;
440  }
441
442  /**
443   * Sets next instance user role
444   *
445   * @param string $roleName The name of the role
446   * @param mixed $activityID The ID of the activity that the role will be able to execute. '*' means any next activity
447   * @return bool true in case of success or false otherwise
448   * @access public
449   */
450  function setNextRole($roleID, $activityID = '*')
451  {
452    return $this->setNextUser('p' . $roleID, $activityID);
453  }
454
455  /**
456   * Removes the user/role of the provided activity ID
457   *
458   * @param mixed $activityID The ID of the activity from which the users/roles will be removed.
459   * @return void
460   * @access public
461   */
462  function unsetNextUser($activityID = '*')
463  {
464    $this->setNextUser('__UNDEF__', $activityID);
465  }
466
467  /**
468   * This method can be used to get the user that must perform the next
469   * activity of the process. This can be empty if no setNextUser was done before.
470   * It wont return the default user but inly the user which was assigned by a setNextUser
471   *
472   * @param mixed $activityID The ID of the activity from which we want the user/role that will execute it.
473   * @return string
474   * @access public
475   */
476  function getNextUser($activityID = '*')
477  {
478    if ($activityID == '*')
479      $order = array('*' . $this->activityID);
480    else
481      $order = array($activityID, '*' . $this->activityID);
482    foreach ($order as $currentOrder)
483    {
484      if (isset($this->changed['nextUser'][$currentOrder]) && ($this->changed['nextUser'][$currentOrder] == '__UNDEF__'))
485        continue;
486      if (isset($this->changed['nextUser'][$currentOrder]))
487        return $this->changed['nextUser'][$currentOrder];
488      if (isset($this->nextUser[$currentOrder]))
489        return $this->nextUser[$currentOrder];
490    }
491    return '';
492  }
493 
494  /**
495   * Creates a new instance.
496   * This method is called in start activities when the activity is completed
497   * to create a new instance representing the started process.
498   *
499   * @param int $activityId start activity id
500   * @param int $user current user id
501   * @return bool
502   * @access private
503   */
504  function _createNewInstance($activityId,$user) {
505    // Creates a new instance setting up started, ended, user, status and owner
506    $pid = $this->getOne('select wf_p_id from '.GALAXIA_TABLE_PREFIX.'activities where wf_activity_id=?',array((int)$activityId));
507    $this->pId = $pid;
508    $this->setStatus('active');
509    //$this->setNextUser('');
510    $now = date("U");
511    $this->setStarted($now);
512    $this->setOwner($user);
513   
514    $query = 'insert into '.GALAXIA_TABLE_PREFIX.'instances
515      (wf_started,wf_ended,wf_status,wf_p_id,wf_owner,wf_properties)
516      values(?,?,?,?,?,?)';
517    $this->query($query,array($now,0,'active',$pid,$user,$this->security_cleanup(Array(),false)));
518    $this->instanceId = $this->getOne('select max(wf_instance_id) from '.GALAXIA_TABLE_PREFIX.'instances
519                      where wf_started=? and wf_owner=?',array((int)$now,$user));
520    $iid=$this->instanceId;
521   
522    // Then add in ".GALAXIA_TABLE_PREFIX."instance_activities an entry for the
523    // activity the user and status running and started now
524    $query = 'insert into '.GALAXIA_TABLE_PREFIX.'instance_activities (wf_instance_id,wf_activity_id,wf_user,
525            wf_started,wf_status) values(?,?,?,?,?)';
526    $this->query($query,array((int)$iid,(int)$activityId,$user,(int)$now,'running'));
527
528    if (($this->isChildInstance) && (!is_null($this->parentInstanceId)))
529    {
530      $query = 'INSERT INTO '.GALAXIA_TABLE_PREFIX.'interinstance_relations(wf_parent_instance_id, wf_parent_activity_id, wf_child_instance_id, wf_parent_lock) VALUES(?,?,?,?)';
531      $this->query($query,array((int) $this->parentInstanceId, (int) $this->parentActivityId, (int) $iid, (int) (($this->parentLock) ? 1 : 0)));
532    }
533   
534    //update database with other datas stored in the object
535    return $this->sync();
536  }
537 
538  /**
539   * Sets the name of this instance
540   *
541   * @param string $value
542   * @return bool
543   * @access public
544   */
545  function setName($value)
546  {
547    $this->changed['name'] = substr($value,0,120);
548    $this->unsynch = true;
549    return true;
550  }
551
552  /**
553   * Get the name of this instance
554   *
555   * @return string
556   * @access public
557   */
558  function getName()
559  {
560    if (!(isset($this->changed['name'])))
561    {
562      return $this->name;
563    }
564    else
565    {
566      return $this->changed['name'];
567    }
568  }
569
570  /**
571   * Sets the category of this instance
572   *
573   * @param string $value
574   * @return bool
575   * @access public
576   */
577  function setCategory($value)
578  {
579    $this->changed['category'] = $value;
580    $this->unsynch = true;
581    return true;
582  }
583
584  /**
585   * Get the category of this instance
586   *
587   * @return string
588   * @access public
589   */
590  function getCategory()
591  {
592    if (!(isset($this->changed['category'])))
593    {
594      return $this->category;
595    }
596    else
597    {
598      return $this->changed['category'];
599    }
600  }
601
602  /**
603   * Normalizes a property name
604   *
605   * @param string $name name you want to normalize
606   * @return string property name
607   * @access private
608   */
609  function _normalize_name($name)
610  {
611    $name = trim($name);
612    $name = str_replace(" ","_",$name);
613    $name = preg_replace("/[^0-9A-Za-z\_]/",'',$name);
614    return $name;
615  }
616
617  /**
618   * Sets a property in this instance. This method is used in activities to
619   * set instance properties.
620   * all property names are normalized for security reasons and to avoid localisation
621   * problems (A->z, digits and _ for spaces). If you have several set to call look
622   * at the setProperties function. Each call to this function has an impact on database
623   *
624   * @param string $name property name (it will be normalized)
625   * @param mixed $value value you want for this property
626   * @return bool
627   * @access public
628   */
629  function set($name,$value)
630  {
631    $name = $this->_normalize_name($name);
632    unset($this->cleared[$name]);
633    $this->changed['properties'][$name] = $this->security_cleanup($value);
634    if (is_array($value))
635      $this->changed['properties'][$name] = "__ARRAY__" . $this->changed['properties'][$name];
636    $this->unsynch = true;
637    return true;
638  }
639 
640  /**
641   * Unsets a property in this instance. This method is used in activities to
642   * unset instance properties.
643   * All property names are normalized for security reasons and to avoid localisation
644   * problems (A->z, digits and _ for spaces). Each call to this function has an impact on database
645   *
646   * @param string $name the property name (it will be normalized)
647   * @return bool
648   * @access public
649   */
650  function clear($name)
651  {
652    $this->set($name, '');
653    $this->cleared[$name] = $name;
654    $this->unsynch = true;
655    return true;
656  }
657
658  /**
659   * Checks if a property in this instance exists. This method is used in activities to
660   * check the existance of instance properties.
661   * All property names are normalized for security reasons and to avoid localisation
662   * problems (A->z, digits and _ for spaces).
663   *
664   * @param string $name property name (it will be normalized)
665   * @return bool
666   * @access public
667   */
668  function exists($name)
669  {
670    $name = $this->_normalize_name($name);
671
672    return ((isset($this->changed['properties'][$name]) || isset($this->properties[$name])) && !isset($this->cleared[$name]));
673  }
674
675  /**
676   * Sets several properties in this instance. This method is used in activities to
677   * set instance properties. Use this method if you have several properties to set
678   * as it will avoid to re-call the SQL engine for each property.
679   * all property names are normalized for security reasons and to avoid localisation
680   * problems (A->z, digits and _ for spaces).
681   *
682   * @param array $properties_array associative array containing for each record the
683   * property name as the key and the property value as the value
684   * @return bool
685   * @access public
686   */
687  function setProperties($properties_array)
688  {
689    $backup_values = $this->properties;
690    foreach ($properties_array as $key => $value)
691    {
692      $name = $this->_normalize_name($key);
693      $this->changed['properties'][$name] = $this->security_cleanup($value);
694    }
695    $this->unsynch = true;
696    return true;
697  }
698 
699  /**
700   * Gets the value of an instance property
701   *
702   * @param string $name name of the property
703   * @param mixed $defaultValue
704   * @return bool
705   * @access public
706   */
707  function get($name, $defaultValue = "__UNDEF__")
708  {
709    $name = $this->_normalize_name($name);
710    $output = "";
711
712    /* select the value of the current property */
713    if (isset($this->changed['properties'][$name]))
714    {
715      $output = $this->changed['properties'][$name];
716    }
717    else
718    {
719      if (isset($this->properties[$name]))
720      {
721        $output = $this->properties[$name];
722      }
723    }
724
725    /* if the property doesn't exist return the default value or throw an error */
726    if (isset($this->cleared[$name]) || ((!isset($this->properties[$name])) && (!isset($this->changed['properties'][$name]))))
727    {
728      if ($defaultValue != "__UNDEF__")
729      {
730        $output = $defaultValue;
731      }
732      else
733      {
734        $this->error[] = tra('property %1 not found', $name);
735        $output = false;
736      }
737    }
738
739    /* if the requested value is an enconded/serialized array, change it back to its original type */
740    if (@strcmp(@substr($output, 0, 9), "__ARRAY__") == 0)
741      if ( ($tmp = base64_decode(substr($output, 9))) !== false )
742        if ( ($tmp = unserialize($tmp)) !== false )
743          $output = $tmp;
744    return $output;
745  }
746 
747  /**
748   * Returns an array of assocs describing the activities where the instance
749   * is present, can be more than one activity if the instance was "splitted"
750   *
751   * @return array
752   * @access public
753   */
754  function getActivities() {
755    return $this->activities;
756  }
757 
758  /**
759   * Gets the instance status can be
760   * 'completed', 'active', 'aborted' or 'exception'
761   *
762   * @return string
763   * @access public
764   */
765  function getStatus() {
766    if (!(isset($this->changed['status'])))
767    {
768      return $this->status;
769    }
770    else
771    {
772      return $this->changed['status'];
773    }
774  }
775 
776  /**
777   * Sets the instance status
778   *
779   * @param string_type $status it can be: 'completed', 'active', 'aborted' or 'exception'
780   * @return bool
781   * @access public
782   */
783  function setStatus($status)
784  {
785    if (!(($status=='completed') || ($status=='active') || ($status=='aborted') || ($status=='exception')))
786    {
787      $this->error[] = tra('unknown status');
788      return false;
789    }
790    $this->changed['status'] = $status;
791    $this->unsynch = true;
792    return true;
793  }
794 
795  /**
796   * Gets the instance priority
797   *
798   * @return int
799   * @access public
800   */
801  function getPriority()
802  {
803    if (!(isset($this->changed['priority'])))
804    {
805      return $this->priority;
806    }
807    else
808    {
809      return $this->changed['priority'];
810    }
811  }
812
813  /**
814   * Sets the instance priority
815   *
816   * @param int $priority
817   * @return bool
818   * @access public
819   */
820  function setPriority($priority)
821  {
822    $mypriority = (int)$priority;
823    $this->changed['priority'] = $mypriority;
824    $this->unsynch = true;
825    return true;
826  }
827   
828  /**
829   * Returns the instanceId
830   *
831   * @return int
832   * @access public
833   */
834  function getInstanceId()
835  {
836    return $this->instanceId;
837  }
838 
839  /**
840   * Returns the processId for this instance
841   *
842   * @return int
843   * @access public
844   */
845  function getProcessId()
846  {
847    return $this->pId;
848  }
849 
850  /**
851   * Returns the user that created the instance
852   *
853   * @return int
854   * @access public
855   */
856  function getOwner()
857  {
858    if (!(isset($this->changed['owner'])))
859    {
860      return $this->owner;
861    }
862    else
863    {
864      return $this->changed['owner'];
865    }
866  }
867 
868  /**
869   * Sets the instance creator user
870   *
871   * @param int $user new owner id, musn't be false, 0 or empty
872   * @return bool
873   * @access public
874   */
875  function setOwner($user)
876  {
877    if (empty($user))
878    {
879      return false;
880    }
881    $this->changed['owner'] = $user;
882    $this->unsynch = true;
883    return true;
884  }
885 
886  /**
887   * Sets the user that must execute the activity indicated by the activityId.
888   * Note that the instance MUST be present in the activity to set the user,
889   * you can't program who will execute an activity.
890   * If the user is empty then the activity user is setted to *, allowing any
891   * authorised user to take the token later concurrent access to this function
892   * is normally handled by WfRuntime and WfSecurity theses objects are the only ones
893   * which should call this function. WfRuntime is handling the current transaction and
894   * WfSecurity is Locking the instance and instance_activities table on a 'run' action
895   * which is the action leading to this setActivityUser call (could be a release
896   * as well on auto-release)
897   *
898   * @param int $activityId
899   * @param int $theuser user id or '*' (or 0, '' or null which will be set to '*')
900   * @return bool
901   * @access public
902   */
903  function setActivityUser($activityId,$theuser) {
904    if(empty($theuser)) $theuser='*';
905    $found = false;
906    for($i=0;$i<count($this->activities);$i++) {
907      if($this->activities[$i]['wf_activity_id']==$activityId) {
908        // here we are in the good activity
909        $found = true;
910
911        // prepare queries
912        $where = ' where wf_activity_id=? and wf_instance_id=?';
913        $bindvars = array((int)$activityId,(int)$this->instanceId);
914        if(!($theuser=='*'))
915        {
916          $where .= ' and (wf_user=? or wf_user=? or wf_user LIKE ?)';
917          $bindvars[]= $theuser;
918          $bindvars[]= '*';
919          $bindvars[]= 'p%';
920        }
921       
922        // update the user
923        $query = 'update '.GALAXIA_TABLE_PREFIX.'instance_activities set wf_user=?';
924        $query .= $where;
925        $bindvars_update = array_merge(array($theuser),$bindvars);
926        $this->query($query,$bindvars_update);
927        $this->activities[$i]['wf_user']=$theuser;
928        return true;
929      }
930    }
931    // if we didn't find the activity it will be false
932    return $found;
933  }
934
935  /**
936   * Returns the user that must execute or is already executing an activity
937   * wherethis instance is present
938   *
939   * @param int $activityId
940   * @return bool
941   * @access public
942   */
943  function getActivityUser($activityId) {
944    for($i=0;$i<count($this->activities);$i++) {
945      if($this->activities[$i]['wf_activity_id']==$activityId) {
946        return $this->activities[$i]['wf_user'];
947      }
948    } 
949    return false;
950  }
951
952  /**
953   * Sets the status of the instance in some activity
954   *
955   * @param int $activityId
956   * @param string $status new status, it can be 'running' or 'completed'
957   * @return bool
958   * @access public
959   */
960  function setActivityStatus($activityId,$status)
961  {
962    if (!(($status=='running') || ($status=='completed')))
963    {
964      $this->error[] = tra('unknown status');
965      return false;
966    }
967    for($i=0;$i<count($this->activities);$i++)
968    {
969      if($this->activities[$i]['wf_activity_id']==$activityId)
970      {
971        $query = 'update '.GALAXIA_TABLE_PREFIX.'instance_activities set wf_status=? where wf_activity_id=? and wf_instance_id=?';
972        $this->query($query,array($status,(int)$activityId,(int)$this->instanceId));
973        return true;
974      }
975    }
976    $this->error[] = tra('new status not set, no corresponding activity was found.');
977    return false;
978  }
979 
980 
981  /**
982   * Gets the status of the instance in some activity, can be
983   * 'running' or 'completed'
984   *
985   * @param int $activityId
986   * @return mixed
987   * @access public
988   */
989  function getActivityStatus($activityId) {
990    for($i=0;$i<count($this->activities);$i++) {
991      if($this->activities[$i]['wf_activity_id']==$activityId) {
992        return $this->activities[$i]['wf_status'];
993      }
994    }
995    $this->error[] = tra('activity status not avaible, no corresponding activity was found.');
996    return false;
997  }
998 
999  /**
1000   * Resets the start time of the activity indicated to the current time
1001   *
1002   * @param int $activityId
1003   * @return bool
1004   * @access public
1005   */
1006  function setActivityStarted($activityId) {
1007    $now = date("U");
1008    for($i=0;$i<count($this->activities);$i++) {
1009      if($this->activities[$i]['wf_activity_id']==$activityId) {
1010        $this->activities[$i]['wf_started']=$now;
1011        $query = "update `".GALAXIA_TABLE_PREFIX."instance_activities` set `wf_started`=? where `wf_activity_id`=? and `wf_instance_id`=?";
1012        $this->query($query,array($now,(int)$activityId,(int)$this->instanceId));
1013        return true;
1014      }
1015    }
1016    $this->error[] = tra('activity start not set, no corresponding activity was found.');
1017    return false;
1018  }
1019 
1020  /**
1021   * Gets the Unix timstamp of the starting time for the given activity
1022   *
1023   * @param int $activityId
1024   * @return mixed
1025   * @access public
1026   */
1027  function getActivityStarted($activityId) {
1028    for($i=0;$i<count($this->activities);$i++) {
1029      if($this->activities[$i]['wf_activity_id']==$activityId) {
1030        return $this->activities[$i]['wf_started'];
1031      }
1032    }
1033    $this->error[] = tra('activity start not avaible, no corresponding activity was found.');
1034    return false;
1035  }
1036 
1037  /**
1038   * Gets an activity from the list of activities of the instance
1039   * the result is an array describing the instance
1040   *
1041   * @param int $activityId
1042   * @return mixed
1043   * @access private
1044   */
1045  function _get_instance_activity($activityId)
1046  {
1047    for($i=0;$i<count($this->activities);$i++) {
1048      if($this->activities[$i]['wf_activity_id']==$activityId) {
1049        return $this->activities[$i];
1050      }
1051    }
1052    $this->error[] = tra('no corresponding activity was found.');
1053    return false;
1054  }
1055
1056  /**
1057   * Sets the time where the instance was started
1058   *
1059   * @param int $time
1060   * @return bool
1061   * @access public
1062   */
1063  function setStarted($time)
1064  {
1065    $this->changed['started'] = $time;
1066    $this->unsynch = true;
1067    return true;
1068  }
1069 
1070  /**
1071   * Gets the time where the instance was started (Unix timestamp)
1072   *
1073   * @return int
1074   * @access public
1075   */
1076  function getStarted()
1077  {
1078    if (!(isset($this->changed['started'])))
1079    {
1080      return $this->started;
1081    }
1082    else
1083    {
1084      return $this->changed['started'];
1085    }
1086  }
1087 
1088  /**
1089   * Sets the end time of the instance (when the process was completed)
1090   *
1091   * @param int $time
1092   * @return bool
1093   * @access public
1094   */
1095  function setEnded($time)
1096  {
1097    $this->changed['ended']=$time;
1098    $this->unsynch = true;
1099    return true;
1100  }
1101 
1102  /**
1103   * Gets the end time of the instance (when the process was completed)
1104   *
1105   * @return int
1106   * @access public
1107   */
1108  function getEnded()
1109  {
1110    if (!(isset($this->changed['ended'])))
1111    {
1112      return $this->ended;
1113    }
1114    else
1115    {
1116      return $this->changed['ended'];
1117    }
1118  }
1119 
1120  /**
1121   * This set to true or false the 'Activity Completed' status which will
1122   * be important to know if the user code has completed the current activity
1123   *
1124   * @param bool $bool true by default, it will be the next status of the 'Activity Completed' indicator
1125   * @access private
1126   * @return void
1127   */
1128  function setActivityCompleted($bool)
1129  {
1130    $this->__activity_completed = $bool;
1131  }
1132 
1133  /**
1134   * Gets the 'Activity Completed' status
1135   *
1136   * @return mixed
1137   * @access public
1138   */
1139  function getActivityCompleted()
1140  {
1141    return $this->__activity_completed;
1142  }
1143 
1144  /**
1145   * This function can be called by the instance object himself (for automatic activities)
1146   * or by the WfRuntime object. In interactive activities code users use complete() --without args--
1147   * which refer to the WfRuntime->complete() function which call this one.
1148   * In non-interactive activities a call to a complete() will generate errors because the engine
1149   * does it his own way as I said first.
1150   * Particularity of this Complete is that it is Transactional, i.e. it it done completely
1151   * or not and row locks are ensured
1152   *
1153   * @param int $activityId activity that is being completed
1154   * @param bool $addworkitem indicates if a workitem should be added for the completed
1155   * activity (true by default)
1156   * @return bool false it means the complete was not done for some internal reason
1157   * consult $instance->get_error() for more informations
1158   * @access public
1159   */
1160  function complete($activityId,$addworkitem=true)
1161  {
1162    //$this->db->
1163    $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));
1164    if ($result->numRows() > 0)
1165      die("Esta instância está aguardando que outras instâncias, das quais depende, sejam finalizadas.");
1166    //ensure it's false at first
1167    $this->setActivityCompleted(false);
1168
1169    //The complete() is in a transaction, it will be completly done or not at all
1170    $this->db->StartTrans();
1171   
1172    //lock rows and ensure access is granted
1173    if (!(isset($this->security))) $this->security =& new WfSecurity($this->db);
1174    if (!($this->security->checkUserAction($activityId,$this->instanceId,'complete')))
1175    {
1176      $this->error[] = tra('you were not allowed to complete the activity');
1177      $this->db->FailTrans();
1178    }
1179    else
1180    {
1181      if (!($this->_internalComplete($activityId,$addworkitem)))
1182      {
1183        $this->error[] = tra('The activity was not completed');
1184        $this->db->FailTrans();
1185      }
1186    }
1187    //we mark completion with result of the transaction wich will be false if any error occurs
1188    //this is the end of the transaction
1189    $this->setActivityCompleted($this->db->CompleteTrans());
1190
1191    //we return the completion state.
1192    return $this->getActivityCompleted();
1193  }
1194
1195  /**
1196   * YOU MUST NOT CALL _internalComplete() directly, use Complete() instead
1197   *
1198   * @param int $activityId activity that is being completed
1199   * @param bool $addworkitem indicates if a workitem should be added for the completed
1200   * activity (true by default)
1201   * @return bool false it means the complete was not done for some internal reason
1202   * consult $instance->get_error() for more informations
1203   * @access private
1204   */
1205  function _internalComplete($activityId,$addworkitem=true) {
1206    global $user;
1207
1208    if(empty($user))
1209    {
1210      $theuser='*';
1211    }
1212    else
1213    {
1214      $theuser=$user;
1215    }
1216
1217    if(!($activityId))
1218    {
1219      $this->error[] = tra('it was impossible to complete, no activity was given.');
1220      return false;
1221    } 
1222   
1223    $now = date("U");
1224   
1225    // If we are completing a start activity then the instance must
1226    // be created first!
1227    $type = $this->getOne('select wf_type from '.GALAXIA_TABLE_PREFIX.'activities where wf_activity_id=?',array((int)$activityId));
1228    if($type=='start')
1229    {
1230      if (!($this->_createNewInstance((int)$activityId,$theuser)))
1231      {
1232        return false;
1233      }
1234    }
1235    else
1236    { 
1237      // Now set ended
1238      $query = 'update '.GALAXIA_TABLE_PREFIX.'instance_activities set wf_ended=? where wf_activity_id=? and wf_instance_id=?';
1239      $this->query($query,array((int)$now,(int)$activityId,(int)$this->instanceId));
1240    }
1241   
1242    //Set the status for the instance-activity to completed
1243    //except for start activities
1244    if (!($type=='start'))
1245    {
1246      if (!($this->setActivityStatus($activityId,'completed')))
1247      {
1248        return false;
1249      }
1250    }
1251   
1252    //If this and end actt then terminate the instance
1253    if($type=='end')
1254    {
1255      if (!($this->terminate($now)))
1256      {
1257        return false;
1258      }
1259    }
1260
1261    //now we synchronise instance with the database
1262    if (!($this->sync())) return false;
1263   
1264    //Add a workitem to the instance
1265    if ($addworkitem)
1266    {
1267      return $this->addworkitem($type,$now, $activityId);
1268    }
1269    else
1270    {
1271      return true;
1272    }
1273  }
1274 
1275  /**
1276   * This function will add a workitem in the workitems table, the instance MUST be synchronised before
1277   * calling this function.
1278   *
1279   * @param string $activity_type activity type, needed because internals are different for start activities
1280   * @param int $ended ending time
1281   * @param int $activityId finishing activity id
1282   * @return mixed
1283   * @access private
1284   */
1285  function addworkitem($activity_type, $ended, $activityId)
1286  {
1287    $iid = $this->instanceId;
1288    $max = $this->getOne('select max(wf_order_id) from '.GALAXIA_TABLE_PREFIX.'workitems where wf_instance_id=?',array((int)$iid));
1289    if(!$max)
1290    {
1291        $max=1;
1292    }
1293    else
1294    {
1295        $max++;
1296    }
1297    if($activity_type=='start')
1298    {
1299      //Then this is a start activity ending
1300      $started = $this->getStarted();
1301      //at this time owner is the creator
1302      $putuser = $this->getOwner();
1303    }
1304    else
1305    {
1306      $act = $this->_get_instance_activity($activityId);
1307      if(!$act)
1308      {
1309        //this will abort the function
1310        $this->error[] = tra('failed to create workitem');
1311        return false;
1312      }
1313      else
1314      {
1315        $started = $act['wf_started'];
1316        $putuser = $act['wf_user'];
1317      }
1318    }
1319    //no more serialize, done by the core security_cleanup
1320    $properties = $this->security_cleanup($this->properties, false); //serialize($this->properties);
1321    $query='insert into '.GALAXIA_TABLE_PREFIX.'workitems
1322        (wf_instance_id,wf_order_id,wf_activity_id,wf_started,wf_ended,wf_properties,wf_user) values(?,?,?,?,?,?,?)';   
1323    $this->query($query,array((int)$iid,(int)$max,(int)$activityId,(int)$started,(int)$ended,$properties,$putuser));
1324    return true;
1325  }
1326 
1327  /**
1328   * Send autorouted activities to the next one(s)
1329   * YOU MUST NOT CALL sendAutorouted() for non-interactive activities since
1330   * the engine does automatically complete and send automatic activities after
1331   * executing them
1332   * This function is in fact a Private function runned by the engine
1333   * You should never use it without knowing very well what you're doing
1334   *
1335   * @param int $activityId activity that is being completed, when this is not
1336   * passed the engine takes it from the $_REQUEST array,all activities
1337   * are executed passing the activityId in the URI
1338   * @param bool $force indicates that the instance must be routed no matter if the
1339   * activity is auto-routing or not. This is used when "sending" an
1340   * instance from a non-auto-routed activity to the next activity
1341   * @return mixed
1342   * @access private
1343   */
1344  function sendAutorouted($activityId,$force=false)
1345  {
1346    $returned_value = Array();
1347    $type = $this->getOne("select `wf_type` from `".GALAXIA_TABLE_PREFIX."activities` where `wf_activity_id`=?",array((int)$activityId));   
1348    //on a end activity we have nothing to do
1349    if ($type == 'end')
1350    {
1351      return true;
1352    }
1353    //If the activity ending is not autorouted then we have nothing to do
1354    if (!(($force) || ($this->getOne("select `wf_is_autorouted` from `".GALAXIA_TABLE_PREFIX."activities` where `wf_activity_id`=?",array($activityId)) == 'y')))
1355    {
1356      $returned_value['transition']['status'] = 'not autorouted';
1357      return $returned_value;
1358    }
1359    //If the activity ending is autorouted then send to the activity
1360    // Now determine where to send the instance
1361    $candidates = $this->getActivityCandidates($activityId);
1362    if($type == 'split')
1363    {
1364      $erase_from = false;
1365      $num_candidates = count($candidates);
1366      $returned_data = Array();
1367      $i = 1;
1368      foreach ($candidates as $cand)
1369      {
1370        // only erase split activity in instance when all the activities comming from the split have been set up
1371        if ($i == $num_candidates)
1372        {
1373          $erase_from = true;
1374        }
1375        $returned_data[$i] = $this->sendTo($activityId,$cand,$erase_from);
1376        $this->unsetNextUser($cand);
1377        $this->sync();
1378        $i++;
1379      }
1380      $this->unsetNextUser('*' . $activityId);
1381      $this->sync();
1382      return $returned_data;
1383    }
1384    elseif($type == 'switch')
1385    {
1386      if (in_array($this->nextActivity[$activityId],$candidates))
1387      {
1388        $selectedActivity = $this->nextActivity[$activityId];
1389        foreach ($candidates as $candidate)
1390          if ($candidate != $selectedActivity)
1391            $this->unsetNextUser($candidate);
1392        $output = $this->sendTo((int)$activityId,(int)$selectedActivity);
1393        $this->unsetNextUser($selectedActivity);
1394        $this->unsetNextUser('*' . $activityId);
1395        $this->sync();
1396        return $output;
1397      }
1398      else
1399      {
1400        $returned_value['transition']['failure'] = tra('Error: nextActivity does not match any candidate in autorouting switch activity');
1401        return $returned_value;
1402      }
1403    }
1404    else
1405    {
1406      if (count($candidates)>1)
1407      {
1408        $returned_value['transition']['failure'] = tra('Error: non-deterministic decision for autorouting activity');
1409        return $returned_value;
1410      }
1411      else
1412      {
1413        $output = $this->sendTo((int)$activityId,(int)$candidates[0]);
1414        $this->unsetNextUser($candidates[0]);
1415        $this->unsetNextUser('*' . $activityId);
1416        $this->sync();
1417        return $output;
1418      }
1419    }
1420  }
1421 
1422  /**
1423   * This is a semi-private function, use GUI's abort function
1424   * Aborts an activity and terminates the whole instance. We still create a workitem to keep track
1425   * of where in the process the instance was aborted
1426   *
1427   * @param bool $addworkitem
1428   * @return bool
1429   * @access public
1430   * @todo review, reuse of completed code
1431   */
1432  function abort($addworkitem=true)
1433  {
1434    // If we are aborting a start activity then the instance must
1435    // be created first!
1436    // ==> No, there's no reason to have an uncompleted start activity to abort
1437
1438    // load all the activities of the current instance
1439    if ($addworkitem)
1440    {
1441      $activities = array();
1442      $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 = ?)';
1443      $result = $this->query($query,array((int)$this->instanceId));
1444      while ($row = $result->fetchRow())
1445        $activities[] = $row;
1446    }
1447
1448    // Now set ended on instance_activities
1449    $now = date("U");
1450
1451    // terminate the instance with status 'aborted'
1452    if (!($this->terminate($now,'aborted')))
1453      return false;
1454
1455    //now we synchronise instance with the database
1456    if (!($this->sync()))
1457      return false;
1458   
1459    //Add a workitem to the instance
1460    if ($addworkitem)
1461    {
1462      foreach ($activities as $activity)
1463        if (!($this->addworkitem($activity['wf_type'], $now, $activity['wf_activity_id'])))
1464          return false;
1465      return true;
1466    }
1467    else
1468    {
1469      return true;
1470    }
1471  }
1472 
1473  /**
1474   * Terminates the instance marking the instance and the process
1475   * as completed. This is the end of a process
1476   * Normally you should not call this method since it is automatically
1477   * called when an end activity is completed
1478   * object is synched at the end of this function
1479   *
1480   * @param int $time terminating time
1481   * @param string $status
1482   * @return bool
1483   * @access private
1484   */
1485  function terminate($time, $status = 'completed') {
1486    //Set the status of the instance to completed
1487    if (!($this->setEnded((int)$time))) return false;
1488    if (!($this->setStatus($status))) return false;
1489    $query = "delete from `".GALAXIA_TABLE_PREFIX."instance_activities` where `wf_instance_id`=?";
1490    $this->query($query,array((int)$this->instanceId));
1491    return $this->sync();
1492  }
1493 
1494 
1495  /**
1496   * Sends the instance from some activity to another activity. (walk on a transition)
1497   * You should not call this method unless you know very very well what
1498   * you are doing
1499   *
1500   * @param int $from activity id at the start of the transition
1501   * @param int $activityId activity id at the end of the transition
1502   * @param bool $erase_from true by default, if true the coming activity row will be erased from
1503   * instance_activities table. You should set it to false for example with split activities while
1504   * you still want to re-call this function
1505   * @return mixed false if anything goes wrong, true if we are at the end of the execution tree and an array
1506   * if a part of the process was automatically runned at the end of the transition. this array contains
1507   * 2 keys 'transition' is the transition we walked on, 'activity' is the result of the run part if it was an automatic activity.
1508   * 'activity' value is an associated array containing several usefull keys:
1509   * 'completed' is a boolean indicating that the activity was completed or not
1510   * 'debug contains debug messages
1511   * 'info' contains some usefull infos about the activity-instance running (like names)
1512   * 'next' is the result of a SendAutorouted part which could in fact be the result of a call to this function, etc
1513   * @access public
1514   */
1515  function sendTo($from,$activityId,$erase_from=true)
1516  {
1517    //we will use an array for return value
1518    $returned_data = Array();
1519    //1: if we are in a join check
1520    //if this instance is also in
1521    //other activity if so do
1522    //nothing
1523    $query = 'select wf_type, wf_name from '.GALAXIA_TABLE_PREFIX.'activities where wf_activity_id=?';
1524    $result = $this->query($query,array($activityId));
1525    if (empty($result))
1526    {
1527      $returned_data['transition']['failure'] = tra('Error: trying to send an instance to an activity but it was impossible to get this activity');
1528      return $returned_data;
1529    }
1530    while ($res = $result->fetchRow())
1531    {
1532      $type = $res['wf_type'];
1533      $targetname = $res['wf_name'];
1534    }
1535    $returned_data['transition']['target_id'] = $activityId;
1536    $returned_data['transition']['target_name'] = $targetname;
1537   
1538    // Verify the existence of a transition
1539    if(!$this->getOne("select count(*) from `".GALAXIA_TABLE_PREFIX."transitions` where `wf_act_from_id`=? and `wf_act_to_id`=?",array($from,(int)$activityId))) {
1540      $returned_data['transition']['failure'] = tra('Error: trying to send an instance to an activity but no transition found');
1541      return $returned_data;
1542    }
1543
1544    //init
1545    $putuser=0;
1546   
1547    //try to determine the user or *
1548    //Use the nextUser
1549    $the_next_user = $this->getNextUser($activityId);
1550    if($the_next_user)
1551    {
1552      //we check rights for this user on the next activity
1553      if (!(isset($this->security))) $this->security =& new WfSecurity($this->db);
1554      if ($this->security->checkUserAccess($the_next_user,$activityId))
1555      {
1556        $putuser = $the_next_user;
1557      }
1558    }
1559    if ($putuser===0)
1560    {
1561      // then check to see if there is a default user
1562      $activity_manager =& new ActivityManager($this->db);
1563      //get_default_user will give us '*' if there is no default_user or if the default user has no role
1564      //mapped anymore
1565      $default_user = $activity_manager->get_default_user($activityId,true);
1566      unset($activity_manager);
1567      // if they were no nextUser, no unique user avaible, no default_user then we'll have '*'
1568      // which will let user having the good role mapping grab this activity later
1569      $putuser = $default_user;
1570    }
1571    if ($the_next_user)
1572      $this->setNextUser($the_next_user, '*' . $activityId);
1573   
1574    //update the instance_activities table
1575    //if not splitting delete first
1576    //please update started,status,user
1577    if (($erase_from) && (!empty($this->instanceId)))
1578    {
1579      $query = "delete from `".GALAXIA_TABLE_PREFIX."instance_activities` where `wf_instance_id`=? and `wf_activity_id`=?";
1580      $this->query($query,array((int)$this->instanceId,$from));
1581    }
1582 
1583    if ($type == 'join') {
1584      if (count($this->activities)>1) {
1585        // This instance will have to wait!
1586        $returned_data['transition']['status'] = 'waiting';
1587        return $returned_data;
1588      }
1589    }   
1590
1591    //create the new instance-activity
1592    $returned_data['transition']['target_id'] = $activityId;
1593    $returned_data['transition']['target_name'] = $targetname;
1594    $now = date("U");
1595    $iid = $this->instanceId;
1596    $query="delete from `".GALAXIA_TABLE_PREFIX."instance_activities` where `wf_instance_id`=? and `wf_activity_id`=?";
1597    $this->query($query,array((int)$iid,(int)$activityId));
1598    $query="insert into `".GALAXIA_TABLE_PREFIX."instance_activities`(`wf_instance_id`,`wf_activity_id`,`wf_user`,`wf_status`,`wf_started`) values(?,?,?,?,?)";
1599    $this->query($query,array((int)$iid,(int)$activityId,$putuser,'running',(int)$now));
1600   
1601    //record the transition walk
1602    $returned_data['transition']['status'] = 'done';
1603
1604   
1605    //we are now in a new activity
1606    $this->_populate_activities($iid);
1607    //if the activity is not interactive then
1608    //execute the code for the activity and
1609    //complete the activity
1610    $isInteractive = $this->getOne("select `wf_is_interactive` from `".GALAXIA_TABLE_PREFIX."activities` where `wf_activity_id`=?",array((int)$activityId));
1611    if ($isInteractive=='n')
1612    {
1613      //first we sync actual instance because the next activity could need it
1614      if (!($this->sync()))
1615      {
1616        $returned_data['activity']['failure'] = true;
1617        return $returned_data;
1618      }
1619      // Now execute the code for the activity
1620      $this->activityID = $activityId;
1621      $returned_data['activity'] = $this->executeAutomaticActivity($activityId, $iid);
1622      $this->activityID = $from;
1623    }
1624    else
1625    {
1626      // we sync actual instance
1627      if (!($this->sync()))
1628      {
1629        $returned_data['failure'] = true;
1630        return $returned_data;
1631      }
1632    }
1633    return $returned_data;
1634  }
1635 
1636  /**
1637   * This is a public method only because the GUI can ask this action for the admin
1638   * on restart failed automated activities, but in fact it's quite an internal function,
1639   * This function handle the execution of automatic activities (and the launch of transitions
1640   * which can be related to this activity)
1641   *
1642   * @param int $activityId activity id at the end of the transition
1643   * @param int $iid instance id
1644   * @return array
1645   * @access public
1646   */
1647  function executeAutomaticActivity($activityId, $iid)
1648  {
1649    $returned_data = Array();
1650    // Now execute the code for the activity (function defined in galaxia's config.php)
1651    $returned_data =& galaxia_execute_activity($activityId, $iid , 1);
1652
1653    //we should have some info in $returned_data now. if it is false there's a problem
1654    if ((!(is_array($returned_data))) && (!($returned_data)) )
1655    {
1656      $this->error[] = tra('failed to execute automatic activity');
1657      //record the failure
1658      $returned_data['failure'] = true;
1659      return $returned_data;
1660    }
1661    else
1662    {
1663      //ok, we have an array, but it can still be a bad result
1664      //this one is just for debug info
1665      if (isset($returned_data['debug']))
1666      {
1667        //we retrieve this info here, in this object
1668        $this->error[] = $returned_data['debug'];
1669      }
1670      //and this really test if it worked, if not we have a nice failure message (better than just failure=true)
1671      if (isset($returned_data['failure']))
1672      {
1673        $this->error[] = tra('failed to execute automatic activity');
1674        $this->error[] = $returned_data['failure'];
1675        //record the failure
1676        return $returned_data;
1677      }
1678     
1679    }
1680    // Reload in case the activity did some change, last sync was done just before calling this function
1681    //TODO: check if this sync is really needed
1682    $this->getInstance($this->instanceId, false, false);
1683
1684    //complete the automatic activity----------------------------
1685    if ($this->Complete($activityId))
1686    {
1687      $returned_data['completed'] = true;
1688     
1689      //and send the next autorouted activity if any
1690      $returned_data['next'] = $this->sendAutorouted($activityId);
1691    }
1692    else
1693    {
1694      $returned_data['failure'] = $this->get_error();
1695    }
1696    return $returned_data;
1697  }
1698 
1699  /**
1700   * Gets a comment for this instance
1701   *
1702   * @param int $cId
1703   * @return mixed
1704   * @access public
1705   */
1706  function get_instance_comment($cId) {
1707    $iid = $this->instanceId;
1708    $query = "select * from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_instance_id`=? and `wf_c_id`=?";
1709    $result = $this->query($query,array((int)$iid,(int)$cId));
1710    $res = $result->fetchRow();
1711    return $res;
1712  }
1713 
1714  /**
1715   * Inserts or updates an instance comment
1716   *
1717   * @param int $cId
1718   * @param int $activityId
1719   * @param object $activity
1720   * @param int $user
1721   * @param string $title
1722   * @param mixed $comment
1723   * @return bool
1724   * @access public
1725   */
1726  function replace_instance_comment($cId, $activityId, $activity, $user, $title, $comment) {
1727    if (!$user) {
1728      $user = 'Anonymous';
1729    }
1730    $iid = $this->instanceId;
1731    //no need on pseudo-instance
1732    if (!!($this->instanceId))
1733    {
1734      if ($cId)
1735      {
1736        $query = "update `".GALAXIA_TABLE_PREFIX."instance_comments` set `wf_title`=?,`wf_comment`=? where `wf_instance_id`=? and `wf_c_id`=?";
1737        $this->query($query,array($title,$comment,(int)$iid,(int)$cId));
1738      }
1739      else
1740      {
1741        $hash = md5($title.$comment);
1742        if ($this->getOne("select count(*) from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_instance_id`=? and `wf_hash`=?",array($iid,$hash)))
1743        {
1744          return false;
1745        }
1746        $now = date("U");
1747        $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(?,?,?,?,?,?,?,?)";
1748        $this->query($query,array((int)$iid,$user,(int)$activityId,$activity,$title,$comment,(int)$now,$hash));
1749      }
1750    }
1751    return true;
1752  }
1753 
1754  /**
1755   * Removes an instance comment
1756   *
1757   * @param int $cId
1758   * @access public
1759   * @return void
1760   */
1761  function remove_instance_comment($cId) {
1762    $iid = $this->instanceId;
1763    $query = "delete from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_c_id`=? and `wf_instance_id`=?";
1764    $this->query($query,array((int)$cId,(int)$iid));
1765  }
1766 
1767  /**
1768   * Lists instance comments
1769   *
1770   * @return array
1771   * @access public
1772   */
1773  function get_instance_comments() {
1774    $iid = $this->instanceId;
1775    $query = "select * from `".GALAXIA_TABLE_PREFIX."instance_comments` where `wf_instance_id`=? order by ".$this->convert_sortmode("timestamp_desc");
1776    $result = $this->query($query,array((int)$iid));   
1777    $ret = Array();
1778    while($res = $result->fetchRow()) {   
1779      $ret[] = $res;
1780    }
1781    return $ret;
1782  }
1783
1784  /**
1785   * Get the activity candidates (more than one in split and switch)
1786   *
1787   * @param int $activityID The activity from which we want to obtain the candidate activities
1788   * @return array List of activities that can be reached from one given activity
1789   * @access public
1790   */
1791  function getActivityCandidates($activityID)
1792  {
1793    $query = 'SELECT wf_act_to_id FROM ' . GALAXIA_TABLE_PREFIX . 'transitions WHERE (wf_act_from_id = ?)';
1794    $result = $this->query($query, array((int) $activityID));
1795    $candidates = Array();
1796    while ($res = $result->fetchRow())
1797      $candidates[] = $res['wf_act_to_id'];
1798    return $candidates;
1799  }
1800}
1801?>
Note: See TracBrowser for help on using the repository browser.