source: sipes/modules_contrib/workflow/workflow.module @ ef72343

stableversion-3.0
Last change on this file since ef72343 was 177a560, checked in by José Gregorio Puentes <jpuentes@…>, 8 años ago

se agrego el directorio de modulos contribuidos de drupal

  • Propiedad mode establecida a 100644
File size: 56.4 KB
Línea 
1<?php
2// $Id: workflow.module,v 1.83.2.18 2010/10/25 02:13:48 jvandyk Exp $
3
4/**
5 * @file
6 * Support workflows made up of arbitrary states.
7 */
8
9define('WORKFLOW_CREATION', 1);
10define('WORKFLOW_CREATION_DEFAULT_WEIGHT', -50);
11define('WORKFLOW_DELETION', 0);
12define('WORKFLOW_ARROW', '&#8594;');
13
14/**
15 * Implementation of hook_help().
16 */
17function workflow_help($path, $arg) {
18  switch ($path) {
19    case 'admin/build/workflow/edit/%':
20      return t('You are currently viewing the possible transitions to and from workflow states. The state is shown in the left column; the state to be moved to is to the right. For each transition, check the box next to the role(s) that may initiate the transition. For example, if only the "production editor" role may move a node from Review state to the Published state, check the box next to "production editor". The author role is built in and refers to the user who authored the node.');
21    case 'admin/build/workflow/add':
22      return t('To get started, provide a name for your workflow. This name will be used as a label when the workflow status is shown during node editing.');
23    case 'admin/build/workflow/state':
24      return t('Enter the name for a state in your workflow. For example, if you were doing a meal workflow it may include states like <em>shop</em>, <em>prepare</em>, <em>eat</em>, and <em>clean up</em>.');
25    case 'admin/build/trigger/workflow':
26      return t('Use this page to set actions to happen when transitions occur. To configure actions, use the <a href="@link">actions settings page</a>.', array('@link' => url('admin/settings/actions')));
27  }
28}
29
30/**
31 * Implementation of hook_perm().
32 */
33function workflow_perm() {
34  return array('administer workflow', 'schedule workflow transitions', 'access workflow summary views');
35}
36
37/**
38 * Implementation of hook_menu().
39 */
40function workflow_menu() {
41  $items['admin/build/workflow'] = array(
42    'title' => 'Workflow',
43    'access arguments' => array('administer workflow'),
44    'page callback' => 'workflow_overview',
45    'description' => 'Allows the creation and assignment of arbitrary workflows to node types.',
46    'file' => 'workflow.admin.inc',
47  );
48  $items['admin/build/workflow/edit/%workflow'] = array(
49    'title' => 'Edit workflow',
50    'type' => MENU_CALLBACK,
51    'access arguments' => array('administer workflow'),
52    'page callback' => 'drupal_get_form',
53    'page arguments' => array('workflow_edit_form', 4),
54    'file' => 'workflow.admin.inc',
55  );
56  $items['admin/build/workflow/list'] = array(
57    'title' => 'List',
58    'weight' => -10,
59    'access arguments' => array('administer workflow'),
60    'page callback' => 'workflow_overview',
61    'file' => 'workflow.admin.inc',
62    'type' => MENU_DEFAULT_LOCAL_TASK,
63  );
64  $items['admin/build/workflow/add'] = array(
65    'title' => 'Add workflow',
66    'weight' => -8,
67    'access arguments' => array('administer workflow'),
68    'page callback' => 'drupal_get_form',
69    'page arguments' => array('workflow_add_form'),
70    'file' => 'workflow.admin.inc',
71    'type' => MENU_LOCAL_TASK,
72  );
73  $items['admin/build/workflow/state'] = array(
74    'title' => 'Add state',
75    'type' => MENU_CALLBACK,
76    'access arguments' => array('administer workflow'),
77    'page callback' => 'drupal_get_form',
78    'page arguments' => array('workflow_state_add_form'),
79    'file' => 'workflow.admin.inc',
80  );
81  $items['admin/build/workflow/state/delete'] = array(
82    'title' => 'Delete State',
83    'type' => MENU_CALLBACK,
84    'access arguments' => array('administer workflow'),
85    'page callback' => 'drupal_get_form',
86    'page arguments' => array('workflow_state_delete_form'),
87    'file' => 'workflow.admin.inc',
88  );
89  $items['admin/build/workflow/delete'] = array(
90    'title' => 'Delete workflow',
91    'type' => MENU_CALLBACK,
92    'access arguments' => array('administer workflow'),
93    'page callback' => 'drupal_get_form',
94    'page arguments' => array('workflow_delete_form'),
95    'file' => 'workflow.admin.inc',
96  );
97  $items['node/%node/workflow'] = array(
98    'title' => 'Workflow',
99    'type' => MENU_LOCAL_TASK,
100    'access callback' => 'workflow_node_tab_access',
101    'access arguments' => array(1),
102    'page callback' => 'workflow_tab_page',
103    'page arguments' => array(1),
104    'file' => 'workflow.pages.inc',
105    'weight' => 2,
106  );
107  return $items;
108}
109
110/**
111 * Menu access control callback. Determine access to Workflow tab.
112 */
113function workflow_node_tab_access($node = NULL) {
114  global $user;
115  $wid = workflow_get_workflow_for_type($node->type);
116  if ($wid === FALSE) {
117    // No workflow associated with this node type.
118    return FALSE;
119  }
120  $roles = array_keys($user->roles);
121  if ($node->uid == $user->uid) {
122    $roles = array_merge(array('author'), $roles);
123  }
124  $workflow = db_fetch_object(db_query("SELECT * FROM {workflows} WHERE wid = %d", $wid));
125  $allowed_roles = $workflow->tab_roles ? explode(',', $workflow->tab_roles) : array();
126
127  if (user_access('administer nodes') || array_intersect($roles, $allowed_roles)) {
128    return TRUE;
129  }
130  else {
131    return FALSE;
132  }
133}
134
135/**
136 * Implementation of hook_theme().
137 */
138function workflow_theme() {
139  return array(
140    'workflow_edit_form' => array(
141      'arguments' => array(
142        'form' => array(),
143      ),
144    ),
145    'workflow_types_form' => array(
146      'arguments' => array(
147        'form' => array(),
148      ),
149    ),
150    'workflow_actions_form' => array(
151      'arguments' => array(
152        'form' => array()
153      ),
154    ),
155    'workflow_history_table_row' => array(
156      'arguments' => array(
157        'history' => NULL,
158        'old_state_name' => NULL,
159        'state_name' => null
160      ),
161    ),
162    'workflow_history_table' => array(
163      'arguments' => array(
164        'rows' => array(),
165        'footer' => NULL,
166      ),
167    ),
168    'workflow_current_state' => array(
169      'arguments' => array(
170        'state_name' => NULL,
171      ),
172    ),
173    'workflow_deleted_state' => array(
174      'arguments' => array(
175        'state_name' => NULL,
176      ),
177    ),
178    'workflow_permissions' => array(
179      'arguments' => array(
180        'header' => array(),
181        'all' => array(),
182      ),
183    ),
184  );
185}
186
187/**
188 * Implementation of hook_views_api().
189 */
190function workflow_views_api() {
191  return array(
192    'api' => 2,
193    'path' => drupal_get_path('module', 'workflow') .'/includes',
194  );
195}
196
197/**
198 * Implementation of hook_nodeapi().
199 */
200function workflow_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
201  switch ($op) {
202    case 'load':
203      $node->_workflow = workflow_node_current_state($node);
204
205      // Add scheduling information.
206      $res = db_query('SELECT * FROM {workflow_scheduled_transition} WHERE nid = %d', $node->nid);
207      if ($row = db_fetch_object($res)) {
208        $node->_workflow_scheduled_sid = $row->sid;
209        $node->_workflow_scheduled_timestamp = $row->scheduled;
210        $node->_workflow_scheduled_comment = $row->comment;
211      }
212      break;
213
214    case 'insert':
215      // If the state is not specified, use first valid state.
216      // For example, a new node must move from (creation) to some
217      // initial state.
218      if (empty($node->workflow)) {
219        $choices = workflow_field_choices($node);
220        $keys = array_keys($choices);
221        $sid = array_shift($keys);
222      }
223      // Note no break; fall through to 'update' case.
224    case 'update':
225      // Do nothing if there is no workflow for this node type.
226      $wid = workflow_get_workflow_for_type($node->type);
227      if (!$wid) {
228        break;
229      }
230
231      // Get new state from value of workflow form field, stored in $node->workflow.
232      if (!isset($sid)) {
233        $sid = $node->workflow;
234      }
235
236      workflow_transition($node, $sid);
237      break;
238
239    case 'delete':
240      $node->workflow_stamp = time();
241      db_query("DELETE FROM {workflow_node} WHERE nid = %d", $node->nid);
242      _workflow_write_history($node, WORKFLOW_DELETION, t('Node deleted'));
243      // Delete any scheduled transitions for this node.
244      db_query("DELETE FROM {workflow_scheduled_transition} WHERE nid = %d", $node->nid);
245      break;
246  }
247}
248
249/**
250 * Implementation of hook_comment().
251 */
252function workflow_comment($a1, $op) {
253  if (($op == 'insert' || $op == 'update') && isset($a1['workflow'])) {
254    $node = node_load($a1['nid']);
255    $sid = $a1['workflow'];
256    $node->workflow_comment = $a1['workflow_comment'];
257    if (isset($a1['workflow_scheduled'])) {
258      $node->workflow_scheduled = $a1['workflow_scheduled'];
259      $node->workflow_scheduled_date = $a1['workflow_scheduled_date'];
260      $node->workflow_scheduled_hour = $a1['workflow_scheduled_hour'];
261    }
262    workflow_transition($node, $sid);
263  }
264}
265
266/**
267 * Validate target state and either execute a transition immediately or schedule
268 * a transition to be executed later by cron.
269 *
270 * @param $node
271 * @param $sid
272 *   An integer; the target state ID.
273 */
274function workflow_transition($node, $sid) {
275  // Make sure new state is a valid choice.
276  if (array_key_exists($sid, workflow_field_choices($node))) {
277    $node->workflow_scheduled = isset($node->workflow_scheduled) ? $node->workflow_scheduled : FALSE;
278    if (!$node->workflow_scheduled) {
279      // It's an immediate change. Do the transition.
280      workflow_execute_transition($node, $sid, isset($node->workflow_comment) ? $node->workflow_comment : NULL);
281    }
282    else {
283      // Schedule the the time to change the state.
284      $comment = $node->workflow_comment;
285      $old_sid = workflow_node_current_state($node);
286
287      if ($node->workflow_scheduled_date['day'] < 10) {
288        $node->workflow_scheduled_date['day'] = '0' .
289        $node->workflow_scheduled_date['day'];
290      }
291
292      if ($node->workflow_scheduled_date['month'] < 10) {
293        $node->workflow_scheduled_date['month'] = '0' .
294        $node->workflow_scheduled_date['month'];
295      }
296
297      if (!$node->workflow_scheduled_hour) {
298        $node->workflow_scheduled_hour = '00:00';
299      }
300
301      $scheduled = $node->workflow_scheduled_date['year'] . $node->workflow_scheduled_date['month'] . $node->workflow_scheduled_date['day'] . ' ' . $node->workflow_scheduled_hour . 'Z';
302      if ($scheduled = strtotime($scheduled)) {
303        // Adjust for user and site timezone settings.
304        global $user;
305        if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
306          $timezone = $user->timezone;
307        }
308        else {
309          $timezone = variable_get('date_default_timezone', 0);
310        }
311        $scheduled = $scheduled - $timezone;
312
313        // Clear previous entries and insert.
314        db_query("DELETE FROM {workflow_scheduled_transition} WHERE nid = %d", $node->nid);
315        db_query("INSERT INTO {workflow_scheduled_transition} VALUES (%d, %d, %d, %d, '%s')", $node->nid, $old_sid, $sid, $scheduled, $comment);
316
317        // Get name of state.
318        $state_name = workflow_get_state_name($sid);
319        watchdog('workflow', '@node_title scheduled for state change to %state_name on !scheduled_date', array('@node_title' => $node->title, '%state_name' => $state_name, '!scheduled_date' => format_date($scheduled)), WATCHDOG_NOTICE, l('view', "node/$node->nid/workflow"));
320        drupal_set_message(t('@node_title is scheduled for state change to %state_name on !scheduled_date', array('@node_title' => $node->title, '%state_name' => $state_name, '!scheduled_date' => format_date($scheduled))));
321      }
322    }
323  }
324}
325
326/**
327 * Form builder. Add form widgets for workflow change to $form.
328 *
329 * This builder is factored out of workflow_form_alter() because
330 * it is also used on the Workflow tab.
331 *
332 * @param $form
333 *   An existing form definition array.
334 * @param $name
335 *   The name of the workflow.
336 * @param $current
337 *   The state ID of the current state, used as the default value.
338 * @param $choices
339 *   An array of possible target states.
340 */
341function workflow_node_form(&$form, $form_state, $title, $name, $current, $choices, $timestamp = NULL, $comment = NULL) {
342  // No sense displaying choices if there is only one choice.
343  if (sizeof($choices) == 1) {
344    $form['workflow'][$name] = array(
345      '#type' => 'hidden',
346      '#value' => $current
347    );
348  }
349  else {
350    $form['workflow'][$name] = array(
351      '#type' => 'radios',
352      '#title' => $form['#wf']->options['name_as_title'] ? $title : '',
353      '#options' => $choices,
354      '#name' => $name,
355      '#parents' => array('workflow'),
356      '#default_value' => $current
357    );
358
359    // Display scheduling form only if a node is being edited and user has
360    // permission. State change cannot be scheduled at node creation because
361    // that leaves the node in the (creation) state.
362    if (!(arg(0) == 'node' && arg(1) == 'add') && user_access('schedule workflow transitions')) {
363      $scheduled = $timestamp ? 1 : 0;
364      $timestamp = $scheduled ? $timestamp : time();
365
366      $form['workflow']['workflow_scheduled'] = array(
367        '#type' => 'radios',
368        '#title' => t('Schedule'),
369        '#options' => array(
370          t('Immediately'),
371          t('Schedule for state change at:'),
372        ),
373        '#default_value' => isset($form_state['values']['workflow_scheduled']) ? $form_state['values']['workflow_scheduled'] : $scheduled,
374      );
375
376      $form['workflow']['workflow_scheduled_date'] = array(
377        '#type' => 'date',
378        '#default_value' => array(
379          'day'   => isset($form_state['values']['workflow_scheduled_date']['day']) ? $form_state['values']['workflow_scheduled_date']['day'] : format_date($timestamp, 'custom', 'j'),
380          'month' => isset($form_state['values']['workflow_scheduled_date']['month']) ? $form_state['values']['workflow_scheduled_date']['month'] :format_date($timestamp, 'custom', 'n'),
381          'year'  => isset($form_state['values']['workflow_scheduled_date']['year']) ? $form_state['values']['workflow_scheduled_date']['year'] : format_date($timestamp, 'custom', 'Y')
382        ),
383      );
384
385      $hours = format_date($timestamp, 'custom', 'H:i');
386      $form['workflow']['workflow_scheduled_hour'] = array(
387        '#type' => 'textfield',
388        '#description' => t('Please enter a time in 24 hour (eg. HH:MM) format. If no time is included, the default will be midnight on the specified date. The current time is: ') . format_date(time()),
389        '#default_value' => $scheduled ? (isset($form_state['values']['workflow_scheduled_hour']) ? $form_state['values']['workflow_scheduled_hour'] : $hours) : NULL,
390      );
391    }
392    if (isset($form['#tab'])) {
393      $determiner = 'comment_log_tab';
394    }
395    else {
396      $determiner = 'comment_log_node';
397    }
398    $form['workflow']['workflow_comment'] = array(
399      '#type' => $form['#wf']->options[$determiner] ? 'textarea': 'hidden',
400      '#title' => t('Comment'),
401      '#description' => t('A comment to put in the workflow log.'),
402      '#default_value' => $comment,
403      '#rows' => 2,
404    );
405  }
406}
407
408/**
409 * Implementation of hook_form_alter().
410 *
411 * @param object &$node
412 * @return array
413 */
414function workflow_form_alter(&$form, $form_state, $form_id) {
415  // Ignore all forms except comment forms and node editing forms.
416  if ($form_id == 'comment_form' || (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id)) {
417    if (isset($form['#node'])) {
418      $node = $form['#node'];
419      // Abort if no workflow is assigned to this node type.
420      if (!in_array('node', variable_get('workflow_' . $node->type, array('node')))) {
421        return;
422      }
423    }
424    else {
425      $type = db_result(db_query("SELECT type FROM {node} WHERE nid = %d", $form['nid']['#value']));
426      // Abort if user does not want to display workflow form on node editing form.
427      if (!in_array('comment', variable_get('workflow_' . $type, array('node')))) {
428        return;
429      }
430      $node = node_load($form['nid']['#value']);
431    }
432    $choices = workflow_field_choices($node);
433    $wid = workflow_get_workflow_for_type($node->type);
434    $states = workflow_get_states($wid);
435    // If this is a preview, the current state should come from
436    // the form values, not the node, as the user may have changed
437    // the state.
438    $current = isset($form_state['values']['workflow']) ? $form_state['values']['workflow'] : workflow_node_current_state($node);
439    $min = $states[$current] == t('(creation)') ? 1 : 2;
440    // Stop if user has no new target state(s) to choose.
441    if (count($choices) < $min) {
442      return;
443    }
444    $workflow = workflow_load($wid);
445    $form['#wf'] = $workflow;
446    $name = check_plain($workflow->name);
447
448    // If the current node state is not one of the choices, autoselect first choice.
449    // We know all states in $choices are states that user has permission to
450    // go to because workflow_field_choices() has already checked that.
451    if (!isset($choices[$current])) {
452      $array = array_keys($choices);
453      $current = $array[0];
454    }
455
456    if (sizeof($choices) > 1) {
457      $form['workflow'] = array(
458        '#type' => 'fieldset',
459        '#title' => $name,
460        '#collapsible' => TRUE,
461        '#collapsed' => FALSE,
462        '#weight' => 10,
463      );
464    }
465
466    $timestamp = NULL;
467    $comment = '';
468
469    // See if scheduling information is present.
470    if (isset($node->_workflow_scheduled_timestamp) && isset($node->_workflow_scheduled_sid)) {
471      // The default value should be the upcoming sid.
472      $current = $node->_workflow_scheduled_sid;
473      $timestamp = $node->_workflow_scheduled_timestamp;
474      $comment = $node->_workflow_scheduled_comment;
475    }
476
477    if (isset($form_state['values']['workflow_comment'])) {
478      $comment = $form_state['values']['workflow_comment'];
479    }
480
481    workflow_node_form($form, $form_state, $name, $name, $current, $choices, $timestamp, $comment);
482  }
483}
484
485/**
486 * Execute a transition (change state of a node).
487 *
488 * @param $node
489 * @param $sid
490 *   Target state ID.
491 * @param $comment
492 *   A comment for the node's workflow history.
493 * @param $force
494 *   If set to TRUE, workflow permissions will be ignored.
495 *
496 * @return int
497 *   ID of new state.
498 */
499function workflow_execute_transition($node, $sid, $comment = NULL, $force = FALSE) {
500  global $user;
501  $old_sid = workflow_node_current_state($node);
502  if ($old_sid == $sid) {
503    // Stop if not going to a different state.
504    // Write comment into history though.
505    if ($comment && !$node->_workflow_scheduled_comment) {
506      $node->workflow_stamp = time();
507      db_query("UPDATE {workflow_node} SET stamp = %d WHERE nid = %d", $node->workflow_stamp, $node->nid);
508      $result = module_invoke_all('workflow', 'transition pre', $old_sid, $sid, $node);
509      _workflow_write_history($node, $sid, $comment);
510      unset($node->workflow_comment);
511
512      $result = module_invoke_all('workflow', 'transition post', $old_sid, $sid, $node);
513      // Rules integration
514      if (module_exists('rules')) {
515        rules_invoke_event('workflow_comment_added', $node, $old_sid, $sid);
516      }
517    }
518    return;
519  }
520
521  $tid = workflow_get_transition_id($old_sid, $sid);
522  if (!$tid && !$force) {
523      watchdog('workflow', 'Attempt to go to nonexistent transition (from %old to %new)', array('%old' => $old_sid, '%new' => $sid, WATCHDOG_ERROR));
524      return;
525  }
526  // Make sure this transition is valid and allowed for the current user.
527  // Check allowability of state change if user is not superuser (might be cron).
528  if (($user->uid != 1) && !$force) {
529    if (!workflow_transition_allowed($tid, array_merge(array_keys($user->roles), array('author')))) {
530      watchdog('workflow', 'User %user not allowed to go from state %old to %new', array('%user' => $user->name, '%old' => $old_sid, '%new' => $sid, WATCHDOG_NOTICE));
531      return;
532    }
533  }
534  // Invoke a callback indicating a transition is about to occur. Modules
535  // may veto the transition by returning FALSE.
536  $result = module_invoke_all('workflow', 'transition pre', $old_sid, $sid, $node);
537
538  // Stop if a module says so.
539  if (in_array(FALSE, $result)) {
540    watchdog('workflow', 'Transition vetoed by module.');
541    return;
542  }
543
544  // If the node does not have an existing $node->_workflow property, save
545  // the $old_sid there so _workflow_write_history() can log it.
546  if (!isset($node->_workflow)) {
547    $node->_workflow = $old_sid;
548  }
549  // Change the state.
550  _workflow_node_to_state($node, $sid, $comment);
551  $node->_workflow = $sid;
552
553  // Register state change with watchdog.
554  $state_name = workflow_get_state_name($sid);
555  $type = node_get_types('name', $node->type);
556  watchdog('workflow', 'State of @type %node_title set to %state_name', array('@type' => $type, '%node_title' => $node->title, '%state_name' => $state_name), WATCHDOG_NOTICE, l('view', 'node/' . $node->nid));
557
558  // Notify modules that transition has occurred. Actions should take place
559  // in response to this callback, not the previous one.
560  module_invoke_all('workflow', 'transition post', $old_sid, $sid, $node);
561
562  // Clear any references in the scheduled listing.
563  db_query('DELETE FROM {workflow_scheduled_transition} WHERE nid = %d', $node->nid);
564
565  // Rules integration
566  if (module_exists('rules')) {
567    rules_invoke_event('workflow_state_changed', $node, $old_sid, $sid);
568  }
569
570  return $sid;
571}
572
573/**
574 * Implementation of hook_action_info().
575 */
576function workflow_action_info() {
577  return array(
578    'workflow_select_next_state_action' => array(
579      'type' => 'node',
580      'description' => t('Change workflow state of post to next state'),
581      'configurable' => FALSE,
582      'hooks' => array(
583        'nodeapi' => array('presave'),
584        'comment' => array('insert', 'update'),
585        'workflow' => array('any'),
586      ),
587    ),
588    'workflow_select_given_state_action' => array(
589      'type' => 'node',
590      'description' => t('Change workflow state of post to new state'),
591      'configurable' => TRUE,
592      'hooks' => array(
593        'nodeapi' => array('presave'),
594        'comment' => array('insert', 'update'),
595        'workflow' => array('any'),
596      ),
597    ),
598  );
599}
600
601/**
602 * Implementation of a Drupal action. Move a node to the next state in the workfow.
603 */
604function workflow_select_next_state_action($node, $context) {
605  // If this action is being fired because it's attached to a workflow transition
606  // then the node's new state (now its current state) should be in $node->workflow
607  // because that is where the value from the workflow form field is stored;
608  // otherwise the current state is placed in $node->_workflow by our nodeapi load.
609  if (!isset($node->workflow) && !isset($node->_workflow)) {
610    watchdog('workflow', 'Unable to get current workflow state of node %nid.', array('%nid' => $node->nid));
611    return;
612  }
613  $current_state = isset($node->workflow) ? $node->workflow : $node->_workflow;
614
615  // Get the node's new state.
616  $choices = workflow_field_choices($node);
617  foreach ($choices as $sid => $name) {
618    if (isset($flag)) {
619      $new_state = $sid;
620      $new_state_name = $name;
621      break;
622    }
623    if ($sid == $current_state) {
624      $flag = TRUE;
625    }
626  }
627
628  // Fire the transition.
629  workflow_execute_transition($node, $new_state);
630}
631
632/**
633 * Implementation of a Drupal action. Move a node to a specified state.
634 */
635function workflow_select_given_state_action($node, $context) {
636  $comment = t($context['workflow_comment'], array('%title' => check_plain($node->title), '%state' => check_plain($context['state_name'])));
637  workflow_execute_transition($node, $context['target_sid'], $comment, $context['force']);
638}
639
640/**
641 * Configuration form for "Change workflow state of post to new state" action.
642 *
643 * @see workflow_select_given_state_action()
644 */
645function workflow_select_given_state_action_form($context) {
646  $result = db_query("SELECT * FROM {workflow_states} ws LEFT JOIN {workflows} w ON ws.wid = w.wid WHERE ws.sysid = 0 AND ws.status = 1 ORDER BY ws.wid, ws.weight");
647  $previous_workflow = '';
648  $options = array();
649  while ($data = db_fetch_object($result)) {
650    $options[$data->name][$data->sid] = $data->state;
651  }
652  $form['target_sid'] = array(
653    '#type' => 'select',
654    '#title' => t('Target state'),
655    '#description' => t('Please select that state that should be assigned when this action runs.'),
656    '#default_value' => isset($context['target_sid']) ? $context['target_sid'] : '',
657    '#options' => $options,
658  );
659  $form['force'] = array(
660    '#type' => 'checkbox',
661    '#title' => t('Force transition'),
662    '#description' => t('If this box is checked, the new state will be assigned even if workflow permissions disallow it.'),
663    '#default_value' => isset($context['force']) ? $context['force'] : '',
664  );
665  $form['workflow_comment'] = array(
666    '#type' => 'textfield',
667    '#title' => t('Message'),
668    '#description' => t('This message will be written into the workflow history log when the action runs. You may include the following variables: %state, %title'),
669    '#default_value' => isset($context['workflow_history']) ? $context['workflow_history'] : t('Action set %title to %state.'),
670  );
671  return $form;
672}
673
674/**
675 * Submit handler for "Change workflow state of post to new state" action
676 * configuration form.
677 *
678 * @see workflow_select_given_state_action_form()
679 */
680function workflow_select_given_state_action_submit($form_id, $form_state) {
681  $state_name = workflow_get_state_name($form_state['values']['target_sid']);
682  return array(
683    'target_sid' => $form_state['values']['target_sid'],
684    'state_name' => $state_name,
685    'force' => $form_state['values']['force'],
686    'workflow_comment' => $form_state['values']['workflow_comment'],
687  );
688}
689
690/**
691 * Get the states current user can move to for a given node.
692 *
693 * @param object $node
694 *   The node to check.
695 * @return
696 *   Array of transitions.
697 */
698function workflow_field_choices($node) {
699  global $user;
700  $wid = workflow_get_workflow_for_type($node->type);
701  if (!$wid) {
702    // No workflow for this type.
703    return array();
704  }
705  $states = workflow_get_states($wid);
706  $roles = array_keys($user->roles);
707  $current_sid = workflow_node_current_state($node);
708
709  // If user is node author or this is a new page, give the authorship role.
710  if (($user->uid == $node->uid && $node->uid > 0) || (arg(0) == 'node' && arg(1) == 'add')) {
711    $roles += array('author' => 'author');
712  }
713  if ($user->uid == 1) {
714    // Superuser is special.
715    $roles = 'ALL';
716  }
717  $transitions = workflow_allowable_transitions($current_sid, 'to', $roles);
718
719  // Include current state if it is not the (creation) state.
720  if ($current_sid == _workflow_creation_state($wid)) {
721    unset($transitions[$current_sid]);
722  }
723  return $transitions;
724}
725
726/**
727 * Get the current state of a given node.
728 *
729 * @param $node
730 *   The node to check.
731 * @return
732 *   The ID of the current state.
733 */
734function workflow_node_current_state($node) {
735  $sid = FALSE;
736
737  // There is no nid when creating a node.
738  if (!empty($node->nid)) {
739    $sid = db_result(db_query('SELECT sid FROM {workflow_node} WHERE nid = %d', $node->nid));
740  }
741
742  if (!$sid && !empty($node->type)) {
743    // No current state. Use creation state.
744    $wid = workflow_get_workflow_for_type($node->type);
745    $sid = _workflow_creation_state($wid);
746  }
747  return $sid;
748}
749
750/**
751 * Return the ID of the creation state for this workflow.
752 *
753 * @param $wid
754 *   The ID of the workflow.
755 */
756function _workflow_creation_state($wid) {
757  static $cache;
758  if (!isset($cache[$wid])) {
759    $result = db_result(db_query("SELECT sid FROM {workflow_states} WHERE wid = %d AND sysid = %d", $wid, WORKFLOW_CREATION));
760    $cache[$wid] = $result;
761  }
762
763  return $cache[$wid];
764}
765
766/**
767 * Implementation of hook_workflow().
768 *
769 * @param $op
770 *   The current workflow operation: 'transition pre' or 'transition post'.
771 * @param $old_state
772 *   The state ID of the current state.
773 * @param  $new_state
774 *   The state ID of the new state.
775 * @param $node
776 *   The node whose workflow state is changing.
777 */
778function workflow_workflow($op, $old_state, $new_state, $node) {
779  switch ($op) {
780    case 'transition pre':
781      // The workflow module does nothing during this operation.
782      // But your module's implementation of the workflow hook could
783      // return FALSE here and veto the transition.
784      break;
785
786    case 'transition post':
787      // A transition has occurred; fire off actions associated with this transition.
788      // Can't fire actions if trigger module is not enabled.
789      if (!module_exists('trigger')) {
790        break;
791      }
792      $tid = workflow_get_transition_id($old_state, $new_state);
793      $op = 'workflow-'. $node->type .'-'. $tid;
794      $aids = _trigger_get_hook_aids('workflow', $op);
795      if ($aids) {
796        $context = array(
797          'hook' => 'workflow',
798          'op' => $op,
799        );
800
801        // We need to get the expected object if the action's type is not 'node'.
802        // We keep the object in $objects so we can reuse it if we have multiple actions
803        // that make changes to an object.
804        foreach ($aids as $aid => $action_info) {
805          if ($action_info['type'] != 'node') {
806            if (!isset($objects[$action_info['type']])) {
807              $objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
808            }
809            // Pass the node as the object for actions of type 'system'.
810            if (!isset($objects[$action_info['type']]) && $action_info['type'] == 'system') {
811              $objects[$action_info['type']] = $node;
812            }
813            // Since we know about the node, we pass that info along to the action.
814            $context['node'] = $node;
815            $result = actions_do($aid, $objects[$action_info['type']], $context);
816          }
817          else {
818            actions_do($aid, $node, $context);
819          }
820        }
821      }
822      break;
823  }
824}
825
826/**
827 * Load function.
828 *
829 * @param $wid
830 *   The ID of the workflow to load.
831 * @return $workflow
832 *   Object representing the workflow.
833 */
834function workflow_load($wid) {
835  $workflow = db_fetch_object(db_query('SELECT * FROM {workflows} WHERE wid = %d', $wid));
836  $workflow->options = unserialize($workflow->options);
837  return $workflow;
838}
839
840/**
841 * Update the transitions for a workflow.
842 *
843 * @param array $transitions
844 *   Transitions, for example:
845 *     18 => array(
846 *       20 => array(
847 *         'author' => 1,
848 *         1        => 0,
849 *         2        => 1,
850 *       )
851 *     )
852 *   means the transition from state 18 to state 20 can be executed by
853 *   the node author or a user in role 2. The $transitions array should
854 *   contain ALL transitions for the workflow.
855 */
856function workflow_update_transitions($transitions = array()) {
857  // Empty string is sometimes passed in instead of an array.
858  if (!$transitions) {
859    return;
860  }
861
862  foreach ($transitions as $from => $to_data) {
863    foreach ($to_data as $to => $role_data) {
864      foreach ($role_data as $role => $can_do) {
865        if ($can_do) {
866          workflow_transition_add_role($from, $to, $role);
867        }
868        else {
869          workflow_transition_delete_role($from, $to, $role);
870        }
871      }
872    }
873  }
874  db_query("DELETE FROM {workflow_transitions} WHERE roles = ''");
875}
876
877/**
878 * Add a role to the list of those allowed for a given transition.
879 *
880 * Add the transition if necessary.
881 *
882 * @param int $from
883 * @param int $to
884 * @param mixed $role
885 *   Int (role ID) or string ('author').
886 */
887function workflow_transition_add_role($from, $to, $role) {
888  $transition = array(
889    'sid' => $from,
890    'target_sid' => $to,
891    'roles' => $role,
892  );
893  $tid = workflow_get_transition_id($from, $to);
894  if ($tid) {
895    $roles = db_result(db_query("SELECT roles FROM {workflow_transitions} WHERE tid=%d", $tid));
896    $roles = explode(',', $roles);
897    if (array_search($role, $roles) === FALSE) {
898      $roles[] = $role;
899      $transition['roles'] = implode(',', $roles);
900      $transition['tid'] = $tid;
901      drupal_write_record('workflow_transitions', $transition, 'tid');
902    }
903  }
904  else {
905    drupal_write_record('workflow_transitions', $transition);
906  }
907}
908
909/**
910 * Remove a role from the list of those allowed for a given transition.
911 *
912 * @param int $tid
913 * @param mixed $role
914 *   Int (role ID) or string ('author').
915 */
916function workflow_transition_delete_role($from, $to, $role) {
917  $tid = workflow_get_transition_id($from, $to);
918  if ($tid) {
919    $roles = db_result(db_query("SELECT roles FROM {workflow_transitions} WHERE tid=%d", $tid));
920    $roles = explode(',', $roles);
921    if (($i = array_search($role, $roles)) !== FALSE) {
922      unset($roles[$i]);
923      db_query("UPDATE {workflow_transitions} SET roles='%s' WHERE tid=%d", implode(',', $roles), $tid);
924    }
925  }
926}
927
928/**
929 * See if a transition is allowed for a given role.
930 *
931 * @param int $tid
932 * @param mixed $role
933 *   A single role (int or string 'author') or array of roles.
934 * @return
935 *   TRUE if the role is allowed to do the transition.
936 */
937function workflow_transition_allowed($tid, $role = NULL) {
938  $allowed = db_result(db_query("SELECT roles FROM {workflow_transitions} WHERE tid = %d", $tid));
939  $allowed = explode(',', $allowed);
940  if ($role) {
941    if (!is_array($role)) {
942      $role = array($role);
943    }
944    return array_intersect($role, $allowed) ==  TRUE;
945  }
946}
947
948/**
949 * Tell caller whether a state is a protected system state, such as the creation state.
950 *
951 * @param $state
952 *   The name of the state to test
953 * @return
954 *   TRUE if the state is a system state.
955 */
956function workflow_is_system_state($state) {
957  static $states;
958  if (!isset($states)) {
959    $states = array(t('(creation)') => TRUE);
960  }
961  return isset($states[$state]);
962}
963
964/**
965 * Given the ID of a workflow, return its name.
966 *
967 * @param integer $wid
968 *   The ID of the workflow.
969 * @return string
970 *   The name of the workflow.
971 */
972function workflow_get_name($wid) {
973  return db_result(db_query("SELECT name FROM {workflows} WHERE wid = %d", $wid));
974}
975
976/**
977 * Get ID of a workflow for a node type.
978 *
979 * @param $type
980 *   Machine readable node type name, e.g. 'story'.
981 * @return int
982 *   The ID of the workflow or FALSE if no workflow is mapped to this type.
983 */
984function workflow_get_workflow_for_type($type) {
985  static $cache;
986  if(!isset($cache[$type])) {
987    $wid = db_result(db_query("SELECT wid FROM {workflow_type_map} WHERE type = '%s'", $type));
988    $cache[$type] = $wid;
989  }
990  else {
991    $wid = $cache[$type];
992  }
993  return $wid > 0 ? $wid : FALSE;
994}
995
996/**
997 * Get names and IDs of all workflows from the database.
998 *
999 * @return
1000 *   An array of workflows keyed by workflow ID.
1001 */
1002function workflow_get_all() {
1003  $workflows = array();
1004  $result = db_query("SELECT wid, name FROM {workflows} ORDER BY name ASC");
1005  while ($data = db_fetch_object($result)) {
1006    $workflows[$data->wid] = check_plain(t($data->name));
1007  }
1008  return $workflows;
1009}
1010
1011/**
1012 * Create a workflow and its (creation) state.
1013 *
1014 * @param $name
1015 *   The name of the workflow.
1016 */
1017function workflow_create($name) {
1018  $workflow = array(
1019    'name' => $name,
1020    'options' => serialize(array('comment_log_node' => 1, 'comment_log_tab' => 1)),
1021  );
1022  drupal_write_record('workflows', $workflow);
1023  workflow_state_save(array(
1024    'wid' => $workflow['wid'],
1025    'state' => t('(creation)'),
1026    'sysid' => WORKFLOW_CREATION,
1027    'weight' => WORKFLOW_CREATION_DEFAULT_WEIGHT));
1028  // Workflow creation affects tabs (local tasks), so force menu rebuild.
1029  menu_rebuild();
1030  return $workflow['wid'];
1031}
1032
1033/**
1034 * Save a workflow's name in the database.
1035 *
1036 * @param $wid
1037 *   The ID of the workflow.
1038 * @param $name
1039 *   The name of the workflow.
1040 * @param $tab_roles
1041 *   Array of role IDs allowed to see the workflow tab.
1042 * @param $options
1043 *   Array of key-value pairs that constitute various settings for
1044 *   this workflow. An example is whether to show the comment form
1045 *   on the workflow tab page or not.
1046 */
1047function workflow_update($wid, $name, $tab_roles, $options) {
1048  db_query("UPDATE {workflows} SET name = '%s', tab_roles = '%s', options = '%s' WHERE wid = %d", $name, implode(',', $tab_roles), serialize($options), $wid);
1049   // Workflow name change affects tabs (local tasks), so force menu rebuild.
1050  menu_rebuild();
1051}
1052
1053/**
1054 * Delete a workflow from the database. Deletes all states,
1055 * transitions and node type mappings, too. Removes workflow state
1056 * information from nodes participating in this workflow.
1057 *
1058 * @param $wid
1059 *   The ID of the workflow.
1060 */
1061function workflow_deletewf($wid) {
1062  $wf = workflow_get_name($wid);
1063  $result = db_query('SELECT sid FROM {workflow_states} WHERE wid = %d', $wid);
1064  while ($data = db_fetch_object($result)) {
1065    // Delete the state and any associated transitions and actions.
1066    workflow_state_delete($data->sid);
1067    db_query('DELETE FROM {workflow_node} WHERE sid = %d', $data->sid);
1068  }
1069  db_query("DELETE FROM {workflow_type_map} WHERE wid = %d", $wid);
1070  db_query('DELETE FROM {workflows} WHERE wid = %d', $wid);
1071  // Notify any interested modules.
1072  module_invoke_all('workflow', 'workflow delete', $wid, NULL, NULL);
1073  // Workflow deletion affects tabs (local tasks), so force menu rebuild.
1074  cache_clear_all('*', 'cache_menu', TRUE);
1075  menu_rebuild();
1076}
1077
1078/**
1079 * Load workflow states for a workflow from the database.
1080 * If $wid is not passed, all states for all workflows are given.
1081 * States that have been deleted are not included.
1082 *
1083 * @param $wid
1084 *   The ID of the workflow.
1085 *
1086 * @return
1087 *   An array of workflow states keyed by state ID.
1088 */
1089function workflow_get_states($wid = NULL) {
1090  $states = array();
1091  if (isset($wid)) {
1092    $result = db_query("SELECT sid, state FROM {workflow_states} WHERE wid = %d AND status = 1 ORDER BY weight, sid", $wid);
1093    while ($data = db_fetch_object($result)) {
1094      $states[$data->sid] = check_plain(t($data->state));
1095    }
1096  }
1097  else {
1098    $result = db_query("SELECT ws.sid, ws.state, w.name FROM {workflow_states} ws INNER JOIN {workflows} w ON ws.wid = w.wid WHERE status = 1 ORDER BY sid");
1099    while ($data = db_fetch_object($result)) {
1100      $states[$data->sid] = check_plain(t($data->name)) .': '. check_plain(t($data->state));
1101    }
1102  }
1103
1104  return $states;
1105}
1106
1107/**
1108 * Given the ID of a workflow state, return a keyed array representing the state.
1109 *
1110 * Note: this will retrieve states that have been deleted (their status key
1111 *   will be set to 0).
1112 *
1113 * @param $sid
1114 *   The ID of the workflow state.
1115 * @return
1116 *   A keyed array with all attributes of the state.
1117 */
1118function workflow_get_state($sid) {
1119  $state = array();
1120  $result = db_query('SELECT wid, state, weight, sysid, status FROM {workflow_states} WHERE sid = %d', $sid);
1121  // State IDs are unique, so there should be only one row.
1122  $data = db_fetch_object($result);
1123  $state['wid'] = $data->wid;
1124  $state['state'] = $data->state;
1125  $state['weight'] = $data->weight;
1126  $state['sysid'] = $data->sysid;
1127  $state['status'] = $data->status;
1128  return $state;
1129}
1130
1131/**
1132 * Given the ID of a state, return its name.
1133 *
1134 * @param integer $sid
1135 *   The ID of the workflow state.
1136 * @return string
1137 *   The name of the workflow state.
1138 */
1139function workflow_get_state_name($sid) {
1140  return db_result(db_query('SELECT state FROM {workflow_states} WHERE sid = %d', $sid));
1141}
1142
1143
1144/**
1145 * Add or update a workflow state to the database.
1146 *
1147 * @param $edit
1148 *   An array containing values for the new or updated workflow state.
1149 * @return
1150 *   The ID of the new or updated workflow state.
1151 */
1152function workflow_state_save($state) {
1153  if (!isset($state['sid'])) {
1154    drupal_write_record('workflow_states', $state);
1155  }
1156  else {
1157    drupal_write_record('workflow_states', $state, 'sid');
1158  }
1159
1160  return $state['sid'];
1161}
1162
1163/**
1164 * Delete a workflow state from the database, including any
1165 * transitions the state was involved in and any associations
1166 * with actions that were made to that transition.
1167 *
1168 * @param $sid
1169 *   The ID of the state to delete.
1170 * @param $new_sid
1171 *   Deleting a state will leave any nodes to which that state is assigned
1172 *   without a state. If $new_sid is given, it will be assigned to those
1173 *   orphaned nodes
1174 */
1175function workflow_state_delete($sid, $new_sid = NULL) {
1176  if ($new_sid) {
1177    // Assign nodes to new state so they are not orphaned.
1178    // A candidate for the batch API.
1179    $node = new stdClass();
1180    $node->workflow_stamp = time();
1181    $result = db_query("SELECT nid FROM {workflow_node} WHERE sid = %d", $sid);
1182    while ($data = db_fetch_object($result)) {
1183      $node->nid = $data->nid;
1184      $node->_workflow = $sid;
1185      _workflow_write_history($node, $new_sid, t('Previous state deleted'));
1186      db_query("UPDATE {workflow_node} SET sid = %d WHERE nid = %d AND sid = %d", $new_sid, $data->nid, $sid);
1187    }
1188  }
1189  else {
1190    // Go ahead and orphan nodes.
1191    db_query('DELETE from {workflow_node} WHERE sid = %d', $sid);
1192  }
1193
1194  // Find out which transitions this state is involved in.
1195  $preexisting = array();
1196  $result = db_query("SELECT sid, target_sid FROM {workflow_transitions} WHERE sid = %d OR target_sid = %d", $sid, $sid);
1197  while ($data = db_fetch_object($result)) {
1198   $preexisting[$data->sid][$data->target_sid] = TRUE;
1199  }
1200
1201  // Delete the transitions and associated actions, if any.
1202  foreach ($preexisting as $from => $array) {
1203    foreach (array_keys($array) as $target_id) {
1204      $tid = workflow_get_transition_id($from, $target_id);
1205      workflow_transition_delete($tid);
1206    }
1207  }
1208
1209  // Delete the state.
1210  db_query("UPDATE {workflow_states} SET status = 0 WHERE sid = %d", $sid);
1211  // Notify interested modules.
1212  module_invoke_all('workflow', 'state delete', $sid, NULL, NULL);
1213}
1214
1215/**
1216 * Delete a transition (and any associated actions).
1217 *
1218 * @param $tid
1219 *   The ID of the transition.
1220 */
1221function workflow_transition_delete($tid) {
1222  $actions = workflow_get_actions($tid);
1223  foreach ($actions as $aid => $type) {
1224    workflow_actions_remove($tid, $aid);
1225  }
1226  db_query("DELETE FROM {workflow_transitions} WHERE tid = %d", $tid);
1227  // Notify interested modules.
1228  module_invoke_all('workflow', 'transition delete', $tid, NULL, NULL);
1229}
1230
1231/**
1232 * Get allowable transitions for a given workflow state. Typical use:
1233 *
1234 * global $user;
1235 * $possible = workflow_allowable_transitions($sid, 'to', $user->roles);
1236 *
1237 * If the state ID corresponded to the state named "Draft", $possible now
1238 * contains the states that the current user may move to from the Draft state.
1239 *
1240 * @param $sid
1241 *   The ID of the state in question.
1242 * @param $dir
1243 *   The direction of the transition: 'to' or 'from' the state denoted by $sid.
1244 *   When set to 'to' all the allowable states that may be moved to are
1245 *   returned; when set to 'from' all the allowable states that may move to the
1246 *   current state are returned.
1247 * @param mixed $roles
1248 *   Array of ints (and possibly the string 'author') representing the user's
1249 *   roles. If the string 'ALL' is passed (instead of an array) the role
1250 *   constraint is ignored (this is the default for backwards compatibility).
1251 *
1252 * @return
1253 *   Associative array of states ($sid => $state_name pairs), excluding current state.
1254 */
1255function workflow_allowable_transitions($sid, $dir = 'to', $roles = 'ALL') {
1256  $transitions = array();
1257
1258  if ($dir == 'to') {
1259    $field = 'target_sid';
1260    $field_where = 'sid';
1261  }
1262  else {
1263    $field = 'sid';
1264    $field_where = 'target_sid';
1265  }
1266
1267  $result = db_query(
1268    "(SELECT t.tid, t.%s as state_id, s.state AS state_name, s.weight AS state_weight "
1269    . "FROM {workflow_transitions} t "
1270    . "INNER JOIN {workflow_states} s "
1271    . "ON s.sid = t.%s "
1272    . "WHERE t.%s = %d AND s.status = 1 "
1273    . "ORDER BY state_weight) "
1274    . "UNION "
1275    . "(SELECT s.sid as tid, s.sid as state_id, s.state as state_name, s.weight as state_weight "
1276    . "FROM {workflow_states} s "
1277    . "WHERE s.sid = %d AND s.status = 1) "
1278    . "ORDER BY state_weight, state_id", $field, $field, $field_where, $sid, $sid);
1279  while ($t = db_fetch_object($result)) {
1280    if ($roles == 'ALL'  // Superuser.
1281      || $sid == $t->state_id // Include current state for same-state transitions.
1282      || workflow_transition_allowed($t->tid, $roles)) {
1283      $transitions[$t->state_id] = check_plain(t($t->state_name));
1284    }
1285  }
1286
1287  return $transitions;
1288}
1289
1290/**
1291 * Save mapping of workflow to node type. E.g., "the story node type
1292 * is using the Foo workflow."
1293 *
1294 * @param $form_state['values']
1295 */
1296function workflow_types_save($form_values) {
1297  db_query("DELETE FROM {workflow_type_map}");
1298  $node_types = node_get_types();
1299  foreach ($node_types as $type => $name) {
1300    db_query("INSERT INTO {workflow_type_map} (type, wid) VALUES ('%s', %d)", $type, $form_values[$type]['workflow']);
1301    variable_set('workflow_' . $type, array_keys(array_filter(($form_values[$type]['placement']))));
1302  }
1303}
1304
1305/**
1306 * Get the actions associated with a given transition.
1307 *
1308 * @see _trigger_get_hook_aids()
1309 *
1310 * @param int $tid
1311 *   ID of transition.
1312 * @return array
1313 *   Array of action ids in the same format as _trigger_get_hook_aids().
1314 */
1315function workflow_get_actions($tid) {
1316  $aids = array();
1317  if (!module_exists('trigger')) {
1318    watchdog('workflow', 'Unable to get actions associated with a transition because the trigger module is not enabled.', array(), WATCHDOG_WARNING);
1319    return $aids;
1320  }
1321
1322  $result = db_query("SELECT op FROM {trigger_assignments} WHERE hook = 'workflow'");
1323  while ($data = db_fetch_object($result)) {
1324    // Transition ID is the last part, e.g., foo-bar-1.
1325    $transition = array_pop(explode('-', $data->op));
1326    if ($tid == $transition) {
1327      $results = db_query("SELECT aa.aid, a.type FROM {trigger_assignments} aa LEFT JOIN {actions} a ON aa.aid = a.aid WHERE aa.hook = '%s' AND aa.op = '%s' ORDER BY weight", 'workflow', $data->op);
1328      while ($action = db_fetch_object($results)) {
1329        $aids[$action->aid]['type'] = $action->type;
1330      }
1331    }
1332  }
1333
1334  return $aids;
1335}
1336
1337/**
1338 * Get the tid of a transition, if it exists.
1339 *
1340 * @param int $from
1341 *   ID (sid) of originating state.
1342 * @param int $to
1343 *   ID (sid) of target state.
1344 * @return int
1345 *   Tid or FALSE if no such transition exists.
1346 */
1347function workflow_get_transition_id($from, $to) {
1348  return db_result(db_query("SELECT tid FROM {workflow_transitions} WHERE sid= %d AND target_sid= %d", $from, $to));
1349}
1350
1351/**
1352 * Remove an action assignment programmatically.
1353 *
1354 * Helpful when deleting a workflow.
1355 *
1356 * @see workflow_transition_delete()
1357 *
1358 * @param $tid
1359 *   Transition ID.
1360 * @param $aid
1361 *   Action ID.
1362 */
1363function workflow_actions_remove($tid, $aid) {
1364  $ops = array();
1365  $result = db_query("SELECT op FROM {trigger_assignments} WHERE hook = 'workflow' AND aid = '%s'", $aid);
1366  while ($data = db_fetch_object($result)) {
1367    // Transition ID is the last part, e.g., foo-bar-1.
1368    $transition = array_pop(explode('-', $data->op));
1369    if ($tid == $transition) {
1370      $ops[] = $data->op;
1371    }
1372  }
1373
1374  foreach ($ops as $op) {
1375    db_query("DELETE FROM {trigger_assignments} WHERE hook = 'workflow' AND op = '%s' AND aid = '%s'", $op, $aid);
1376    $description = db_result(db_query("SELECT description FROM {actions} WHERE aid = '%s'", $aid));
1377    watchdog('workflow', 'Action %action has been unassigned.',  array('%action' => $description));
1378  }
1379}
1380
1381/**
1382 * Put a node into a state.
1383 * No permission checking here; only call this from other functions that know
1384 * what they're doing.
1385 *
1386 * @see workflow_execute_transition()
1387 *
1388 * @param object $node
1389 * @param int $sid
1390 */
1391function _workflow_node_to_state($node, $sid, $comment = NULL) {
1392  global $user;
1393  $node->workflow_stamp = time();
1394  if (db_result(db_query("SELECT nid FROM {workflow_node} WHERE nid = %d", $node->nid))) {
1395    db_query("UPDATE {workflow_node} SET sid = %d, uid = %d, stamp = %d WHERE nid = %d", $sid, $user->uid, $node->workflow_stamp, $node->nid);
1396  }
1397  else {
1398    db_query("INSERT INTO {workflow_node} (nid, sid, uid, stamp) VALUES (%d, %d, %d, %d)", $node->nid, $sid, $user->uid, $node->workflow_stamp);
1399  }
1400  _workflow_write_history($node, $sid, $comment);
1401}
1402
1403function _workflow_write_history($node, $sid, $comment) {
1404  global $user;
1405  db_query("INSERT INTO {workflow_node_history} (nid, old_sid, sid, uid, comment, stamp) VALUES (%d, %d, %d, %d, '%s', %d)", $node->nid, $node->_workflow, $sid, $user->uid, $comment, $node->workflow_stamp);
1406}
1407
1408/**
1409 * Get a list of roles.
1410 *
1411 * @return
1412 *   Array of role names keyed by role ID, including the 'author' role.
1413 */
1414function workflow_get_roles() {
1415  static $roles = NULL;
1416  if (!$roles) {
1417    $result = db_query('SELECT * FROM {role} ORDER BY name');
1418    $roles = array('author' => 'author');
1419    while ($data = db_fetch_object($result)) {
1420      $roles[$data->rid] = check_plain($data->name);
1421    }
1422  }
1423  return $roles;
1424}
1425
1426/**
1427 * Implementation of hook_cron().
1428 */
1429function workflow_cron() {
1430  $clear_cache = FALSE;
1431
1432  // If the time now is greater than the time to execute a
1433  // transition, do it.
1434  $nodes = db_query('SELECT * FROM {workflow_scheduled_transition} s WHERE s.scheduled > 0 AND s.scheduled < %d', time());
1435
1436  while ($row = db_fetch_object($nodes)) {
1437    $node = node_load($row->nid);
1438
1439    // Make sure transition is still valid; i.e., the node is
1440    // still in the state it was when the transition was scheduled.
1441    if ($node->_workflow == $row->old_sid) {
1442      // Do transition.
1443      workflow_execute_transition($node, $row->sid, $row->comment, TRUE);
1444
1445      watchdog('content', '%type: scheduled transition of %title.', array('%type' => t($node->type), '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
1446      $clear_cache = TRUE;
1447    }
1448    else {
1449      // Node is not in the same state it was when the transition
1450      // was scheduled. Defer to the node's current state and
1451      // abandon the scheduled transition.
1452      db_query('DELETE FROM {workflow_scheduled_transition} WHERE nid = %d', $node->nid);
1453    }
1454  }
1455
1456  if ($clear_cache) {
1457    // Clear the cache so that if the transition resulted in a node
1458    // being published, the anonymous user can see it.
1459    cache_clear_all();
1460  }
1461}
1462
1463/**
1464 * Implementation of action_info_alter().
1465 */
1466function workflow_action_info_alter(&$info) {
1467  foreach (array_keys($info) as $key) {
1468    // Modify each action's hooks declaration, changing it to say
1469    // that the action supports any hook.
1470    $info[$key]['hooks']['any'] = TRUE;
1471  }
1472}
1473
1474/**
1475 * Implementation of hook_hook_info().
1476 * Expose each transition as a hook.
1477 */
1478function workflow_hook_info() {
1479  $states = workflow_get_states();
1480  if (!$states) {
1481    return;
1482  }
1483  $trigger_page = substr($_GET['q'], 0, 28) == 'admin/build/trigger/workflow';
1484  if ($trigger_page && $wid = arg(4)) {
1485    $result = db_query("SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid FROM {workflow_type_map} tm LEFT JOIN {workflows} w ON tm.wid = w.wid LEFT JOIN {workflow_states} ws ON w.wid = ws.wid LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid WHERE w.wid = %d AND wt.target_sid IS NOT NULL ORDER BY tm.type, ws.weight", $wid);
1486  }
1487  else {
1488    $result = db_query("SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid FROM {workflow_type_map} tm LEFT JOIN {workflows} w ON tm.wid = w.wid LEFT JOIN {workflow_states} ws ON w.wid = ws.wid LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid WHERE wt.target_sid IS NOT NULL ORDER BY tm.type, ws.weight");
1489  }
1490  while ($data = db_fetch_object($result)) {
1491    $pseudohooks['workflow-'. $data->type .'-'. $data->tid] = array('runs when' => t('When %type moves from %state to %target_state', array('%type' => $data->type, '%state' => $states[$data->sid], '%target_state' => $states[$data->target_sid])));
1492  }
1493  // $pseudohooks will not be set if no workflows have been assigned
1494  // to node types.
1495  if (isset($pseudohooks)) {
1496    return array(
1497      'workflow' => array(
1498        'workflow' => $pseudohooks,
1499      ),
1500    );
1501  }
1502  if ($trigger_page) {
1503    drupal_set_message(t('Either no transitions have been set up or this workflow has not yet been assigned to a content type. To enable the assignment of actions, edit the workflow to assign permissions for roles to do transitions. After that is completed, transitions will appear here and you will be able to assign actions to them.'));
1504  }
1505}
1506
1507/**
1508 * Implementation of hook_menu_alter().
1509 *
1510 * Work around loss of menu local task inheritance in Drupal 6.2.
1511 */
1512function workflow_menu_alter(&$callbacks) {
1513  if (module_exists('trigger') & isset($callbacks['admin/build/trigger/workflow'])) {
1514    $callbacks['admin/build/trigger/workflow']['access callback'] = 'trigger_access_check';
1515  }
1516}
1517
1518/**
1519 * Implementation of hook_token_values().
1520 */
1521function workflow_token_values($type, $object = NULL) {
1522  $values = array();
1523  switch ($type) {
1524    case 'node':
1525    case 'workflow':
1526      $node = (object)$object;
1527
1528      if ($wid = workflow_get_workflow_for_type($node->type)) {
1529        $values['workflow-name'] = workflow_get_name($wid);
1530        $states = workflow_get_states($wid);
1531      }
1532      else {
1533        break;
1534      }
1535
1536      $result = db_query_range("SELECT h.* FROM {workflow_node_history} h WHERE nid = %d ORDER BY stamp DESC", $node->nid, 0, 1);
1537
1538      if ($row = db_fetch_object($result)) {
1539        $account = user_load(array('uid' => $row->uid));
1540        $comment = $row->comment;
1541      }
1542
1543      if (isset($node->workflow) && !isset($node->workflow_stamp)) {
1544        // The node is being submitted but the form data has not been saved to the database yet,
1545        // so we set the token values from the workflow form fields.
1546        $sid = $node->workflow;
1547        $old_sid = isset($row->sid) ? $row->sid : _workflow_creation_state($wid);
1548        $date = time();
1549        $user_name = $node->uid ? $node->name : variable_get('anonymous', 'Anonymous');
1550        $uid = $node->uid;
1551        $mail = $node->uid ? $node->user_mail : '';
1552        $comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
1553      }
1554      else if (!isset($node->workflow) && empty($row->sid)) {
1555        // If the state is not specified and the node has no workflow history,
1556        // the node is being inserted and will soon be transitioned to the first valid state.
1557        // We find this state using the same logic as workflow_nodeapi().
1558        $choices = workflow_field_choices($node);
1559        $keys = array_keys($choices);
1560        $sid = array_shift($keys);
1561        $old_sid = _workflow_creation_state($wid);
1562        $date = time();
1563        $user_name = $node->uid ? $node->name : variable_get('anonymous', 'Anonymous');
1564        $uid = $node->uid;
1565        $mail = $node->uid ? $node->user_mail : '';
1566        $comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
1567      }
1568      else {
1569        // Default to the most recent transition data in the workflow history table.
1570        $sid = $row->sid;
1571        $old_sid = $row->old_sid;
1572        $date = $row->stamp;
1573        $user_name = $account->uid ? $account->name : variable_get('anonymous', 'Anonymous');
1574        $uid = $account->uid;
1575        $mail = $account->uid ? $account->mail : '';
1576      }
1577
1578      $values['workflow-current-state-name']                =  $states[$sid];
1579      $values['workflow-old-state-name']                    =  $states[$old_sid];
1580
1581      $values['workflow-current-state-date-iso']            =  date('Ymdhis', $date);
1582      $values['workflow-current-state-date-tstamp']         =  $date;
1583      $values['workflow-current-state-date-formatted']      =  date('M d, Y h:i:s', $date);
1584
1585      $values['workflow-current-state-updating-user-name']  = check_plain($user_name);
1586      $values['workflow-current-state-updating-user-uid']   = $uid;
1587      $values['workflow-current-state-updating-user-mail']  = check_plain($mail);
1588
1589      $values['workflow-current-state-log-entry']           = filter_xss($comment, array('a', 'em', 'strong'));
1590      break;
1591  }
1592
1593  return $values;
1594}
1595
1596/**
1597 * Implementation of hook_token_list().
1598 */
1599function workflow_token_list($type = 'all') {
1600  $tokens = array();
1601
1602  if ($type == 'workflow' || $type == 'node' || $type == 'all') {
1603    $tokens['workflow']['workflow-name']                          =  'Name of workflow appied to this node';
1604    $tokens['workflow']['workflow-current-state-name']            =  'Current state of content';
1605    $tokens['workflow']['workflow-old-state-name']                =  'Old state of content';
1606    $tokens['workflow']['workflow-current-state-date-iso']        =  'Date of last state change (ISO)';
1607    $tokens['workflow']['workflow-current-state-date-tstamp']     =  'Date of last state change (timestamp)';
1608    $tokens['workflow']['workflow-current-state-date-formatted']  =  'Date of last state change (formated - M d, Y h:i:s)';;
1609
1610    $tokens['workflow']['workflow-current-state-updating-user-name']       = 'Username of last state changer';
1611    $tokens['workflow']['workflow-current-state-updating-user-uid']        = 'uid of last state changer';
1612    $tokens['workflow']['workflow-current-state-updating-user-mail']       = 'email of last state changer';
1613
1614    $tokens['workflow']['workflow-current-state-log-entry']       = 'Last workflow comment log';
1615    $tokens['node'] = $tokens['workflow'];
1616  }
1617
1618  return $tokens;
1619}
1620
1621/**
1622 * Implementation of hook_content_extra_fields().
1623 */
1624function workflow_content_extra_fields($type_name) {
1625  $extra = array();
1626  if (!in_array('node', variable_get('workflow_'. $type_name, array('node')))) {
1627    return;
1628  }
1629  $extra['workflow'] = array(
1630    'label' => t('Workflow'),
1631    'description' => t('Workflow module form'),
1632    'weight' => 10,
1633  );
1634  return $extra;
1635}
1636
1637/**
1638 * Implementation of hook_user().
1639 */
1640function workflow_user($op, &$edit, &$account, $category = NULL) {
1641  switch ($op) {
1642    case 'delete':
1643      db_query("UPDATE {workflow_node} SET uid = 0 WHERE uid = %d", $account->uid);
1644      db_query("UPDATE {workflow_node_history} SET uid = 0 WHERE uid = %d", $account->uid);
1645    break;
1646  }
1647}
Nota: Vea TracBrowser para ayuda de uso del navegador del repositorio.