Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
workflow_manager.inc
1 <?php
18 require_once SQ_FUDGE_PATH.'/general/datetime.inc';
19 
20 
32 {
33 
39  var $_valid_logic_with_right_operand = Array('>=');
40 
46  var $_valid_step_logic = Array('>=' => 'At least', 'all' => 'All');
47 
53  var $_valid_cond_logic = Array('>=' => 'At least', 'all' => 'All');
54 
55 
60  function Workflow_Manager()
61  {
62  $this->MySource_Object();
63 
64  }//end constructor
65 
74  function allowsWorkflow($assetid)
75  {
76  if (preg_match('/:/',$assetid)) return FALSE;
77  return TRUE;
78 
79  }//end allowsWorkflow()
80 
81 
96  function getSchemas($assetid, $granted=NULL, $running=FALSE, $cascades=NULL)
97  {
98  $storage_access = ((is_null($granted)) ? 2 : (int) $granted);
99  if (!isset($this->_tmp['schemas'][(int)$assetid][$storage_access][(int)$running])) {
100 
101  $sql = ' SELECT DISTINCT schemaid, granted, cascades
102  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_wflow ';
103  $where = 'assetid = :assetid';
104  if ($running) {
105  if (MatrixDAL::getDbType() === 'oci') {
106  $where .= ' AND DBMS_LOB.GETLENGTH(wflow) > 0';
107  } else {
108  $where .= ' AND wflow IS NOT NULL';
109  }
110  }
111  if (!is_null($granted) || $running) {
112  $where .= ' AND granted = :granted';
113  }
114  if (!is_null($cascades)) {
115  $where .= ' AND cascades = :cascades';
116  }
117  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
118 
119  try {
120  $query = MatrixDAL::preparePdoQuery($sql.$where);
121  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid, PDO::PARAM_STR);
122  if (!is_null($granted) || $running) {
123  if ($running) {
124  $granted_bind = '1';
125  } else {
126  $granted_bind = $granted ? '1' : '0';
127  }
128  MatrixDAL::bindValueToPdo($query, 'granted', $granted_bind, PDO::PARAM_STR);
129  }
130  if (!is_null($cascades)) {
131  MatrixDAL::bindValueToPdo($query, 'cascades', $cascades ? '1' : '0', PDO::PARAM_STR);
132  }
133  $result = MatrixDAL::executePdoAll($query);
134  } catch (Exception $e) {
135  throw new Exception('Unable to get workflow schemas for asset ID #'.$assetid.' due to database error: '.$e->getMessage());
136  }
137 
138  $schemas = Array();
139  foreach ($result as $data) {
140  if (is_null($granted)) {
141  $schemas[$data['schemaid']] = $data['granted'];
142  } else {
143  $schemas[] = $data['schemaid'];
144  }
145  }
146 
147  $this->_tmp['schemas'][(int)$assetid][$storage_access][(int)$running] = $schemas;
148  }//end if
149 
150  return $this->_tmp['schemas'][(int)$assetid][$storage_access][(int)$running];
151 
152  }//end getSchemas()
153 
154 
169  function getAssetSchemaInfo($assetid, $schemaid=NULL, $cascades=NULL, $include_cascades=TRUE, $include_last_started_by=FALSE)
170  {
171  $sql = ' SELECT DISTINCT schemaid, granted, cascades, last_started_by
172  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_wflow ';
173  $where = 'assetid = :assetid';
174  if (!is_null($schemaid)) {
175  $where .= ' AND schemaid = :schemaid';
176  }
177  if (!is_null($cascades)) {
178  $where .= ' AND cascades = :cascades';
179  }
180  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
181 
182  try {
183  $query = MatrixDAL::preparePdoQuery($sql.$where);
184  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid, PDO::PARAM_STR);
185  if (!is_null($schemaid)) {
186  MatrixDAL::bindValueToPdo($query, 'schemaid', (int)$schemaid, PDO::PARAM_INT);
187  }
188  if (!is_null($cascades)) {
189  MatrixDAL::bindValueToPdo($query, 'cascades', $cascades ? '1' : '0', PDO::PARAM_STR);
190  }
191  $result = MatrixDAL::executePdoAssoc($query);
192  } catch (Exception $e) {
193  throw new Exception('Unable to get asset workflow schemas of asset ID #'.$assetid.' due to database error: '.$e->getMessage());
194  }
195 
196  $schemas = Array();
197  foreach ($result as $data) {
198  if ($include_cascades || $include_last_started_by) {
199  $schemas[$data['schemaid']] = Array(
200  'granted' => $data['granted'],
201  );
202  if ($include_cascades) $schemas[$data['schemaid']]['cascades'] = $data['cascades'];
203  if ($include_last_started_by) $schemas[$data['schemaid']]['last_started_by'] = $data['last_started_by'];
204 
205  } else {
206  $schemas[$data['schemaid']] = $data['granted'];
207  }
208  }
209 
210  // If a schemaid is specified, flatten out array
211  if (!is_null($schemaid)) {
212  if (array_key_exists($schemaid, $schemas)) {
213  $schemas = $schemas[$schemaid];
214  } else {
215  $schemas = Array();
216  }
217  }
218 
219  return $schemas;
220 
221  }//end getAssetSchemaInfo()
222 
223 
235  function setSchema($assetid, $schemaid, $granted, $cascades=TRUE, $force_set=FALSE)
236  {
237  $assetid = (int) $assetid;
238  $schemaid = (int) $schemaid;
239  $granted = (bool) $granted;
240  $db_action = 'insert';
241 
242  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid);
243  if (is_null($schema)) {
244  trigger_localised_error('SYS0175', E_USER_WARNING, $schemaid);
245  return FALSE;
246  }
247 
248  if (!($schema instanceof Workflow_Schema)) {
249  trigger_localised_error('SYS0173', E_USER_WARNING, $schema->name, $schemaid);
250  return FALSE;
251  }
252 
253  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
254  if (is_null($asset)) {
255  trigger_localised_error('SYS0174', E_USER_WARNING, $assetid);
256  return FALSE;
257  }
258 
259  if (!$asset->adminAccess('workflow')) {
260  trigger_localised_error('SYS0117', E_USER_WARNING, $asset->name);
261  return FALSE;
262  }
263 
264  // check for any running schemas - because if the asset is in workflow we cant do anything
265  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
266  if (!empty($running_schemas)) {
267  trigger_localised_error('SYS0118', E_USER_WARNING, $asset->name);
268  return FALSE;
269  }
270 
271  // get the current schemas that are set
272  $schema_info = $this->getAssetSchemaInfo($assetid, $schemaid);
273 
274  // check if this schema is already set
275  if (!empty($schema_info)) {
276 
277  if ((bool)$schema_info['granted'] == $granted) {
278  // schema is set with same access level
279  if ((bool)$schema_info['cascades'] == $cascades) {
280  // same cascade level too, so no update needed
281  return TRUE;
282  } else {
283  // same access level, but different cascade, so update it
284  $db_action = 'update';
285  }
286  } else {
287  if ($force_set) {
288  $db_action = 'update';
289  } else {
290  // schema is set but with the opposite access level
291  $new_access = ($granted) ? 'apply' : 'deny';
292  $current_access = ($granted) ? 'denied' : 'applied';
293 
294  trigger_localised_error('SYS0124', E_USER_WARNING, $new_access, $schema->name, $asset->name, $current_access);
295  return FALSE;
296  }
297  }
298  }
299 
300  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
301  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
302 
303  if ($db_action == 'insert') {
304  $query_name = 'setWorkflowSchema';
305  } else if ($db_action == 'update') {
306  $query_name = 'updateWorkflowSchema';
307  }
308 
309  try {
310  $bind_vars = Array(
311  'assetid' => (string)$assetid,
312  'schemaid' => (string)$schemaid,
313  'wflow' => NULL,
314  'granted' => $granted ? '1' : '0',
315  'cascades' => $cascades ? '1' : '0',
316  );
317  MatrixDAL::executeQuery('core', $query_name, $bind_vars);
318  } catch (DALException $e) {
319  throw new Exception ('Unable to set workflow schema for "'.$asset->name.'" (#'.$asset->id.') due to database error: '.$e->getMessage());
320  }
321 
322  unset($this->_tmp['schemas'][(int)$assetid]);
323  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
324  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
325 
326  return TRUE;
327 
328  }//end setSchema()
329 
330 
341  function deleteSchema($assetid, $schemaid, $running = TRUE)
342  {
343  $assetid = (int) $assetid;
344  $schemaid = (int) $schemaid;
345 
346  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
347  if (is_null($asset)) {
348  trigger_localised_error('SYS0153', E_USER_WARNING, $assetid);
349  return FALSE;
350  }
351 
352  if (!$asset->adminAccess('workflow')) {
353  trigger_localised_error('SYS0105', E_USER_WARNING, $asset->name);
354  return FALSE;
355  }
356 
357  if ($running) {
358  // check for any running schemas - because if the asset is in workflow we cant do anything
359  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
360  if (!empty($running_schemas)) {
361  trigger_localised_error('SYS0106', E_USER_WARNING, $asset->name);
362  return FALSE;
363  }
364  }
365 
366  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
367  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
368 
369  try {
370  $bind_vars = Array(
371  'assetid' => (string)$asset->id,
372  'schemaid' => (string)$schemaid,
373  );
374  MatrixDAL::executeQuery('core', 'deleteWorkflowSchema', $bind_vars);
375  } catch (DALException $e) {
376  throw new Exception ('Unable to delete workflow schema for "'.$asset->name.'" (#'.$asset->id.') due to database error: '.$e->getMessage());
377  }
378 
379  unset($this->_tmp['schemas'][(int)$assetid]);
380  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
381  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
382 
383  return TRUE;
384 
385  }//end deleteSchema()
386 
387 
400  function getSchemaWorkflows($assetid, $schemaid='all')
401  {
402  $assetid = (int) $assetid;
403 
404  if ($schemaid != 'all') {
405  // we are looking for a specific schema, but we might already have
406  // the information we need in the 'all' array
407  if (isset($this->_tmp['schema_workflows'][$assetid]['all'][$schemaid])) {
408  $this->_tmp['schema_workflows'][$assetid][$schemaid] = $this->_tmp['schema_workflows'][$assetid]['all'][$schemaid];
409  }
410  }
411 
412  if (!isset($this->_tmp['schema_workflows'][$assetid][$schemaid])) {
413  $workflows = Array();
414  $bind_vars = Array();
415 
416  $sql = 'SELECT
417  schemaid, wflow
418  FROM
419  '.SQ_TABLE_RUNNING_PREFIX.'ast_wflow ';
420 
421  $where = 'assetid = :assetid
422  AND granted = \'1\'';
423  $bind_vars['assetid'] = $assetid;
424 
425  if ($schemaid != 'all') {
426  $where .= ' AND schemaid = :schemaid';
427  $bind_vars['schemaid'] = $schemaid;
428  }
429  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
430 
431  try {
432  $query = MatrixDAL::preparePdoQuery($sql.$where);
433  foreach ($bind_vars as $bind_var => $bind_value) {
434  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
435  }
436  $results = MatrixDAL::executePdoGroupedAssoc($query);
437  } catch (Exception $e) {
438  throw new Exception('Unable to get workflow schema(s) for asset: '.$assetid.' due to database error: '.$e->getMessage());
439  }
440 
441  foreach ($results as $assetid => $serialized_wflow) {
442  $workflows[$assetid] = (empty($serialized_wflow[0]['wflow']) ? '' : unserialize($serialized_wflow[0]['wflow']));
443  }
444 
445  if ($schemaid == 'all') {
446  $this->_tmp['schema_workflows'][$assetid]['all'] = $workflows;
447  } else {
448  $this->_tmp['schema_workflows'][$assetid][$schemaid] = $workflows[$schemaid];
449  }
450  }//end if
451 
452  if (!isset($this->_tmp['schema_workflows'][$assetid][$schemaid])) {
453  return Array();
454  }
455 
456  return $this->_tmp['schema_workflows'][$assetid][$schemaid];
457 
458  }//end getSchemaWorkflows()
459 
460 
470  function setWorkflow($assetid, $workflow)
471  {
472  $assetid = (int) $assetid;
473 
474  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
475  if (is_null($asset)) {
476  trigger_localised_error('SYS0177', E_USER_WARNING, $assetid);
477  return FALSE;
478  }
479 
480  if (!$asset->adminAccess('workflow')) {
481  trigger_localised_error('SYS0117', E_USER_WARNING, $asset->name);
482  return FALSE;
483  }
484 
485  $schemas = $this->getSchemas($asset->id, TRUE);
486  if (empty($schemas)) return FALSE;
487 
488  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
489  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
490 
491  foreach ($schemas as $schemaid) {
492  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid);
493  if (is_null($schema)) {
494  trigger_localised_error('SYS0174', E_USER_WARNING, $schemaid);
495  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
496  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
497  return FALSE;
498  }
499 
500  if (!($schema instanceof Workflow_Schema)) {
501  trigger_localised_error('SYS0176', E_USER_WARNING, $schema->name, $schemaid);
502  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
503  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
504  return FALSE;
505  }
506 
507  if (!isset($workflow[$schema->name])) continue;
508 
509  try {
510  $bind_vars = Array(
511  'wflow' => serialize($workflow[$schema->name]),
512  'assetid' => $asset->id,
513  'schemaid' => $schema->id,
514  );
515  MatrixDAL::executeQuery('core', 'updateWorkflow', $bind_vars);
516  } catch (Exception $e) {
517  throw new Exception('Unable to update workflow table for asset: '.$assetid.' due to database error: '.$e->getMessage());
518  }
519 
520  $this->_tmp['schema_workflows'][$assetid][$schemaid] = $workflow[$schema->name];
521  }
522 
523  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
524  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
525  return TRUE;
526 
527  }//end setWorkflow()
528 
529 
543  function whoCanPublish($assetid, $schemaid=NULL, $group=FALSE)
544  {
545  // if there are no current workflows running, we are not waiting for anyone
546  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
547  if (empty($running_schemas)) return Array();
548 
549  // these will be the people who can approve this asset
550  // at this point in the workflow
551  $can_publish = Array();
552 
553  foreach ($running_schemas as $sid) {
554  if (!is_null($schemaid) && $sid != $schemaid) {
555  continue;
556  }
557  if ($group) {
558  $can_publish[$sid] = $this->_whoCanPublishWorkflow($assetid, $sid);
559  $can_publish[$sid] = array_unique($can_publish[$sid]);
560  } else {
561  $can_publish = array_merge($can_publish, $this->_whoCanPublishWorkflow($assetid, $sid));
562  }
563  }
564 
565  if (!$group) $can_publish = array_unique($can_publish);
566  return $can_publish;
567 
568  }//end whoCanPublish()
569 
570 
580  function notifyOnLive($assetid, $old_status)
581  {
582  // we dont have to notify people for silent workflow assets
583  if ($this->silentWorkflowParty($assetid)) return TRUE;
584 
585  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
586  if (is_null($asset)) {
587  trigger_localised_error('SYS0180', E_USER_WARNING, $assetid);
588  return FALSE;
589  }
590 
591  // if there are no workflows, we dont notify anyone
592  $schemas = $this->getAssetSchemaInfo($assetid, NULL, NULL, FALSE, TRUE);
593  if (empty($schemas)) return TRUE;
594 
595  foreach ($schemas as $schemaid => $schema_info) {
596  if (!$schema_info['granted']) continue;
597 
598  // these will be the people who to notify (some may be groups)
599  $notify = Array();
600 
601  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
602  if ($schema && $schema->attr('notify_starter_on_live')){
603  if (!empty($schema_info['last_started_by'])){
604  $started_by_user = $GLOBALS['SQ_SYSTEM']->am->getAsset($schema_info['last_started_by'], '', TRUE);
605  if ($started_by_user){
606  // see bug #4986 Edit User doesnt get workflow emails initiated via EES
607  // and see simple_edit_user::canAccessBackend()
608  if ($started_by_user instanceof Simple_Edit_User) $started_by_user->_tmp['starter_of_workflow'] = TRUE;
609  $notify[] = $schema_info['last_started_by'];
610  }
611  }
612  }
613 
614  $conditions = $GLOBALS['SQ_SYSTEM']->am->getChildren($schemaid, 'workflow_step_condition');
615  foreach ($conditions as $cond_id => $type_code) {
616  $condition = $GLOBALS['SQ_SYSTEM']->am->getAsset($cond_id, 'workflow_step_condition');
617  if (is_null($condition)) continue;
618  if ($condition->attr('notify')) {
619  // check if this is a roleid rather than a user_groupid or userid
620  if (SQ_CONF_ENABLE_ROLES_WF_SYSTEM == '1') {
621  $fetch_global_roles = (SQ_CONF_ENABLE_GLOBAL_ROLES == '1');
622  $role = $GLOBALS['SQ_SYSTEM']->am->getRole($assetid, $condition->attr('userid'), NULL, FALSE, $fetch_global_roles);
623  if (!empty($role)) {
624  foreach ($role as $roleid => $userids) {
625  for ($i=0; $i<count($userids); $i++) {
626  $notify[] = $userids[$i];
627  }
628  }
629  } else {
630  // the user or group in this condition wants to be notified
631  $notify[] = $condition->attr('userid');
632  }
633  } else {
634  // the user or group in this condition wants to be notified
635  $notify[] = $condition->attr('userid');
636  }
637  }
638  }//end foreach
639 
640  if (!empty($notify)) {
641  $notify = array_unique($notify);
642  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
643  $msg = $ms->newMessage($notify);
644  $msg->type = 'asset.status.notify';
645  $msg_reps = Array(
646  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
647  'asset_name' => $asset->name,
648  'status' => get_status_description($old_status),
649  );
650 
651  // create a link to the preview screen, or to the details screen if preview doesn't exist
652  $asset_edt_fns = $asset->getEditFns();
653  if (isset($asset_edt_fns->static_screens['preview'])) {
654  $msg_reps['asset_url'] = current_url().$asset->getBackendHref('preview', FALSE);
655  } else {
656  $msg_reps['asset_url'] = current_url().$asset->getBackendHref('details', FALSE);
657  }
658 
659  $msg->replacements = $msg_reps;
660 
661  if ($schema){
662  // Does this schema have a custom message?
663  if (trim($schema->attr('message_notify_on_live')) !== '' ) {
664  $msg->body = trim($schema->attr('message_notify_on_live'));
665  }//end if
666 
667  // Does this schema have a custom subject?
668  if (trim($schema->attr('subject_notify_on_live')) !== '') {
669  $subject = trim($schema->attr('subject_notify_on_live'));
670  $msg->subject = (!empty($subject)) ? $subject : 'Asset Made Live';
671  }//end if
672 
673  if ($schema->attr('schema_reply_to_email_address') != '') {
674  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
675  } else {
676  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
677  }
678  if ($schema->attr('schema_from_email_address') != '') {
679  $msg->from = $schema->attr('schema_from_email_address');
680  }
681  }//end if
682 
683  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
684 
685  $msg->parameters['assetid'] = $asset->id;
686  $ms->enqueueMessage($msg);
687  }
688  }//end foreach
689 
690  return TRUE;
691 
692  }//end notifyOnLive()
693 
694 
704  function requiresComment($assetid, $userid)
705  {
706  if ($this->silentWorkflowParty($assetid)) return FALSE;
707 
708  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
709  if (is_null($asset)) {
710  // TODO: Check error
711  trigger_localised_error('SYS0180', E_USER_WARNING, $assetid);
712  return FALSE;
713  }
714 
715  // if there are no workflows, we can't require comments
716  $schemas = $this->getSchemas($assetid, TRUE);
717  if (empty($schemas)) return FALSE;
718 
719  $schema_workflows = $this->getSchemaWorkflows($assetid);
720  $current_workflow_steps = $this->getWorkflowCurrentSteps($assetid);
721 
722  foreach ($schemas as $schemaid) {
723  // nothing in this schema, try the next
724  if (empty($schema_workflows[$schemaid])) continue;
725 
726  // check to see if the step exists
727  if (empty($current_workflow_steps[$schemaid])) {
728  continue;
729  }
730 
731  // get the current step for this schema
732  $current_step =& $this->getCurrentStep($schema_workflows[$schemaid]);
733  // is the user listed in the conditions?
734  $listed_user = FALSE;
735 
736  // loop through the conditions for the current ste
737  $condition_type_info = $GLOBALS['SQ_SYSTEM']->am->getAssetTypeInfo(array_keys($current_step['conditions']));
738  foreach ($current_step['conditions'] as $conditionid => $condition_details) {
739  // is a user_group
740  if (in_array('user_group', $condition_type_info[$conditionid])) {
741  $group_users = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($conditionid, Array('user'), FALSE, NULL, NULL, NULL, TRUE, NULL, NULL, FALSE, NULL, Array(), TRUE));
742  if (in_array($userid, $group_users)) {
743  $listed_user = TRUE;
744  }
745  } else {
746  // assume user
747  if ($conditionid == $userid) $listed_user = TRUE;
748  }
749 
750  if (array_get_index($condition_details, 'require_comment', FALSE)) {
751  // no point going through the rest if we know we need to comment
752  return TRUE;
753  }
754 
755  }
756 
757  // if the user wasn't listed, then check if unlisted users need to comment
758  // if so, return: there's no point checking the rest
759  if (!$listed_user && array_get_index($current_step, 'require_comment_from_unlisted_users', FALSE)) {
760  return TRUE;
761  }
762  }//end foreach
763 
764  return FALSE;
765 
766  }//end requiresComment()
767 
768 
781  function _whoCanPublishWorkflow($assetid, $schemaid)
782  {
783  $am = $GLOBALS['SQ_SYSTEM']->am;
784  // these will be the people who can approve this asset at this point in the workflow
785  $can_publish = Array();
786  $workflow = $this->getSchemaWorkflows($assetid, $schemaid);
787  if (!isset($workflow['steps'])) return Array();
788 
789  $step_data =& $this->getCurrentStep($workflow);
790  if (empty($step_data)) return Array();
791 
792  for (reset($step_data['conditions']);
793  NULL !== ($pub = key($step_data['conditions'])); next($step_data['conditions'])) {
794  $cond_data =& $step_data['conditions'][$pub];
795 
796  // safety code
797  if (!isset($cond_data['complete'])) {
798  $cond_data['complete'] = FALSE;
799  }
800  if (!isset($cond_data['published_by'])) {
801  $cond_data['published_by'] = Array();
802  }
803 
804  if ($cond_data['complete']) {
805  $complete = TRUE;
806  } else {
807  $complete = FALSE;
808  $can_publish_cond = Array();
809 
810  // work out who can publish right now
811  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($pub);
812  if ($asset->id) {
813  $asset_class = get_class($asset);
814  // Note that the order of IF statements is important, because roles are
815  // a descendent of groups but should be treated differently
816  if ($am->isTypeDecendant($asset_class, 'user')) {
817  $can_publish_cond[] = $asset->id;
818  } else if ($am->isTypeDecendant($asset_class, 'role')) {
819  // expand global users
820  if (SQ_CONF_ENABLE_ROLES_WF_SYSTEM == '1') {
821  $fetch_global_roles = (SQ_CONF_ENABLE_GLOBAL_ROLES == '1');
822  $roles = $GLOBALS['SQ_SYSTEM']->am->getRole($assetid, $asset->id, NULL, FALSE, TRUE);
823  foreach ($roles as $roleid => $userids) {
824  foreach ($userids as $userid) {
825  $info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo(Array($userid), Array(), FALSE, 'type_code');
826  if ($GLOBALS['SQ_SYSTEM']->am->isTypeDecendant($info[$userid], 'user_group')) {
827  $can_publish_cond = array_merge($can_publish_cond, array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($userid, Array('user'), FALSE, NULL, NULL, NULL, TRUE, NULL, NULL, FALSE, NULL, Array(), TRUE)));
828  } else {
829  $can_publish_cond[] = $userid;
830  }
831  }
832  }
833  }
834  } else if ($am->isTypeDecendant($asset_class, 'user_group')) {
835  // getChildren with all the shadow asset children
836  $can_publish_cond = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($asset->id, Array('user'), FALSE, NULL, NULL, NULL, TRUE, NULL, NULL, FALSE, NULL, Array(), TRUE));
837  }
838  }
839 
840  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
841 
842  if ($cond_data['logic'] == 'all') {
843  $cond_string = 'if ('.count($cond_data['published_by']).
844  ' >= '.count($can_publish_cond).') { $complete = true; }';
845  } else {
846  $cond_string = 'if ('.count($cond_data['published_by']).
847  ' '.$cond_data['logic'].') { $complete = true; }';
848  }
849  eval($cond_string);
850 
851  }//end else
852 
853  if (!$complete) {
854  $can_publish = array_merge($can_publish, array_diff($can_publish_cond, array_keys($cond_data['published_by'])));
855  }
856  }//end for
857 
858  return array_unique($can_publish);
859 
860 
861  }//end _whoCanPublishWorkflow()
862 
863 
872  function _loadCurrentStep(&$workflow)
873  {
874  // run through and work out what step we are up to
875  $workflow['current_step'] = Array();
876 
877  $current_step_found = FALSE;
878  $step_array =& $workflow['steps'];
879 
880  while (!$current_step_found) {
881  $current_step_found = TRUE;
882  for (reset($step_array); NULL !== ($stepid = key($step_array)); next($step_array)) {
883  $step_data =& $step_array[$stepid];
884 
885  // 'load' the current step
886 
887  $step_keys = array_keys($workflow['current_step']);
888  $last_key = end($step_keys);
889  $workflow['current_step'][$last_key] = $stepid;
890 
891  if ($step_data['expired']) {
892  if ($step_data['completed']) {
893 
894  // expired and complete means that it expired, but was completed by virtue of its escalation workflow being completed
895  $current_step_found = FALSE;
896  continue;
897  } else {
898  // we need to move to this asset's substeps if there are any, otherwise to the sibling.
899  $step_array =& $step_data['escalation_steps'];
900 
901  // add an element to the current step array cos we're going deeper.
902  $workflow['current_step'][] = $stepid;
903  $current_step_found = FALSE;
904 
905  // make the new asset the step array and keep running
906  continue(2);
907  }
908  }
909 
910  $current_step_found = TRUE;
911  $completed_conds = 0;
912 
913  for (reset($step_data['conditions']);
914  NULL !== ($pub = key($step_data['conditions'])); next($step_data['conditions'])) {
915  $current_step_found = TRUE;
916  $cond_data =& $step_data['conditions'][$pub];
917 
918  // safety code
919  if (!isset($cond_data['complete'])) {
920  $cond_data['complete'] = FALSE;
921  }
922  if (!isset($cond_data['published_by'])) {
923  $cond_data['published_by'] = Array();
924  }
925 
926  if (empty($cond_data['published_by'])) {
927  // nobody has approved this, so it cant be finished
928  if ($step_data['logic'] == 'all') {
929  $current_step_found = TRUE;
930  break(2);
931  } else {
932  continue;
933  }
934  }
935  $complete = FALSE;
936 
937  if ($cond_data['complete']) {
938  $current_step_found = FALSE;
939  $complete = TRUE;
940  } else {
941 
942  $current_step_found = TRUE;
943  if ($cond_data['logic'] == 'all') {
944  // lets work out what number 'ALL' represents
945  $can_publish_cond = Array();
946  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($pub);
947  if ($asset->id) {
948  if ($asset instanceof User) {
949  $can_publish_cond[] = $asset->id;
950  } else if ($asset instanceof User_Group) {
951  $can_publish_cond = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($asset->id, Array('user'), FALSE, NULL, NULL, NULL, TRUE, NULL, NULL, FALSE, NULL, Array(), TRUE));
952  }
953  }
954  $cond_string = 'if ('.count($cond_data['published_by']).
955  ' >= '.count($can_publish_cond).') { $complete = true; }';
956  } else {
957  $cond_string = 'if ('.count($cond_data['published_by']).
958  ' '.$cond_data['logic'].') { $complete = true; }';
959  }
960  eval($cond_string);
961  }
962 
963  if ($complete) {
964  // this condition has been completed
965  $cond_data['complete'] = TRUE;
966  $current_step_found = FALSE;
967  $completed_conds++;
968  if ($step_data['logic'] == 'all') continue;
969 
970  $step_completed = FALSE;
971  $logic_string = 'if ('.$completed_conds.' '.$step_data['logic'].' ) { $step_completed = true; }';
972  eval($logic_string);
973 
974  if ($step_completed) {
975  // this step is completed so move to the next one
976  break;
977  } else {
978  // step is not finished yet so move to the next condition
979  $current_step_found = TRUE;
980  continue;
981  }
982  }
983  }//end for
984 
985  // is the current step complete? If not, we are up to this step and can return
986 
987  if ($step_data['logic'] == 'all' && $completed_conds < count($step_data['conditions'])) {
988  $current_step_found = TRUE;
989  }
990  if ($current_step_found) break;
991  }//end for
992 
993 
994  // we've been through all the siblings at the current level, and they're all complete.
995  $step_keys = array_keys($workflow['current_step']);
996  $last_key = end($step_keys);
997  if ($workflow['current_step'][$last_key] == count($step_array) && $current_step_found == FALSE) {
998 
999  // if we're at the top level of workflow, we can't unescalate, we're cooked
1000  if (count($workflow['current_step']) == 1) {
1001  $workflow['current_step'] = Array();
1002  $current_step_found = TRUE;
1003  continue;
1004  }
1005 
1006  // get the index of the current step at this level. We should be at the bottom level, so take the last token from current_step.
1007 
1008  $completed_step_id = array_pop($workflow['current_step']);
1009 
1010  $step_array[$completed_step_id]['completed'] = time();
1011 
1012 
1013  // ideally we don't want to set complete and start times for steps in here, but we
1014  // need to indicate somehow that we've just checked out the escalation workflow and it's all done
1015  $step_array =& $this->getCurrentStepArray($workflow);
1016  $current_step_id = end($workflow['current_step']);
1017  $step_array[$current_step_id]['completed'] = time();
1018 
1019  }
1020  }//end while !current step found
1021 
1022  }//end _loadCurrentStep()
1023 
1024 
1034  function recordPublish($assetid, $publisher)
1035  {
1036  $assetid = (int) $assetid;
1037  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1038  if (is_null($asset)) {
1039  trigger_localised_error('SYS0160', E_USER_WARNING, $assetid);
1040  return FALSE;
1041  }
1042 
1043  // if there are no current workflows running, how can someone publish (you do the math)?
1044  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
1045  if (empty($running_schemas)) return FALSE;
1046  $schema_workflows = $this->getSchemaWorkflows($assetid);
1047 
1048  // we'll need these for later to check if any workflow has changed steps
1049  if (!$this->silentWorkflowParty($asset->id)) {
1050  $schema_publishers_before = Array();
1051  $steps_before = $this->getWorkflowCurrentSteps($asset->id);
1052  foreach ($running_schemas as $schemaid) {
1053  $schema_publishers_before[$schemaid] = $this->whoCanPublish($asset->id, $schemaid);
1054  }
1055  }
1056 
1057  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1058  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1059 
1060  for (reset($schema_workflows); NULL !== ($schemaid = key($schema_workflows)); next($schema_workflows)) {
1061  if (!in_array($schemaid, $running_schemas)) continue;
1062  $workflow =& $schema_workflows[$schemaid];
1063  if ($workflow['complete'] == TRUE) continue;
1064  if ($this->_recordPublish($workflow, $publisher, $assetid)) {
1065  $new_workflow = serialize($workflow);
1066 
1067  try {
1068  $bind_vars = Array(
1069  'wflow' => $new_workflow,
1070  'assetid' => $assetid,
1071  'schemaid' => $schemaid,
1072  );
1073  MatrixDAL::executeQuery('core', 'updateWorkflow', $bind_vars);
1074  } catch (Exception $e) {
1075  throw new Exception('Unable to update workflow table for asset: '.$assetid.' due to database error: '.$e->getMessage());
1076  }
1077 
1078  // updated cached version of this workflow
1079  $this->_tmp['schema_workflows'][$assetid][$schemaid] = $workflow;
1080  if (isset($this->_tmp['schema_workflows'][$assetid]['all'][$schemaid])) {
1081  $this->_tmp['schema_workflows'][$assetid]['all'][$schemaid] = $workflow;
1082  }
1083  }
1084  }
1085 
1086  // send an internal message to let people know someone has approved
1087  $asset_type = $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name');
1088  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($GLOBALS['SQ_SYSTEM']->currentUserId());
1089  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
1090 
1091  // workflow logs since the start of this workflow
1092  $log_message = '';
1093  $comments = $ms->getMessages(0, 'asset.workflow.userlog', Array(), Array(), $workflow['started'], NULL, 'name', Array('assetid' => $asset->id));
1094  foreach ($comments as $comment) {
1095  $log_message .= '"'.$comment['body'].'"'.', by '.$comment['from_name'].', '.ts_iso8601($comment['sent'])."\n";
1096  }
1097 
1098  $log = $ms->newMessage();
1099  $msg_reps = Array(
1100  'workflow_user' => $user->name,
1101  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
1102  'asset_name' => $asset->name,
1103  'assetid' => $asset->id,
1104  'workflow_url' => current_url().$asset->getBackendHref('workflow', FALSE),
1105  'asset_url' => $asset->getDependantParentsURL(),
1106  'asset_version' => $asset->version,
1107  'log_message' => $log_message,
1108  );
1109  $log->replacements = $msg_reps;
1110  $log->type = 'asset.workflow.log.approve';
1111  $log->parameters['assetid'] = $asset->id;
1112  $log->parameters['version'] = substr($asset->version, 0, strrpos($asset->version, '.'));
1113  $ms->enqueueMessage($log);
1114 
1115  if (!$this->silentWorkflowParty($asset->id)) {
1116  // send internal messages to everyone who could approve before to let them know that someone did
1117  $base_msg = $ms->newMessage();
1118  $base_msg->type = 'asset.workflow.announce.approve';
1119  $base_msg->parameters['assetid'] = $asset->id;
1120  $base_msg->replacements = $msg_reps;
1121 
1122  // Send custom messages to anything that the publisher is in the workflow of
1123  $generic_publishers_before = Array();
1124  foreach ($schema_publishers_before as $schemaid => $publishers_step) {
1125  $workflow =& $schema_workflows[$schemaid];
1126  $current_step = $this->getCurrentStep($workflow, $steps_before[$schemaid]);
1127  if (in_array($publisher, $publishers_step)) {
1128  // Does this step have a custom message?
1129  if (isset($current_step['message_approve']) && trim($current_step['message_approve']) !== '' ) {
1130  $msg = clone $base_msg;
1131  $msg->to = $publishers_step;
1132  // see if we have the 'from' field set
1133  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
1134  if ($schema->attr('schema_reply_to_email_address') != '') {
1135  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
1136  } else {
1137  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
1138  }
1139  if ($schema->attr('schema_from_email_address') != '') {
1140  $msg->from = $schema->attr('schema_from_email_address');
1141  }
1142 
1143  $subject = trim(array_get_index($current_step, 'subject_approve', 'Asset Changes Approved'));
1144  $msg->subject = (!empty($subject)) ? $subject : 'Asset Changes Approved';
1145  $msg->body = trim($current_step['message_approve']);
1146  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
1147  $ms->enqueueMessage($msg);
1148  } else if (isset($current_step['subject_approve']) && trim($current_step['subject_approve']) !== '') {
1149  $msg = clone $base_msg;
1150  $msg->to = $publishers_step;
1151  // see if we have the 'from' field set
1152  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
1153  if ($schema->attr('schema_reply_to_email_address') != '') {
1154  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
1155  } else {
1156  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
1157  }
1158  if ($schema->attr('schema_from_email_address') != '') {
1159  $msg->from = $schema->attr('schema_from_email_address');
1160  }
1161 
1162  $subject = trim(array_get_index($current_step, 'subject_approve', 'Asset Changes Approved'));
1163  $msg->subject = (!empty($subject)) ? $subject : 'Asset Changes Approved';
1164  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
1165  $ms->enqueueMessage($msg);
1166  } else {
1167  $generic_publishers_before = array_merge($generic_publishers_before, $publishers_step);
1168  }
1169  } else {
1170  $generic_publishers_before = array_merge($generic_publishers_before, $publishers_step);
1171  }
1172  }
1173 
1174  // Any other current publishers where the publisher is out of the workflow,
1175  // or was in the workflow but there is no custom message?
1176  // Send a generic workflow approval message in this case.
1177  if (count($generic_publishers_before) > 0) {
1178  $msg = clone $base_msg;
1179  $msg->to = $generic_publishers_before;
1180 
1181  $ms->enqueueMessage($msg);
1182  }
1183 
1184  // fire the 'Workflow Approval' event
1185  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_workflow_approval', $asset);
1186  }
1187 
1188  // now check if any workflows have progressed to the next step
1189  // and send messages to the new approvers if they have
1190  if (!$this->silentWorkflowParty($asset->id)) {
1191  $steps_after = $this->getWorkflowCurrentSteps($asset->id);
1192  foreach ($steps_after as $schemaid => $step) {
1193  // Are there actually no steps left?
1194  if (empty($step)) continue;
1195 
1196  if (!($step == $steps_before[$schemaid])) {
1197  $publishers = $this->whoCanPublish($asset->id, $schemaid);
1198 
1199  // Bug fix #2636, add the slash to the URL in case the trigger fire this chain of action.
1200  $workflow_backend_href = $asset->getBackendHref('workflow', FALSE);
1201  if (!empty($workflow_backend_href) && substr($workflow_backend_href, 0, 2) != './') {
1202  $workflow_backend_href = '/'.$workflow_backend_href;
1203  }//end if
1204 
1205  $asset_edt_fns = $asset->getEditFns();
1206  if (isset($asset_edt_fns->static_screens['preview'])) {
1207  $preview_backend_href = $asset->getBackendHref('preview', FALSE);
1208  if (!empty($preview_backend_href) && substr($preview_backend_href, 0, 2) != './') {
1209  $preview_backend_href = '/'.$preview_backend_href;
1210  }//end if
1211  $preview_url = current_url().$preview_backend_href;
1212  } else {
1213  $details_backend_href = $asset->getBackendHref('preview', FALSE);
1214  if (!empty($details_backend_href) && substr($details_backend_href, 0, 2) != './') {
1215  $details_backend_href = '/'.$details_backend_href;
1216  }//end if
1217  $preview_url = current_url().$details_backend_href;
1218  }
1219 
1220  if ($asset->status == SQ_STATUS_LIVE_APPROVAL){
1221  $msg = $ms->newMessage($publishers);
1222 
1223  $workflow = $schema_workflows[$schemaid];
1224  $current_step = $this->getCurrentStep($workflow, $step);
1225 
1226  $msg_reps = Array(
1227  'workflow_user' => $user->name,
1228  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
1229  'asset_name' => $asset->name,
1230  'assetid' => $asset->id,
1231  'workflow_url' => current_url().$workflow_backend_href,
1232  'preview_url' => $preview_url,
1233  );
1234 
1235  $msg->type = 'asset.workflow.review';
1236  $msg->parameters['assetid'] = $asset->id;
1237  // see if we have the 'from' field set
1238  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
1239  if ($schema->attr('schema_reply_to_email_address') != '') {
1240  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
1241  } else {
1242  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
1243  }
1244  if ($schema->attr('schema_from_email_address') != '') {
1245  $msg->from = $schema->attr('schema_from_email_address');
1246  }
1247 
1248  // Does this step have a custom message (for inviting users to the next step)?
1249  if (isset($current_step['message_review_invitation']) && trim($current_step['message_review_invitation']) !== '' ) {
1250  $msg->body = trim($current_step['message_review_invitation']);
1251  }
1252 
1253  // Does this step have a custom subject (for inviting users to the next step)?
1254  if (isset($current_step['subject_review_invitation']) && trim($current_step['subject_review_invitation']) !== '' ) {
1255  $subject = trim(array_get_index($current_step, 'subject_review_invitation', 'Asset Up For Review'));
1256  $msg->subject = (!empty($subject)) ? $subject : 'Asset Up For Review';
1257  }
1258 
1259  $msg->replacements = $msg_reps;
1260  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
1261  $ms->enqueueMessage($msg);
1262  } else {
1263  // send internal messages to everyone in the next step who can now publish the asset
1264  $msg = $ms->newMessage($publishers);
1265 
1266  $workflow =& $schema_workflows[$schemaid];
1267  $previous_step =& $this->getCurrentStep($workflow, $steps_before[$schemaid]);
1268  $previous_step_name =& $previous_step['step_name'];
1269  $current_step =& $this->getCurrentStep($workflow, $step);
1270  $current_step_name =& $current_step['step_name'];
1271 
1272  $accept_url = $workflow_backend_href.'&asset_version='.$asset->version.'&workflow_link_action=approve';
1273  $reject_url = $workflow_backend_href.'&asset_version='.$asset->version.'&workflow_link_action=reject';
1274 
1275  $msg_reps = Array(
1276  'workflow_user' => $user->name,
1277  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
1278  'asset_name' => $asset->name,
1279  'assetid' => $asset->id,
1280  'previous_step_id' => implode('.', $steps_before[$schemaid]),
1281  'previous_step_name' => $previous_step_name,
1282  'current_step_id' => implode('.', $step),
1283  'current_step_name' => $current_step_name,
1284  'workflow_url' => current_url().$workflow_backend_href,
1285  'accept_url' => current_url().$accept_url,
1286  'reject_url' => current_url().$reject_url,
1287  'asset_url' => $asset->getDependantParentsURL(),
1288  'asset_version' => $asset->version,
1289  'log_message' => $log_message,
1290  'preview_url' => $preview_url,
1291  );
1292 
1293  $msg->type = 'asset.workflow.invitation.progress';
1294  $msg->parameters['assetid'] = $asset->id;
1295  $msg->replacements = $msg_reps;
1296  // see if we have the 'from' field set
1297  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
1298  if ($schema->attr('schema_reply_to_email_address') != '') {
1299  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
1300  } else {
1301  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
1302  }
1303  if ($schema->attr('schema_from_email_address') != '') {
1304  $msg->from = $schema->attr('schema_from_email_address');
1305  }
1306 
1307  // Does this step have a custom message?
1308  if (isset($current_step['message_invitation']) && trim($current_step['message_invitation']) !== '' ) {
1309  $msg->body = trim($current_step['message_invitation']);
1310  }//end if
1311 
1312  // Does this step have a custom subject?
1313  if (isset($current_step['subject_invitation']) && trim($current_step['subject_invitation']) !== '' ) {
1314  $subject = trim(array_get_index($current_step, 'subject_invitation', 'Workflow Approval Required'));
1315  $msg->subject = (!empty($subject)) ? $subject : 'Workflow Approval Required';
1316  }//end if
1317 
1318  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
1319  $ms->enqueueMessage($msg);
1320  }//end else
1321 
1322  }//end if step has advanced
1323  }//end foreach $steps_after
1324 
1325  }//end if not silent workflow party
1326 
1327  // if the user who has completed the step on this workflow is also able to
1328  // approve the next part of the workflow, let their action to approve workflow
1329  $current_userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
1330  $list_of_publishers = $this->whoCanPublish($asset->id);
1331  // Test they are the next user in workflow
1332  if (in_array($current_userid, $list_of_publishers)) {
1333  if (!$this->recordPublish($asset->id, $current_userid)) {
1334  trigger_localised_error('SYS0078', E_USER_WARNING, $current_userid);
1335  // dont die here because they can try to approve again later, we just want to warn them
1336  }
1337  }
1338 
1339  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1340  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1341  return TRUE;
1342 
1343  }//end recordPublish()
1344 
1345 
1369  function testPublish($assetid, $publisher, $stream_name='')
1370  {
1371  $schema_workflows = $this->getSchemaWorkflows($assetid);
1372  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
1373 
1374  if (empty($running_schemas)) {
1375  // we dont actually have any running workflows, so lets pretend we do
1376  $schemas = $this->getSchemas($assetid, TRUE);
1377  $streamids = Array();
1378 
1379  // foreach of our schemas check to make sure they are still on system
1380  // see bug 5678 enhancement: better error handling for non exist workflow schema
1381  foreach ($schemas as $index => $schema) {
1382  if (!$GLOBALS['SQ_SYSTEM']->am->assetExists($schema)) {
1383  trigger_error("Workflow Schema Id #$schema is applied to Asset Id #$assetid but cannot be found in system anymore.", E_USER_WARNING);
1384  unset($schemas[$index]);
1385  }
1386  }
1387  if ($stream_name === NULL) {
1388  // ALL Streams
1389  foreach ($schemas as $schemaid) {
1390  $streamids[$schemaid] = $this->getStreams($schemaid);
1391  }
1392  } else if ($stream_name === '') {
1393  // Default Stream
1394  $default_streamids = $this->getDefaultStream($schemas);
1395  // Make it an array to fit it with the all-schemas option
1396  // (name doesn't even matter)
1397  foreach ($default_streamids as $schemaid => $streamid) {
1398  $streamids[$schemaid] = Array($streamid => 'Default Stream');
1399  }
1400  } else {
1401  // Named Stream
1402  $named_streamids = $this->getStreamByName($schemas, $stream_name);
1403  // Make it an array to fit it with the all-schemas option
1404  // (name doesn't even matter)
1405  foreach ($named_streamids as $schemaid => $streamid) {
1406  $streamids[$schemaid] = Array($streamid => $stream_name);
1407  }
1408  }
1409 
1410  foreach ($schemas as $schemaid) {
1411  foreach ($streamids[$schemaid] as $streamid => &$stream_name) {
1412  $schema_workflows[$schemaid][$streamid] = $this->generateWorkflowArray($assetid, $streamid);
1413  }
1414  $running_schemas[] = $schemaid;
1415  }
1416  } else {
1417  // Keep the schema workflow format consistent
1418  foreach($schema_workflows as $schemaid => &$schema_workflow) {
1419  // Handle pre-3.26 schema without a stream ID
1420  $streamid = array_get_index($schema_workflow, 'stream_assetid', 0);
1421  $schema_workflow = Array($streamid => $schema_workflow);
1422  }
1423  }
1424 
1425  for (reset($schema_workflows); NULL !== ($schemaid = key($schema_workflows)); next($schema_workflows)) {
1426  if (!in_array($schemaid, $running_schemas)) continue;
1427 
1428  foreach ($schema_workflows[$schemaid] as $streamid => &$workflow) {
1429  // already finished the workflow?
1430  if ($workflow['complete']) continue;
1431 
1432  // Try to push workflow forward until we can't publish no more -
1433  // either our effort is not enough, or we've completed it
1434  while (!$workflow['complete']) {
1435  if (!$this->_recordPublish($workflow, $publisher, $assetid)) {
1436  break;
1437  }
1438  }
1439 
1440  // We haven't finished the workflow?
1441  if (!$workflow['complete']) return FALSE;
1442  }
1443  }
1444 
1445  return TRUE;
1446 
1447  }//end testPublish()
1448 
1449 
1464  function _recordPublish(&$workflow, $publisherid, $assetid)
1465  {
1466  $am = $GLOBALS['SQ_SYSTEM']->am;
1467  $workflow_updated = FALSE;
1468  // safety code for blank workflows
1469  if (empty($workflow['steps'])) {
1470  $workflow_updated = TRUE;
1471  $workflow['complete'] = TRUE;
1472  $workflow['steps'] = Array();
1473  } else {
1474  $step_data =& $this->getCurrentStep($workflow);
1475  for (reset($step_data['conditions']);
1476  NULL !== ($pub = key($step_data['conditions'])); next($step_data['conditions'])) {
1477 
1478  $cond_data =& $step_data['conditions'][$pub];
1479  // work out who can publish this condition
1480  $can_publish = Array();
1481  $publisher = $GLOBALS['SQ_SYSTEM']->am->getAsset($pub);
1482  if ($publisher->id) {
1483  $asset_class = get_class($publisher);
1484 
1485  // Note that the order of IF statements is important, because roles are
1486  // a descendent of groups but should be treated differently
1487  if ($am->isTypeDecendant($asset_class, 'user')) {
1488  $can_publish[] = $publisher->id;
1489  } else if ($am->isTypeDecendant($asset_class, 'role')) {
1490  if (SQ_CONF_ENABLE_ROLES_WF_SYSTEM == '1') {
1491  $fetch_global_roles = (SQ_CONF_ENABLE_GLOBAL_ROLES == '1');
1492  $roles = $GLOBALS['SQ_SYSTEM']->am->getRole($assetid, $publisher->id, NULL, FALSE, $fetch_global_roles, TRUE);
1493  foreach ($roles as $roleid => $userids) {
1494  foreach ($userids as $userid) {
1495  $can_publish[] = $userid;
1496  }
1497  }
1498  }
1499  } else if ($am->isTypeDecendant($asset_class, 'user_group')) {
1500  $can_publish = array_keys($GLOBALS['SQ_SYSTEM']->am->getChildren($publisher->id, Array('user'), FALSE, NULL, NULL, NULL, TRUE, NULL, NULL, FALSE, NULL, Array(), TRUE));
1501  }
1502  }
1503 
1504  if (in_array($publisherid, $can_publish)) {
1505  if (!isset($cond_data['published_by'])) {
1506  $cond_data['published_by'] = Array();
1507  }
1508  // if the publisherid has not already published
1509  if (!isset($cond_data['published_by'][$publisherid])) {
1510  $cond_data['published_by'][$publisherid] = time();
1511  $workflow_updated = TRUE;
1512  }
1513 
1514  }
1515  }//end for
1516 
1517  if ($workflow_updated) {
1518  // load the current step into the workflow
1519  $this->_loadCurrentStep($workflow);
1520 
1521  // if the current step returns nothing, workflow is complete
1522  if ($workflow['current_step'] === Array()) {
1523  $workflow['complete'] = TRUE;
1524  // mark the last step as completed
1525  $num_steps = count($workflow['steps']);
1526  if ($num_steps > 0) {
1527  $workflow['steps'][$num_steps]['completed'] = time();
1528  }
1529  // else start the next workflow step
1530  } else {
1531  $current_step =& $this->getCurrentStep($workflow);
1532  if (!$current_step['started']) {
1533  $current_step['started'] = time();
1534  }
1535  // if this is not the first step in this workflow,
1536  $step_keys = array_keys($workflow['current_step']);
1537  $last_key = end($step_keys);
1538  if ($workflow['current_step'][$last_key] > 1) {
1539  $previous_step_address = $workflow['current_step'];
1540  $previous_step_address[$last_key]--;
1541  $previous_step =& $this->getCurrentStep($workflow, $previous_step_address);
1542  if (!$previous_step['completed']) {
1543  $previous_step['completed'] = time();
1544  }
1545  }
1546  }//end if
1547  }
1548  }//end else
1549 
1550  return $workflow_updated;
1551 
1552  }//end _recordPublish()
1553 
1554 
1567  function generateWorkflowArray($assetid, $streamid)
1568  {
1569  $assetid = (int) $assetid;
1570  $streamid = (int) $streamid;
1571 
1572  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1573  if (is_null($asset)) {
1574  trigger_localised_error('SYS0155', E_USER_WARNING, $assetid);
1575  return FALSE;
1576  }
1577 
1578  $stream = $GLOBALS['SQ_SYSTEM']->am->getAsset($streamid);
1579  if (is_null($stream)) {
1580  trigger_localised_error('SYS0156', E_USER_WARNING, $streamid);
1581  return FALSE;
1582  }
1583 
1584  if (!($stream instanceof Workflow_Stream)) {
1585  trigger_localised_error('SYS0154', E_USER_WARNING, $stream->name, $streamid);
1586  return FALSE;
1587  }
1588 
1589  // check that the schema we are generating an array for is actually set
1590  $asset_schemas = $this->getSchemas($assetid, TRUE);
1591  $stream_parents = $GLOBALS['SQ_SYSTEM']->am->getDependantParents($streamid, 'workflow_schema', TRUE);
1592 
1593  if (count(array_intersect($asset_schemas, $stream_parents)) === 0) {
1594  return Array();
1595  }
1596 
1597  $edit_fns = $stream->getEditFns();
1598  return $edit_fns->generateWorkflowArray($stream);
1599 
1600  }//end generateWorkflowArray()
1601 
1602 
1613  function startWorkflow($assetid, $auto_approve=TRUE, $base_msg=NULL, $stream_name=NULL)
1614  {
1615  $assetid = (int) $assetid;
1616  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1617  if (is_null($asset)) {
1618  trigger_localised_error('SYS0179', E_USER_WARNING, $assetid);
1619  return FALSE;
1620  }
1621 
1622  $schemas = $this->getSchemas($assetid, TRUE, FALSE);
1623 
1624  if (empty($schemas)) return FALSE;
1625  if ($stream_name === NULL) {
1626  $stream_name = $this->getStartingStream($assetid);
1627  $this->setStartingStream($assetid, NULL);
1628  }
1629 
1630  if ($stream_name === NULL) {
1631  $streams = $this->getDefaultStream($schemas);
1632  $stream_name = 'Default Stream';
1633  } else {
1634  $streams = $this->getStreamByName($schemas, $stream_name);
1635  }
1636 
1637  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1638  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1639 
1640  $workflow_names = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($schemas);
1641 
1642  foreach ($schemas as $schemaid) {
1643  // Grab the workflow array from the appropriate stream, and then
1644  // make that our workflow for this
1645  $workflow = $this->generateWorkflowArray($assetid, $streams[$schemaid]);
1646  $workflow['schema_name'] = $workflow_names[$schemaid]['name'];
1647 
1648  $stream_names = $this->getStreams($schemaid);
1649  $workflow['stream_assetid'] = $streams[$schemaid];
1650  $workflow['stream_name'] = $stream_names[$streams[$schemaid]];
1651  if (empty($workflow['steps'])) continue;
1652  $new_workflow = serialize($workflow);
1653 
1654  $sql = 'UPDATE
1655  '.SQ_TABLE_RUNNING_PREFIX.'ast_wflow
1656  SET
1657  wflow = :wflow,
1658  last_started_by = :last_started_by
1659  WHERE
1660  assetid = :assetid
1661  AND schemaid = :schemaid';
1662 
1663  try {
1664  $query = MatrixDAL::preparePdoQuery($sql);
1665  $bind_vars = Array(
1666  'wflow' => $new_workflow,
1667  'assetid' => $assetid,
1668  'schemaid' => $schemaid,
1669  'last_started_by' => $workflow['started_by'],
1670  );
1671  foreach ($bind_vars as $bind_name => $bind_value){
1672  MatrixDAL::bindValueToPdo($query, $bind_name, $bind_value);
1673  }
1674  MatrixDAL::execPdoQuery($query);
1675  } catch (DALException $e) {
1676  throw new Exception('Unable to update workflow table for asset: '.$assetid.' due to database error: '.$e->getMessage());
1677  }
1678 
1679  }
1680  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1681  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1682 
1683  $this->_tmp = Array();
1684 
1685  if ($auto_approve) {
1686  // if the user who has started this workflow is also able to
1687  // approve it as part of workflow, let their action to start workflow
1688  // signal their intention to approve it as well
1689  $publishers = $this->whoCanPublish($asset->id);
1690  $current_userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
1691  if (in_array($current_userid, $publishers)) {
1692  if (!$this->recordPublish($asset->id, $current_userid)) {
1693  trigger_localised_error('SYS0078', E_USER_WARNING, $current_userid);
1694  // dont die here because they can try to approve again later, we just want to warn them
1695  } else {
1696  // bug fix #3807 Workflow email send to second reviewer contains wrong content and link
1697  // we we have sucessfully completed recordPublish() call, then the email to all the
1698  // approvers in the following step condition has already been safe. It will be safe
1699  // to return from here.
1700  return TRUE;
1701  }
1702  }
1703  }
1704 
1705  if (!$this->silentWorkflowParty($asset->id)) {
1706  // send internal messages to everyone who can now publish the asset
1707  $generic_msg = NULL;
1708 
1709  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
1710  $schema_workflows = $this->getSchemaWorkflows($asset->id);
1711  foreach ($schema_workflows as $schemaid => $schema_workflow) {
1712  $current_step = $this->getCurrentStep($schema_workflow);
1713  $publishers = $this->whoCanPublish($asset->id, $schemaid);
1714 
1715  // we don't want to be sending asset.workflow.invitation emails to users who have admin
1716  // permissions on the asset in workflow, see expandUsersTo() in internal_message.inc
1717  if (is_null($base_msg)) {
1718  if (empty($publishers)) {
1719  continue;
1720  } else {
1721  // create a new internal message if we have not been supplied with one
1722  $msg = $ms->newMessage();
1723  }
1724  } else {
1725  // Clone the base internal message
1726  $msg = clone $base_msg;
1727  if (empty($msg->to) && empty($msg->type) && empty($publishers)) {
1728  continue;
1729  }
1730  }
1731 
1732  // see if we have the 'from' field set
1733  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
1734  if ($schema->attr('schema_reply_to_email_address') != '') {
1735  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
1736  } else {
1737  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
1738  }
1739  if ($schema->attr('schema_from_email_address') != '') {
1740  $msg->from = $schema->attr('schema_from_email_address');
1741  }
1742 
1743  // added the publishers to the list of people that are to be notified
1744  $msg->to = array_merge($msg->to, $publishers);
1745  // complete any missing internal message fields that have not been supplied
1746  if (empty($msg->replacements)) {
1747  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($GLOBALS['SQ_SYSTEM']->currentUserId());
1748  // Bug fix #2636, add the slash to the URL in case the trigger fire this chain of action.
1749  $workflow_backend_href = $asset->getBackendHref('workflow', FALSE);
1750  if (!empty($workflow_backend_href) && substr($workflow_backend_href, 0, 2) != './') {
1751  $workflow_backend_href = '/'.$workflow_backend_href;
1752  }//end if
1753 
1754  $accept_url = $workflow_backend_href.'&asset_version='.$asset->version.'&workflow_link_action=approve';
1755  $reject_url = $workflow_backend_href.'&asset_version='.$asset->version.'&workflow_link_action=reject';
1756 
1757 
1758  $msg_reps = Array(
1759  'workflow_user' => $user->name,
1760  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
1761  'asset_name' => $asset->name,
1762  'assetid' => $asset->id,
1763  'workflow_url' => current_url().$workflow_backend_href,
1764  'accept_url' => current_url().$accept_url,
1765  'reject_url' => current_url().$reject_url,
1766  'previous_step_id' => '',
1767  'previous_step_name' => '',
1768  'current_step_id' => implode('.', $schema_workflow['current_step']),
1769  'current_step_name' => $current_step['step_name'],
1770  'stream' => $schema_workflow['stream_name'],
1771  'asset_url' => $asset->getDependantParentsURL(),
1772  'asset_version' => $asset->version,
1773  'log_message' => '',
1774  );
1775  $asset_edt_fns = $asset->getEditFns();
1776  if (isset($asset_edt_fns->static_screens['preview'])) {
1777  $preview_backend_href = $asset->getBackendHref('preview', FALSE);
1778  if (!empty($preview_backend_href) && substr($preview_backend_href, 0, 2) != './') {
1779  $preview_backend_href = '/'.$preview_backend_href;
1780  }//end if
1781  $msg_reps['preview_url'] = current_url().$preview_backend_href;
1782  } else {
1783  $details_backend_href = $asset->getBackendHref('preview', FALSE);
1784  if (!empty($details_backend_href) && substr($details_backend_href, 0, 2) != './') {
1785  $details_backend_href = '/'.$details_backend_href;
1786  }//end if
1787  $msg_reps['preview_url'] = current_url().$details_backend_href;
1788  }
1789  $msg->replacements = $msg_reps;
1790 
1791  // Does this step have a custom message?
1792  if (isset($current_step['message_invitation']) && trim($current_step['message_invitation']) !== '' ) {
1793  // This message has a custom body. Just send it.
1794  $subject = trim(array_get_index($current_step, 'subject_invitation', 'Workflow Approval Required'));
1795  $msg->subject = (!empty($subject)) ? $subject : 'Workflow Approval Required';
1796  $msg->body = trim($current_step['message_invitation']);
1797  } else if (isset($current_step['subject_invitation']) && trim($current_step['subject_invitation']) !== '') {
1798  // This message has a custom subject. Just send it.
1799  $subject = trim(array_get_index($current_step, 'subject_invitation', 'Workflow Approval Required'));
1800  $msg->subject = (!empty($subject)) ? $subject : 'Workflow Approval Required';
1801  } else {
1802  // This is the generic message. Make sure only one of these are sent out,
1803  // regardless of how many schemas trigger it.
1804  if ($generic_msg === NULL) {
1805  $generic_msg = $msg;
1806  } else {
1807  $generic_msg->to = array_merge($generic_msg->to, $msg->to);
1808  continue;
1809  }
1810  }
1811 
1812  }//end if
1813  if (empty($msg->type)) {
1814  $msg->type = 'asset.workflow.invitation';
1815  } else if ($msg->type == 'asset.workflow.review'){
1816  // Does this step have a custom message?
1817  if (isset($current_step['message_review_invitation']) && trim($current_step['message_review_invitation']) !== '' ) {
1818  $msg->body = trim($current_step['message_review_invitation']);
1819  }//end if
1820 
1821  // Does this step have a custom subject?
1822  if (isset($current_step['subject_review_invitation']) && trim($current_step['subject_review_invitation']) !== '' ) {
1823  $subject = trim(array_get_index($current_step, 'subject_review_invitation', 'Asset Up For Review'));
1824  $msg->subject = (!empty($subject)) ? $subject : 'Asset Up For Review';
1825  }//end if
1826  }
1827 
1828  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
1829 
1830  $msg->parameters['assetid'] = $asset->id;
1831  $ms->enqueueMessage($msg);
1832  }//end foreach
1833  }//end if
1834 
1835  return TRUE;
1836 
1837  }//end startWorkflow()
1838 
1839 
1848  function isWorkflowComplete($assetid)
1849  {
1850  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
1851  if (empty($running_schemas)) return TRUE;
1852  $schema_workflows = $this->getSchemaWorkflows($assetid);
1853 
1854  foreach ($schema_workflows as $schemaid => $workflow) {
1855  if (!in_array($schemaid, $running_schemas)) continue;
1856  if (empty($workflow['steps'])) continue;
1857  if (!$workflow['complete']) return FALSE;
1858  }
1859 
1860  return TRUE;
1861 
1862  }//end isWorkflowComplete()
1863 
1864 
1873  function getWorkflowCurrentSteps($assetid)
1874  {
1875  $running_schemas = $this->getSchemas($assetid, TRUE, TRUE);
1876  if (empty($running_schemas)) return Array();
1877  $schema_workflows = $this->getSchemaWorkflows($assetid);
1878 
1879  $steps = Array();
1880  foreach ($schema_workflows as $schemaid => $workflow) {
1881  if (!in_array($schemaid, $running_schemas)) continue;
1882  if (empty($workflow['steps'])) continue;
1883  $steps[$schemaid] = $workflow['current_step'];
1884 
1885  }
1886 
1887  return $steps;
1888 
1889  }//end getWorkflowCurrentSteps()
1890 
1891 
1901  function cancelWorkflow($assetid, $base_msg=NULL)
1902  {
1903  $assetid = (int) $assetid;
1904  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1905  if (is_null($asset)) {
1906  trigger_localised_error('SYS0146', E_USER_WARNING, $assetid);
1907  return FALSE;
1908  }
1909 
1910  // we have to get the publishers before the workflow is cleared, because
1911  // this function depends on having a workflow running
1912  $publishers = $this->whoCanPublish($asset->id);
1913  $workflow_values = $this->getSchemaWorkflows($assetid);
1914  $workflow = reset($workflow_values);
1915  $started_by = $workflow['started_by'];
1916  $started_by_user = $GLOBALS['SQ_SYSTEM']->am->getAsset($started_by, '', TRUE);
1917  if ($started_by_user){
1918  // see bug #4986 Simple Edit User doesnt get workflow emails initiated via EES
1919  // and see simple_edit_user::canAccessBackend()
1920  if ($started_by_user instanceof Simple_Edit_User) $started_by_user->_tmp['starter_of_workflow'] = TRUE;
1921  }
1922  $publishers = array_unique(array_merge($publishers, Array($started_by)));
1923 
1924  $current_steps = $this->getWorkflowCurrentSteps($assetid);
1925  if (!$this->_clearWorkflow($assetid)) return FALSE;
1926 
1927  // send internal messages to everyone who could approve before to let them know that someone cancelled
1928  if (!$this->silentWorkflowParty($asset->id)) {
1929  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
1930 
1931  // fire the 'Workflow Rejection' event
1932  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_workflow_rejection', $asset);
1933  $generic_msg = NULL;
1934 
1935  // workflow logs since the start of this workflow
1936  $log_message = '';
1937  $comments = $ms->getMessages(0, 'asset.workflow.userlog', Array(), Array(), $workflow['started'], NULL, 'name', Array('assetid' => $asset->id));
1938  foreach ($comments as $comment) {
1939  $log_message .= '"'.$comment['body'].'"'.', by '.$comment['from_name'].', '.ts_iso8601($comment['sent'])."\n";
1940  }
1941 
1942  foreach ($current_steps as $schemaid => $current_step_num) {
1943  $current_step = $this->getCurrentStep($workflow_values[$schemaid], $current_step_num);
1944 
1945  // we don't want to be sending asset.workflow.announce.reject emails to users who have admin
1946  // permissions on the asset in workflow, see expandUsersTo() in internal_message.inc
1947  if (is_null($base_msg)) {
1948  if (empty($publishers)) {
1949  continue;
1950  } else {
1951  // create a new internal message if we have not been supplied with one
1952  $msg = $ms->newMessage();
1953  }
1954  } else {
1955  $msg = clone $base_msg;
1956  if (empty($msg->to) && empty($msg->type) && empty($publishers)) {
1957  continue;
1958  }
1959  }
1960 
1961  // see if we have the 'from' field set
1962  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
1963  if ($schema->attr('schema_reply_to_email_address') != '') {
1964  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
1965  } else {
1966  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
1967  }
1968  if ($schema->attr('schema_from_email_address') != '') {
1969  $msg->from = $schema->attr('schema_from_email_address');
1970  }
1971 
1972  // added the publishers to the list of people that are to be notified
1973  // (note: the publishers get emails from each workflow - they should be
1974  // combined into one at the Messaging Service end)
1975  $msg->to = array_merge($msg->to, $publishers);
1976 
1977  // complete any missing internal message fields that have not been supplied
1978  if (empty($msg->replacements)) {
1979  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($GLOBALS['SQ_SYSTEM']->currentUserId());
1980  $msg_reps = Array(
1981  'workflow_user' => $user->name,
1982  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
1983  'asset_name' => $asset->name,
1984  'assetid' => $asset->id,
1985  'workflow_url' => current_url().$asset->getBackendHref('workflow', FALSE),
1986  'asset_url' => $asset->getDependantParentsURL(),
1987  'asset_version' => $asset->version,
1988  'log_message' => $log_message,
1989  );
1990  $asset_edt_fns = $asset->getEditFns();
1991  if (isset($asset_edt_fns->static_screens['preview'])) {
1992  $msg_reps['preview_url'] = current_url().$asset->getBackendHref('preview', FALSE);
1993  } else {
1994  $msg_reps['preview_url'] = current_url().$asset->getBackendHref('details', FALSE);
1995  }
1996  $msg->replacements = $msg_reps;
1997 
1998  // Does this step have a custom message?
1999  if (isset($current_step['message_reject']) && trim($current_step['message_reject']) !== '' ) {
2000  $subject = trim(array_get_index($current_step, 'subject_reject', 'Asset Changes Rejected'));
2001  $msg->subject = (!empty($subject)) ? $subject : 'Asset Changes Rejected';
2002  $msg->body = trim($current_step['message_reject']);
2003  } else if (isset($current_step['subject_reject']) && trim($current_step['subject_reject']) !== '') {
2004  $subject = trim(array_get_index($current_step, 'subject_reject', 'Asset Changes Rejected'));
2005  $msg->subject = (!empty($subject)) ? $subject : 'Asset Changes Rejected';
2006  } else {
2007  // This is the generic message. Make sure only one of these are sent out,
2008  // regardless of how many schemas trigger it.
2009  if ($generic_msg === NULL) {
2010  $generic_msg = $msg;
2011  } else {
2012  $generic_msg->to = array_merge($generic_msg->to, $msg->to);
2013  continue;
2014  }
2015  }
2016  }
2017  if (empty($msg->type)) {
2018  $msg->type = 'asset.workflow.announce.reject';
2019  } else if ($msg->type == 'asset.workflow.review.cancel'){
2020  // Does this step have a custom message?
2021  if (isset($current_step['message_review_cancel']) && trim($current_step['message_review_cancel']) !== '' ) {
2022  $subject = trim(array_get_index($current_step, 'subject_review_cancel', 'Asset Review Cancelled'));
2023  $msg->subject = (!empty($subject)) ? $subject : 'Asset Review Cancelled';
2024  $msg->body = trim($current_step['message_review_cancel']);
2025  } else if (isset($current_step['subject_review_cancel']) && trim($current_step['subject_review_cancel']) !== '') {
2026  $subject = trim(array_get_index($current_step, 'subject_review_cancel', 'Asset Review Cancelled'));
2027  $msg->subject = (!empty($subject)) ? $subject : 'Asset Review Cancelled';
2028  } else {
2029  // This is the generic message. Make sure only one of these are sent out,
2030  // regardless of how many schemas trigger it.
2031  if ($generic_msg === NULL) {
2032  $generic_msg = $msg;
2033  } else {
2034  $generic_msg->to = array_merge($generic_msg->to, $msg->to);
2035  continue;
2036  }
2037  }
2038  }
2039 
2040  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
2041  $msg->parameters['assetid'] = $asset->id;
2042  $ms->enqueueMessage($msg);
2043 
2044  }//end foreach
2045 
2046  }//end if
2047 
2048  return TRUE;
2049 
2050  }//end cancelWorkflow()
2051 
2052 
2061  function completeWorkflow($assetid)
2062  {
2063  $assetid = (int) $assetid;
2064  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
2065  if (is_null($asset)) {
2066  trigger_localised_error('SYS0150', E_USER_WARNING, $assetid);
2067  return FALSE;
2068  }
2069 
2070  return $this->_clearWorkflow($assetid);
2071 
2072  }//end completeWorkflow()
2073 
2074 
2083  function _clearWorkflow($assetid)
2084  {
2085  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2086  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2087 
2088  $sql = 'UPDATE
2089  sq_ast_wflow
2090  SET
2091  wflow = NULL
2092  WHERE
2093  assetid = :assetid';
2094 
2095  try {
2096  $query = MatrixDAL::preparePdoQuery($sql);
2097  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
2098  MatrixDAL::execPdoQuery($query);
2099  } catch (DALException $e) {
2100  throw new Exception ('Unable to clear workflow for asset: '.$assetid.' due to database error: '.$e->getMessage());
2101  }
2102 
2103  unset($this->_tmp['schemas'][$assetid]);
2104  unset($this->_tmp['schema_workflows'][$assetid]);
2105 
2106  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2107  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2108 
2109  return TRUE;
2110 
2111  }//end _clearWorkflow()
2112 
2113 
2122  function purgeWorkflow($assetid)
2123  {
2124  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2125  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2126 
2127  try {
2128  $bind_vars = Array(
2129  'assetid' => (string)$assetid,
2130  );
2131  MatrixDAL::executeQuery('core', 'purgeWorkflow', $bind_vars);
2132  } catch (DALException $e) {
2133  throw new Exception ('Unable to purge workflow schema for "'.$asset->name.'" (#'.$asset->id.') due to database error: '.$e->getMessage());
2134  }
2135 
2136  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2137  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2138 
2139  return TRUE;
2140 
2141  }//end purgeWorkflow()
2142 
2143 
2157  function silentWorkflowParty($assetid)
2158  {
2159  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset((int) $assetid);
2160  if (is_null($asset)) return TRUE;
2161  $edit_fns = $asset->getEditFns();
2162  return !isset($edit_fns->static_screens['workflow']);
2163 
2164  }//end silentWorkflowParty()
2165 
2166 
2178  function escalateWorkflow($assetid, $schemaid)
2179  {
2180  $assetid = (int) $assetid;
2181  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
2182  if (is_null($asset)) {
2183  trigger_localised_error('SYS0160', E_USER_WARNING, $assetid);
2184  return FALSE;
2185  }
2186 
2187  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
2188  $schema_workflows = $this->getSchemaWorkflows($assetid);
2189  $workflow =& $schema_workflows[$schemaid];
2190 
2191  // workflow logs since the start of this workflow
2192  $log_message = '';
2193  $comments = $ms->getMessages(0, 'asset.workflow.userlog', Array(), Array(), $workflow['started'], NULL, 'name', Array('assetid' => $asset->id));
2194  foreach ($comments as $comment) {
2195  $log_message .= '"'.$comment['body'].'"'.', by '.$comment['from_name'].', '.ts_iso8601($comment['sent'])."\n";
2196  }
2197 
2198  // mark the current step as escalated
2199  $current_step =& $this->getCurrentStep($workflow);
2200 
2201  // we'll need these for later to check if any workflow has changed steps
2202  $step_before = $current_step;
2203  $step_before_id = $workflow['current_step'];
2204  $publishers_before = $this->whoCanPublish($assetid);
2205 
2206 
2207  $current_step['expired'] = TRUE;
2208 
2209  // if there are no escalation steps for the current step, we can't escalate to substeps, so defer to the next step
2210  if (empty($current_step['escalation_steps'])) {
2211  $current_step['completed'] = time();
2212  }
2213 
2214  // load the current step into the workflow
2215  $this->_loadCurrentStep($workflow);
2216 
2217  if ($workflow['current_step'] == Array()) {
2218  if ($this->silentWorkflowParty($asset->id)) return TRUE;
2219  // We can't complete workflow by escalating
2220  $hrefs = $GLOBALS['SQ_SYSTEM']->am->getAssetBackendHref(Array($assetid => 'workflow'), FALSE);
2221  $href = str_replace('./', '', current($hrefs));
2222  $root_urls = explode("\n", SQ_CONF_SYSTEM_ROOT_URLS);
2223  $url = 'http://'.current($root_urls).'/'.$href;
2224  $type = 'asset.workflow.stale';
2225  $admins = $GLOBALS['SQ_SYSTEM']->am->getPermission($assetid, SQ_PERMISSION_ADMIN, TRUE, FALSE, TRUE);
2226 
2227 
2228 
2229  $msg_reps = Array(
2230  'asset_name' => $asset->attr('name'),
2231  'assetid' => $assetid,
2232  'step_id' => implode('.', $step_before_id),
2233  'step_name' => $current_step['step_name'],
2234  'schema' => $workflow['schema_name'],
2235  'stream' => $workflow['stream_name'],
2236  'started_time' => easy_datetime($current_step['started']),
2237  'expiry_time' => easy_time_total($current_step['expiry_time']),
2238  'workflow_url' => $url,
2239  'asset_url' => $asset->getDependantParentsURL(),
2240  'asset_version' => $asset->version,
2241  'log_message' => $log_message,
2242  );
2243 
2244  // If there are no admins, send it to system admins instead
2245  if (empty($admins)) $admins = Array(0);
2246 
2247  $msg = $ms->newMessage($admins, $type, $msg_reps);
2248 
2249  // see if we have the 'from' field set
2250  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
2251  if ($schema->attr('schema_reply_to_email_address') != '') {
2252  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
2253  } else {
2254  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
2255  }
2256  if ($schema->attr('schema_from_email_address') != '') {
2257  $msg->from = $schema->attr('schema_from_email_address');
2258  }
2259 
2260  // Does this step have a custom message for stale workflow?
2261  if (isset($current_step['message_stale']) && trim($current_step['message_stale']) !== '' ) {
2262  $msg->body = trim($current_step['message_stale']);
2263  }
2264 
2265  // Does this step have a custom subject for stale workflow?
2266  if (isset($current_step['subject_stale']) && trim($current_step['subject_stale']) !== '' ) {
2267  $subject = trim(array_get_index($current_step, 'subject_stale', 'Stale Workflow Step'));
2268  $msg->subject = (!empty($subject)) ? $subject : 'Stale Workflow Step';
2269  }//end if
2270 
2271  $msg->parameters['assetid'] = $asset->id;
2272  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
2273  $ms->enqueueMessage($msg);
2274 
2275  return TRUE;
2276  }
2277 
2278  // we may have gone deeper in workflow. If so, mark the current step as started.,
2279  $current_step =& $this->getCurrentStep($workflow);
2280  if (!$current_step['started']) {
2281  $current_step['started'] = time();
2282  }
2283 
2284 
2285  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2286  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2287 
2288  $new_workflow = serialize($workflow);
2289  try {
2290  $bind_vars = Array(
2291  'wflow' => $new_workflow,
2292  'assetid' => $assetid,
2293  'schemaid' => $schemaid,
2294  );
2295  MatrixDAL::executeQuery('core', 'updateWorkflow', $bind_vars);
2296  } catch (Exception $e) {
2297  throw new Exception('Unable to update workflow table for asset: '.$assetid.' due to database error: '.$e->getMessage());
2298  }
2299 
2300  $this->_tmp['schema_workflows'][$assetid][$schemaid] = $workflow;
2301  if (isset($this->_tmp['schema_workflows'][$assetid]['all'][$schemaid])) {
2302  $this->_tmp['schema_workflows'][$assetid]['all'][$schemaid] = $workflow;
2303  }
2304 
2305 
2306  // send an internal message to let people know workflow has been escalated
2307  $asset_type = $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name');
2308  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($GLOBALS['SQ_SYSTEM']->currentUserId());
2309 
2310  $log = $ms->newMessage();
2311 
2312  $url_parts = explode('://' , $asset->getURL());
2313 
2314  if (count($url_parts) > 1) {
2315  $site_url = $GLOBALS['SQ_SYSTEM']->am->getRootURL($url_parts[1]);
2316  $root_url = $url_parts[0].'://'.$site_url['url'];
2317  } else {
2318  // Asset doesn't has url, so ...
2319  $hrefs = $GLOBALS['SQ_SYSTEM']->am->getAssetBackendHref(Array($assetid => 'workflow'), FALSE);
2320  $href = str_replace('./', '', current($hrefs));
2321  $root_urls = explode("\n", SQ_CONF_SYSTEM_ROOT_URLS);
2322  $root_url = 'http://'.current($root_urls).'/'.$href;
2323  }
2324 
2325  $msg_reps = Array(
2326  'workflow_user' => $user->name,
2327  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
2328  'asset_name' => $asset->name,
2329  'workflow_url' => $root_url.'/'.$asset->getBackendHref('workflow', FALSE),
2330  'assetid' => $assetid,
2331  'previous_step_id' => implode('.', $step_before_id),
2332  'previous_step_name' => $step_before['step_name'],
2333  'current_step_id' => implode('.', $workflow['current_step']),
2334  'current_step_name' => $current_step['step_name'],
2335  'schema' => $workflow['schema_name'],
2336  'stream' => $workflow['stream_name'],
2337  'started_time' => easy_datetime($step_before['started']),
2338  'expiry_time' => easy_time_total($step_before['expiry_time']),
2339  'asset_url' => $asset->getDependantParentsURL(),
2340  'asset_version' => $asset->version,
2341  'log_message' => $log_message,
2342  );
2343 
2344  // see if we have the 'from' field set
2345  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid, 'workflow_schema');
2346  if ($schema->attr('schema_reply_to_email_address') != '') {
2347  $log->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
2348  } else {
2349  $log->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
2350  }
2351  if ($schema->attr('schema_from_email_address') != '') {
2352  $log->from = $schema->attr('schema_from_email_address');
2353  }
2354 
2355  $log->replacements = $msg_reps;
2356  $log->type = 'asset.workflow.log.escalated';
2357 
2358  $log->parameters['assetid'] = $asset->id;
2359  $log->parameters['version'] = substr($asset->version, 0, strrpos($asset->version, '.'));
2360  $ms->enqueueMessage($log);
2361 
2362 
2363  // send internal messages to everyone who could approve before to let them know they have lost their chance
2364 
2365  if (!$this->silentWorkflowParty($asset->id)) {
2366  $msg = $ms->newMessage($publishers_before);
2367  // see if we have the 'from' field set
2368  if ($schema->attr('schema_reply_to_email_address') != '') {
2369  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
2370  } else {
2371  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
2372  }
2373  if ($schema->attr('schema_from_email_address') != '') {
2374  $msg->from = $schema->attr('schema_from_email_address');
2375  }
2376 
2377  $msg->type = 'asset.workflow.announce.escalated';
2378  $msg->parameters['assetid'] = $asset->id;
2379  $msg->replacements = $msg_reps;
2380 
2381  // Does this step have a custom message for escalation?
2382  if (isset($step_before['message_escalated']) && trim($step_before['message_escalated']) !== '' ) {
2383  $msg->body = trim($step_before['message_escalated']);
2384  }
2385 
2386  // Does this step have a custom subject for escalation?
2387  if (isset($step_before['subject_escalated']) && trim($step_before['subject_escalated']) !== '' ) {
2388  $subject = trim(array_get_index($step_before, 'subject_escalated', 'Workflow Escalated'));
2389  $msg->subject = (!empty($subject)) ? $subject : 'Workflow Escalated';
2390  }
2391 
2392  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
2393  $ms->enqueueMessage($msg);
2394 
2395  // send messages to the new approvers if they have
2396 
2397  $publishers = $this->whoCanPublish($asset->id, $schemaid);
2398  $url_parts = explode('://' , $asset->getURL());
2399  if (count($url_parts) > 1) {
2400  $site_url = $GLOBALS['SQ_SYSTEM']->am->getRootURL($url_parts[1]);
2401  $root_url = $url_parts[0].'://'.$site_url['url'];
2402  } else {
2403  // Asset doesn't has url, so ...
2404  $hrefs = $GLOBALS['SQ_SYSTEM']->am->getAssetBackendHref(Array($assetid => 'workflow'), FALSE);
2405  $href = str_replace('./', '', current($hrefs));
2406  $root_urls = explode("\n", SQ_CONF_SYSTEM_ROOT_URLS);
2407  $root_url = 'http://'.current($root_urls).'/'.$href;
2408  }
2409 
2410  if ($asset->status == SQ_STATUS_LIVE_APPROVAL){
2411  $msg = $ms->newMessage($publishers);
2412 
2413  $msg_reps = Array(
2414  'workflow_user' => $user->name,
2415  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
2416  'asset_name' => $asset->name,
2417  'assetid' => $asset->id,
2418  'workflow_url' => $root_url.'/'.$asset->getBackendHref('workflow', FALSE),
2419  );
2420 
2421  $asset_edt_fns = $asset->getEditFns();
2422  if (isset($asset_edt_fns->static_screens['preview'])) {
2423  $msg_reps['preview_url'] = $root_url.'/'.$asset->getBackendHref('preview', FALSE);
2424  } else {
2425  $msg_reps['preview_url'] = $root_url.'/'.$asset->getBackendHref('details', FALSE);
2426  }
2427  // see if we have the 'from' field set
2428  if ($schema->attr('schema_reply_to_email_address') != '') {
2429  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
2430  } else {
2431  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
2432  }
2433  if ($schema->attr('schema_from_email_address') != '') {
2434  $msg->from = $schema->attr('schema_from_email_address');
2435  }
2436 
2437  $msg->type = 'asset.workflow.review';
2438  $msg->parameters['assetid'] = $asset->id;
2439 
2440  // Does this step have a custom message (for inviting users to the next step)?
2441  if (isset($current_step['message_review_invitation']) && trim($current_step['message_review_invitation']) !== '' ) {
2442  $msg->body = trim($current_step['message_review_invitation']);
2443  }
2444 
2445  // Does this step have a custom subject (for inviting users to the next step)?
2446  if (isset($current_step['subject_review_invitation']) && trim($current_step['subject_review_invitation']) !== '' ) {
2447  $subject = trim(array_get_index($current_step, 'subject_review_invitation', 'Asset Up For Review'));
2448  $msg->subject = (!empty($subject)) ? $subject : 'Asset Up For Review';
2449  }
2450 
2451  $msg->replacements = $msg_reps;
2452  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
2453  $ms->enqueueMessage($msg);
2454 
2455  } else {
2456  // send internal messages to everyone in the next step who can now publish the asset
2457  $msg = $ms->newMessage($publishers);
2458 
2459  $workflow_backend_href = $asset->getBackendHref('workflow', FALSE);
2460  $accept_url = $workflow_backend_href.'&asset_version='.$asset->version.'&workflow_link_action=approve';
2461  $reject_url = $workflow_backend_href.'&asset_version='.$asset->version.'&workflow_link_action=reject';
2462 
2463  $msg_reps = Array(
2464  'workflow_user' => $user->name,
2465  'type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($asset->type(), 'name'),
2466  'asset_name' => $asset->name,
2467  'assetid' => $asset->id,
2468  'previous_step_id' => implode('.',$step_before_id),
2469  'previous_step_name' => $step_before['step_name'],
2470  'current_step_id' => implode('.',$workflow['current_step']),
2471  'current_step_name' => $current_step['step_name'],
2472  'stream' => $workflow['stream_name'],
2473  'workflow_url' => $root_url.'/'.$workflow_backend_href,
2474  'accept_url' => $root_url.'/'.$accept_url,
2475  'reject_url' => $root_url.'/'.$reject_url,
2476  'asset_url' => $asset->getDependantParentsURL(),
2477  'asset_version' => $asset->version,
2478  'log_message' => $log_message,
2479  );
2480 
2481  $asset_edt_fns = $asset->getEditFns();
2482  if (isset($asset_edt_fns->static_screens['preview'])) {
2483  $msg_reps['preview_url'] = $root_url.'/'.$asset->getBackendHref('preview', FALSE);
2484  } else {
2485  $msg_reps['preview_url'] = $root_url.'/'.$asset->getBackendHref('details', FALSE);
2486  }
2487  // see if we have the 'from' field set
2488  if ($schema->attr('schema_reply_to_email_address') != '') {
2489  $msg->parameters['reply_to'] = $schema->attr('schema_reply_to_email_address');
2490  } else {
2491  $msg->parameters['reply_to'] = $GLOBALS['SQ_SYSTEM']->currentUserId();
2492  }
2493  if ($schema->attr('schema_from_email_address') != '') {
2494  $msg->from = $schema->attr('schema_from_email_address');
2495  }
2496 
2497  $msg->type = 'asset.workflow.invitation.progress';
2498  $msg->parameters['assetid'] = $asset->id;
2499 
2500  // Does this step have a custom message (for inviting users to the next step)?
2501  if (isset($current_step['message_invitation']) && trim($current_step['message_invitation']) !== '' ) {
2502  $msg->body = trim($current_step['message_invitation']);
2503  }
2504 
2505  // Does this step have a custom subject (for inviting users to the next step)?
2506  if (isset($current_step['subject_invitation']) && trim($current_step['subject_invitation']) !== '' ) {
2507  $subject = trim(array_get_index($current_step, 'subject_invitation', 'Workflow Approval Required'));
2508  $msg->subject = (!empty($subject)) ? $subject : 'Workflow Approval Required';
2509  }
2510 
2511  $msg->replacements = $msg_reps;
2512  $this->addMessageReplacements($asset, $msg->subject, $msg->body, $msg->replacements);
2513  $ms->enqueueMessage($msg);
2514  }//end else
2515  }//end if
2516 
2517  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2518  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2519 
2520  return TRUE;
2521 
2522  }//end escalateWorkflow()
2523 
2524 
2534  function &getCurrentStepArray(&$workflow, $current_step=NULL)
2535  {
2536  // go through workflow and return the current and sibling steps based on the current step
2537  if ($current_step == NULL) {
2538  $current_step = $workflow['current_step'];
2539  }
2540 
2541  array_pop($current_step);
2542  $steps =& $workflow['steps'];
2543  foreach ($current_step as $step_id) {
2544  $steps =& $steps[$step_id]['escalation_steps'];
2545  }
2546  return $steps;
2547 
2548  }//end getCurrentStepArray()
2549 
2550 
2560  function &getCurrentStep(&$workflow, $current_step=NULL)
2561  {
2562  if ($current_step == NULL) {
2563  $current_step = $workflow['current_step'];
2564  }
2565  $steps =& $this->getCurrentStepArray($workflow, $current_step);
2566 
2567  return $steps[end($current_step)];
2568 
2569  }//end getCurrentStep()
2570 
2571 
2583  public function getStreams($schemaid, $ignore_default=FALSE)
2584  {
2585  $stream_names = Array();
2586  if (!$GLOBALS['SQ_SYSTEM']->am->assetExists($schemaid)) {
2587  trigger_error("Workflow Schema Id #$schemaid is applied to this asset but cannot be found in system anymore.", E_USER_WARNING);
2588  return $stream_names;
2589  }
2590  $stream_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($schemaid, SQ_LINK_TYPE_2, 'workflow_stream', FALSE);
2591  $streamids = Array();
2592 
2593  foreach ($stream_links as $stream_link) {
2594  if (($ignore_default === FALSE) || ($stream_link['value'] !== 'default_stream')) {
2595  $streamids[] = $stream_link['minorid'];
2596  }
2597  }
2598 
2599  $stream_names = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($streamids, 'workflow_stream', TRUE, 'name');
2600  return $stream_names;
2601 
2602  }//end getStreams()
2603 
2604 
2613  public function getBypassableStreams($assetid, $schemaid, $current_userid=NULL)
2614  {
2615  if ($current_userid === NULL) {
2616  $current_userid = $GLOBALS['SQ_SYSTEM']->user->id;
2617  }
2618 
2619  $bypassable_streams = Array();
2620  $streams = $this->getStreams($schemaid);
2621  foreach ($streams as $streamid => $stream_name) {
2622  $test_result = $this->testPublish($assetid, $current_userid, $stream_name);
2623  if ($test_result === TRUE) {
2624  $bypassable_streams[] = $streamid;
2625  }
2626  }
2627 
2628  return $bypassable_streams;
2629 
2630  }//end getBypassableStreams()
2631 
2632 
2644  public function getDefaultStream($schemaids)
2645  {
2646  // Did we receive a single schema?
2647  $return_array = TRUE;
2648  if (is_array($schemaids) === FALSE) {
2649  $return_array = FALSE;
2650  $schemaids = Array($schemaids);
2651  }
2652 
2653  $return_value = Array();
2654  foreach ($schemaids as $schemaid) {
2655  $stream_link = $GLOBALS['SQ_SYSTEM']->am->getLink($schemaid, SQ_LINK_TYPE_2, 'workflow_stream', FALSE, 'default_stream');
2656 
2657  if (empty($stream_link) === FALSE) {
2658  $return_value[$schemaid] = $stream_link['minorid'];
2659  }
2660  }
2661 
2662  if ($return_array === FALSE) {
2663  $return_value = reset($return_value);
2664  }
2665 
2666  return $return_value;
2667 
2668  }//end getDefaultStream()
2669 
2670 
2686  public function getStreamByName($schemaids, $stream_name)
2687  {
2688  // Did we receive a single schema?
2689  $return_array = TRUE;
2690  if (is_array($schemaids) === FALSE) {
2691  $return_array = FALSE;
2692  $schemaids = Array($schemaids);
2693  }
2694 
2695  // Find the stream name if it exists
2696  $return_value = Array();
2697  foreach ($schemaids as $schemaid) {
2698  $schema_stream_names = $this->getStreams($schemaid);
2699  $found_streamid = array_search($stream_name, $schema_stream_names);
2700  if ($found_streamid !== FALSE) {
2701  $return_value[$schemaid] = $found_streamid;
2702  }
2703  }
2704 
2705  // Now add the default stream for these
2706  $unfound_streams = array_diff($schemaids, array_keys($return_value));
2707  if (count($unfound_streams) > 0) {
2708  $unfound_streamids = $this->getDefaultStream($unfound_streams);
2709  $return_value += $unfound_streamids;
2710  }
2711 
2712  if ($return_array === FALSE) {
2713  $return_value1 = reset($return_value);
2714  }
2715 
2716  return $return_value;
2717 
2718  }//end getStreamByName()
2719 
2720 
2743  public function setStartingStream($assetid, $stream_name)
2744  {
2745  $this->_tmp['starting_streams'][$assetid] = $stream_name;
2746 
2747  }//end setStartingStream()
2748 
2749 
2757  public function getStartingStream($assetid)
2758  {
2759  $starting_streams = array_get_index($this->_tmp, 'starting_streams', Array());
2760  return array_get_index($starting_streams, $assetid, NULL);
2761 
2762  }//end getStartingStream()
2763 
2764 
2776  public function addMessageReplacements($asset, $subject, $body, &$replacements)
2777  {
2778 
2779  require_once SQ_FUDGE_PATH.'/general/text.inc';
2780  $keywords = retrieve_keywords_replacements($subject);
2781  foreach ($keywords as $keyword) {
2782  if (isset($replacements[$keyword])) continue;
2783  $replacement = $asset->getKeywordReplacement($keyword);
2784  if (!empty($replacement) && trim($replacement, '%') != $keyword) $replacements[$keyword] = $replacement;
2785  }
2786 
2787  $keywords = retrieve_keywords_replacements($body);
2788  foreach ($keywords as $keyword) {
2789  if (isset($replacements[$keyword])) continue;
2790  $replacement = $asset->getKeywordReplacement($keyword);
2791  if (!empty($replacement) && trim($replacement, '%') != $keyword) $replacements[$keyword] = $replacement;
2792  }
2793 
2794  }//end addMessageReplacements()
2795 
2796 
2809  public function setCurrentUserAsLastStarted($userid, $assetid)
2810  {
2811  // we need the workflow on this asset that aren't running
2812  $schemas = $this->getSchemas($assetid, TRUE, FALSE);
2813 
2814  $sql = "UPDATE sq_ast_wflow
2815  SET last_started_by = :last_started_by
2816  WHERE schemaid = :schemaid AND assetid = :assetid";
2817 
2818  foreach ($schemas as $schemaid) {
2819  try {
2820  $query = MatrixDAL::preparePdoQuery($sql);
2821  MatrixDAL::bindValueToPdo($query, 'last_started_by', $userid);
2822  MatrixDAL::bindValueToPdo($query, 'schemaid', $schemaid);
2823  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
2824  MatrixDAL::execPdoQuery($query);
2825  } catch (Exception $e) {
2826  trigger_localised_error('CORE0320', E_USER_NOTICE, $assetid, $schemaid, $e->getMessage());
2827  continue;
2828  }//end try/catch
2829  }//end foreach
2830 
2831  }//end setCurrentUserAsLastStarted()
2832 
2833 
2834 }//end class
2835 
2836 ?>