source: sandbox/workflow/trunk/inc/engine/src/API/Instance.php @ 2886

Revision 2886, 57.4 KB checked in by viani, 14 years ago (diff)

Ticket #1089 - Merged 2859:2872 /sandbox/workflow/branches/1089/ em /sandbox/workflow/trunk/

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