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

Revision 3060, 57.3 KB checked in by viani, 11 years ago (diff)

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