WorkflowTransition.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. <?php
  2. /**
  3. * @file
  4. * Contains workflow\includes\Entity\WorkflowTransition.
  5. * Contains workflow\includes\Entity\WorkflowTransitionController.
  6. *
  7. * Implements (scheduled/executed) state transitions on entities.
  8. */
  9. /**
  10. * Implements an actual Transition.
  11. *
  12. * If a transition is executed, the new state is saved in the Field or {workflow_node}.
  13. * If a transition is saved, it is saved in table {workflow_history_node}
  14. */
  15. class WorkflowTransition extends Entity {
  16. // Field data.
  17. public $entity_type;
  18. public $field_name = '';
  19. public $language = LANGUAGE_NONE;
  20. public $delta = 0;
  21. // Entity data.
  22. public $revision_id;
  23. public $entity_id; // Use WorkflowTransition->getEntity() to fetch this.
  24. public $nid; // @todo D8: remove $nid, use $entity_id. (requires conversion of Views displays.)
  25. // Transition data.
  26. // public $hid = 0;
  27. public $wid = 0;
  28. public $old_sid = 0;
  29. public $new_sid = 0;
  30. public $sid = 0; // @todo D8: remove $sid, use $new_sid. (requires conversion of Views displays.)
  31. public $uid = 0; // Use WorkflowTransition->getUser() to fetch this.
  32. public $stamp;
  33. public $comment = '';
  34. // Cached data, from $this->entity_id and $this->uid.
  35. protected $entity = NULL; // Use WorkflowTransition->getEntity() to fetch this.
  36. protected $user = NULL; // Use WorkflowTransition->getUser() to fetch this.
  37. // Extra data.
  38. protected $is_scheduled = NULL;
  39. protected $is_executed = NULL;
  40. protected $force = NULL;
  41. /**
  42. * Entity class functions.
  43. */
  44. /**
  45. * Creates a new entity.
  46. *
  47. * @param array $values
  48. * The initial values.
  49. * @param string $entityType
  50. * The entity type of this Entity subclass.
  51. *
  52. * @see entity_create()
  53. *
  54. * No arguments passed, when loading from DB.
  55. * All arguments must be passed, when creating an object programmatically.
  56. * One argument $entity may be passed, only to directly call delete() afterwards.
  57. */
  58. public function __construct(array $values = array(), $entityType = 'WorkflowTransition') {
  59. // Please be aware that $entity_type and $entityType are different things!
  60. parent::__construct($values, $entityType);
  61. $this->hid = isset($this->hid) ? $this->hid : 0;
  62. // This transition is not scheduled
  63. $this->is_scheduled = FALSE;
  64. // This transition is not executed, if it has no hid, yet, upon load.
  65. $this->is_executed = ($this->hid > 0);
  66. // Fill the 'new' fields correctly. @todo D8: rename these fields in db table.
  67. $this->entity_id = $this->nid;
  68. $this->new_sid = $this->sid;
  69. // Initialize wid, if not set.
  70. if ($this->old_sid && !$this->wid) {
  71. $this->getWorkflow();
  72. }
  73. }
  74. /**
  75. * Helper function for __construct. Used for all children of WorkflowTransition (aka WorkflowScheduledTransition)
  76. *
  77. * @param $entity_type
  78. * @param $entity
  79. * @param $field_name
  80. * @param $old_sid
  81. * @param $new_sid
  82. * @param null $uid
  83. * @param int $stamp
  84. * @param string $comment
  85. */
  86. public function setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid = NULL, $stamp = REQUEST_TIME, $comment = '') {
  87. // Normally, the values are passed in an array, and set in parent::__construct, but we do it ourselves.
  88. // (But there is no objection to do it there.)
  89. global $user;
  90. $this->entity_type = (!$entity_type) ? $this->entity_type : $entity_type;
  91. $this->field_name = (!$field_name) ? $this->field_name : $field_name;
  92. $uid = ($uid === NULL) ? $user->uid : $uid;
  93. // If constructor is called with new() and arguments.
  94. // Load the supplied entity.
  95. if ($entity && !$entity_type) {
  96. // Not all parameters are passed programmatically.
  97. drupal_set_message(t('Wrong call to new Workflow*Transition()'), 'error');
  98. }
  99. elseif ($entity) {
  100. $this->setEntity($entity_type, $entity);
  101. }
  102. if (!$entity && !$old_sid && !$new_sid) {
  103. // If constructor is called without arguments, e.g., loading from db.
  104. }
  105. elseif ($entity && $old_sid) {
  106. // Caveat: upon entity_delete, $new_sid is '0'.
  107. // If constructor is called with new() and arguments.
  108. $this->old_sid = $old_sid;
  109. $this->sid = $new_sid;
  110. $this->uid = $uid;
  111. $this->stamp = $stamp;
  112. $this->comment = $comment;
  113. // Set language. Multi-language is not supported for Workflow Node.
  114. $this->language = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
  115. }
  116. elseif (!$old_sid) {
  117. // Not all parameters are passed programmatically.
  118. drupal_set_message(
  119. t('Wrong call to constructor Workflow*Transition(@old_sid to @new_sid)', array('@old_sid' => $old_sid, '@new_sid' => $new_sid)),
  120. 'error');
  121. }
  122. // Fill the 'new' fields correctly. @todo D8: rename these fields in db table.
  123. $this->entity_id = $this->nid;
  124. $this->new_sid = $this->sid;
  125. // Initialize wid, if not set.
  126. if ($this->old_sid && !$this->wid) {
  127. $this->getWorkflow();
  128. }
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. protected function defaultLabel() {
  134. // @todo; Should return title of WorkflowConfigTransition. Make it a superclass??
  135. return t('Workflow transition !hid', array('!hid' =>3));
  136. }
  137. // protected function defaultUri() {
  138. // return array('path' => 'workflow_transition/' . $this->hid);
  139. // }
  140. /**
  141. * CRUD functions.
  142. */
  143. /**
  144. * Given a node, get all transitions for it.
  145. *
  146. * Since this may return a lot of data, a limit is included to allow for only one result.
  147. *
  148. * @param string $entity_type
  149. * @param array $entity_ids
  150. * @param string $field_name
  151. * Optional. Can be NULL, if you want to load any field.
  152. * @param null $limit
  153. * @param string $langcode
  154. *
  155. * @return array
  156. * An array of WorkflowTransitions.
  157. */
  158. public static function loadMultiple($entity_type, array $entity_ids, $field_name = '', $limit = NULL, $langcode = '') {
  159. $query = db_select('workflow_node_history', 'h');
  160. $query->condition('h.entity_type', $entity_type);
  161. if ($entity_ids) {
  162. $query->condition('h.nid', $entity_ids);
  163. }
  164. if ($field_name !== NULL) {
  165. // If we do not know/care for the field_name, fetch all history.
  166. // E.g., in workflow.tokens.
  167. $query->condition('h.field_name', $field_name);
  168. }
  169. // Add selection on language.
  170. // Workflow Node: only has 'und'.
  171. // Workflow Field: untranslated field have 'und'.
  172. // Workflow Field: translated fields may be specified.
  173. if ($langcode) {
  174. $query->condition('h.language', $langcode);
  175. }
  176. $query->fields('h');
  177. // The timestamp is only granular to the second; on a busy site, we need the id.
  178. // $query->orderBy('h.stamp', 'DESC');
  179. $query->orderBy('h.hid', 'DESC');
  180. if ($limit) {
  181. $query->range(0, $limit);
  182. }
  183. $result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowTransition');
  184. return $result;
  185. }
  186. /**
  187. * Property functions.
  188. */
  189. /**
  190. * Verifies if the given transition is allowed.
  191. *
  192. * - In settings;
  193. * - In permissions;
  194. * - By permission hooks, implemented by other modules.
  195. *
  196. * @param $roles
  197. * @param $user
  198. * @param $force
  199. *
  200. * @return bool TRUE if OK, else FALSE.
  201. * TRUE if OK, else FALSE.
  202. *
  203. * Having both $roles AND $user seems redundant, but $roles have been
  204. * tampered with, even though they belong to the $user.
  205. * @see WorkflowConfigTransition::isAllowed()
  206. */
  207. protected function isAllowed($roles, $user, $force) {
  208. if ($force || ($user->uid == 1)) {
  209. return TRUE;
  210. }
  211. // Check allow-ability of state change if user is not superuser (might be cron).
  212. // Get the WorkflowConfigTransition.
  213. // @todo: some day, WorkflowConfigTransition can be a parent of WorkflowTransition.
  214. $workflow = $this->getWorkflow();
  215. $config_transitions = $workflow->getTransitionsBySidTargetSid($this->old_sid, $this->new_sid);
  216. $config_transition = reset($config_transitions);
  217. if (!$config_transition || !$config_transition->isAllowed($roles)) {
  218. $t_args = array(
  219. '%old_sid' => $this->old_sid,
  220. '%new_sid' => $this->new_sid,
  221. );
  222. watchdog('workflow', 'Attempt to go to nonexistent transition (from %old_sid to %new_sid)', $t_args, WATCHDOG_ERROR);
  223. return FALSE;
  224. }
  225. return TRUE;
  226. }
  227. /**
  228. * Execute a transition (change state of a node).
  229. *
  230. * @param bool $force
  231. * If set to TRUE, workflow permissions will be ignored.
  232. *
  233. * @return int
  234. * New state ID. If execution failed, old state ID is returned,
  235. *
  236. * deprecated workflow_execute_transition() --> WorkflowTransition::execute().
  237. */
  238. public function execute($force = FALSE) {
  239. $user = $this->getUser();
  240. $old_sid = $this->old_sid;
  241. $new_sid = $this->new_sid;
  242. // Load the entity, if not already loaded.
  243. // This also sets the (empty) $revision_id in Scheduled Transitions.
  244. $entity = $this->getEntity();
  245. // Only after getEntity(), the following are surely set.
  246. $entity_type = $this->entity_type;
  247. $entity_id = $this->entity_id;
  248. $field_name = $this->field_name;
  249. // Make sure $force is set in the transition, too.
  250. if ($force) {
  251. $this->force($force);
  252. }
  253. $force = $this->isForced();
  254. // Prepare an array of arguments for error messages.
  255. $args = array(
  256. '%user' => isset($user->name) ? $user->name : '',
  257. '%old' => $old_sid,
  258. '%new' => $new_sid,
  259. );
  260. if (!$this->getOldState()) {
  261. drupal_set_message($message = t('You tried to set a Workflow State, but
  262. the entity is not relevant. Please contact your system administrator.'),
  263. 'error');
  264. $message = 'Setting a non-relevant Entity from state %old to %new';
  265. $uri = entity_uri($entity_type, $entity);
  266. watchdog('workflow', $message, $args, WATCHDOG_ERROR, l('view', $uri['path']));
  267. return $old_sid;
  268. }
  269. // Check if the state has changed.
  270. $state_changed = ($old_sid != $new_sid);
  271. // If so, check the permissions.
  272. if ($state_changed) {
  273. // State has changed. Do some checks upfront.
  274. if (!$force) {
  275. // Make sure this transition is allowed by workflow module Admin UI.
  276. $roles = array_keys($user->roles);
  277. $roles = array_merge(array(WORKFLOW_ROLE_AUTHOR_RID), $roles);
  278. if (!$this->isAllowed($roles, $user, $force)) {
  279. watchdog('workflow', 'User %user not allowed to go from state %old to %new', $args, WATCHDOG_NOTICE);
  280. // If incorrect, quit.
  281. return $old_sid;
  282. }
  283. }
  284. if (!$force) {
  285. // Make sure this transition is allowed by custom module.
  286. // @todo D8: remove, or replace by 'transition pre'. See WorkflowState::getOptions().
  287. // @todo D8: replace all parameters that are included in $transition.
  288. $permitted = module_invoke_all('workflow', 'transition permitted', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this, $user);
  289. // Stop if a module says so.
  290. if (in_array(FALSE, $permitted, TRUE)) {
  291. watchdog('workflow', 'Transition vetoed by module.');
  292. return $old_sid;
  293. }
  294. }
  295. // Make sure this transition is valid and allowed for the current user.
  296. // Invoke a callback indicating a transition is about to occur.
  297. // Modules may veto the transition by returning FALSE.
  298. // (Even if $force is TRUE, but they shouldn't do that.)
  299. $permitted = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
  300. // Stop if a module says so.
  301. if (in_array(FALSE, $permitted, TRUE)) {
  302. watchdog('workflow', 'Transition vetoed by module.');
  303. return $old_sid;
  304. }
  305. }
  306. elseif ($this->comment) {
  307. // No need to ask permission for adding comments.
  308. // Since you should not add actions to a 'transition pre' event, there is
  309. // no need to invoke the event.
  310. }
  311. else {
  312. // There is no state change, and no comment.
  313. // We may need to clean up something.
  314. }
  315. if ($state_changed || $this->comment) {
  316. // Store the transition, so it can be easily fetched later on.
  317. // Store in an array, to prepare for multiple workflow_fields per entity.
  318. // This is a.o. used in hook_entity_update to trigger 'transition post'.
  319. // Only add the Transition once! or you will encounter endless loops in
  320. // hook_entity_update() in workflow_actions_entity_update et all.
  321. if (!isset($entity->workflow_transitions[$field_name])) {
  322. $entity->workflow_transitions[$field_name] = &$this;
  323. }
  324. // The transition is allowed. Let other modules modify the comment.
  325. // @todo D8: remove all but last items from $context.
  326. $context = array(
  327. 'node' => $entity,
  328. 'sid' => $new_sid,
  329. 'old_sid' => $old_sid,
  330. 'uid' => $user->uid,
  331. 'transition' => $this,
  332. );
  333. drupal_alter('workflow_comment', $this->comment, $context);
  334. }
  335. // Now, change the database.
  336. // Log the new state in {workflow_node}.
  337. if (!$field_name) {
  338. if ($state_changed || $this->comment) {
  339. // If the node does not have an existing 'workflow' property,
  340. // save the $old_sid there, so it can be logged.
  341. if (!isset($entity->workflow)) { // This is a workflow_node sid.
  342. $entity->workflow = $old_sid; // This is a workflow_node sid.
  343. }
  344. // Change the state for {workflow_node}.
  345. // The equivalent for Field API is in WorkflowDefaultWidget::submit.
  346. $data = array(
  347. 'nid' => $entity_id,
  348. 'sid' => $new_sid,
  349. 'uid' => (isset($entity->workflow_uid) ? $entity->workflow_uid : $user->uid),
  350. 'stamp' => REQUEST_TIME,
  351. );
  352. workflow_update_workflow_node($data);
  353. $entity->workflow = $new_sid; // This is a workflow_node sid.
  354. }
  355. }
  356. else {
  357. // This is a Workflow Field.
  358. // Until now, adding code here (instead of in workflow_execute_transition() )
  359. // doesn't work, creating an endless loop.
  360. // Update 10-dec-2016: the following line, added above, may have resolved that.
  361. // if (!isset($entity->workflow_transitions[$field_name]))
  362. /*
  363. if ($state_changed || $this->comment) {
  364. // Do a separate update to update the field (Workflow Field API)
  365. // This will call hook_field_update() and WorkflowFieldDefaultWidget::submit().
  366. // $entity->{$field_name}[$this->language] = array();
  367. // $entity->{$field_name}[$this->language][0]['workflow']['workflow_sid'] = $new_sid;
  368. // $entity->{$field_name}[$this->language][0]['workflow']['workflow_comment'] = $this->comment;
  369. $entity->{$field_name}[$this->language][0]['transition'] = $this;
  370. // Save the entity, but not through entity_save(),
  371. // since this will check permissions again and trigger rules.
  372. // @TODO: replace below by a workflow_field setter callback.
  373. // The transition was successfully executed, or else a message was raised.
  374. // entity_save($entity_type, $entity);
  375. // or
  376. // field_attach_update($entity_type, $entity);
  377. // Reset the entity cache after update.
  378. entity_get_controller($entity_type)->resetCache(array($entity_id));
  379. $new_sid = workflow_node_current_state($entity, $entity_type, $field_name);
  380. }
  381. */
  382. }
  383. $this->is_executed = TRUE;
  384. if ($state_changed || $this->comment) {
  385. // Log the transition in {workflow_node_history}.
  386. $this->save();
  387. // Register state change with watchdog.
  388. if ($state_changed) {
  389. $workflow = $this->getWorkflow();
  390. // Get the workflow_settings, unified for workflow_node and workflow_field.
  391. // @todo D8: move settings back to Workflow (like workflownode currently is).
  392. // @todo D8: to move settings back, grep for "workflow->options" and "field['settings']".
  393. $field = _workflow_info_field($field_name, $workflow);
  394. if (($new_state = $this->getNewState()) && !empty($field['settings']['watchdog_log'])) {
  395. $entity_type_info = entity_get_info($entity_type);
  396. $message = ($this->isScheduled()) ? 'Scheduled state change of @type %label to %state_name executed' : 'State of @type %label set to %state_name';
  397. $args = array(
  398. '@type' => $entity_type_info['label'],
  399. '%label' => entity_label($entity_type, $entity),
  400. '%state_name' => $new_state->label(),
  401. );
  402. $uri = entity_uri($entity_type, $entity);
  403. watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path']));
  404. }
  405. }
  406. // Remove any scheduled state transitions.
  407. foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) {
  408. /* @var $scheduled_transition WorkflowScheduledTransition */
  409. $scheduled_transition->delete();
  410. }
  411. // Notify modules that transition has occurred.
  412. // Action triggers should take place in response to this callback, not the 'transaction pre'.
  413. if (!$field_name) {
  414. // Now that workflow data is saved, reset stuff to avoid problems
  415. // when Rules etc want to resave the data.
  416. // Remember, this is only for nodes, and node_save() is not necessarily performed.
  417. unset($entity->workflow_comment);
  418. module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
  419. entity_get_controller('node')->resetCache(array($entity->nid)); // from entity_load(), node_save();
  420. }
  421. else {
  422. // module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
  423. // We have a problem here with Rules, Trigger, etc. when invoking
  424. // 'transition post': the entity has not been saved, yet. we are still
  425. // IN the transition, not AFTER. Alternatives:
  426. // 1. Save the field here explicitly, using field_attach_save;
  427. // 2. Move the invoke to another place: hook_entity_insert(), hook_entity_update();
  428. // 3. Rely on the entity hooks. This works for Rules, not for Trigger.
  429. // --> We choose option 2:
  430. // - First, $entity->workflow_transitions[] is set for easy re-fetching.
  431. // - Then, post_execute() is invoked via workflowfield_entity_insert(), _update().
  432. }
  433. }
  434. return $new_sid;
  435. }
  436. /**
  437. * Invokes 'transition post'.
  438. *
  439. * Add the possibility to invoke the hook from elsewhere.
  440. */
  441. public function post_execute($force = FALSE) {
  442. $old_sid = $this->old_sid;
  443. $new_sid = $this->new_sid;
  444. $entity = $this->getEntity(); // Entity may not be loaded, yet.
  445. $entity_type = $this->entity_type;
  446. // $entity_id = $this->entity_id;
  447. $field_name = $this->field_name;
  448. $state_changed = ($old_sid != $new_sid);
  449. if ($state_changed || $this->comment) {
  450. module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
  451. }
  452. }
  453. /**
  454. * Get the Transitions $workflow.
  455. *
  456. * @return Workflow|NULL
  457. * The workflow for this Transition.
  458. */
  459. public function getWorkflow() {
  460. $workflow = NULL;
  461. if (!$this->wid) {
  462. $state = workflow_state_load_single($this->new_sid ? $this->new_sid : $this->old_sid);
  463. $this->wid = (int) $state->wid;
  464. }
  465. if ($this->wid) {
  466. $workflow = workflow_load($this->wid);
  467. }
  468. return $workflow;
  469. }
  470. /**
  471. * Get the Transitions $entity.
  472. *
  473. * @return object
  474. * The entity, that is added to the Transition.
  475. */
  476. public function getEntity() {
  477. if (empty($this->entity) && $this->entity_type) {
  478. $entity_type = $this->entity_type;
  479. $entity_id = $this->entity_id;
  480. $entity = entity_load_single($entity_type, $entity_id);
  481. // Set the entity cache.
  482. $this->entity = $entity;
  483. // Make sure the vid of Entity and Transition are equal.
  484. // Especially for Scheduled Transition, that do not have this set, yet,
  485. // or may have an outdated revision ID.
  486. $info = entity_get_info($entity_type);
  487. $revision_key = $info['entity keys']['revision'];
  488. $this->revision_id = (isset($entity->{$revision_key})) ? $entity->{$revision_key} : NULL;
  489. }
  490. return $this->entity;
  491. }
  492. /**
  493. * Set the Transitions $entity.
  494. *
  495. * @param string $entity_type
  496. * The entity type of the entity.
  497. * @param mixed $entity
  498. * The Entity ID or the Entity object, to add to the Transition.
  499. *
  500. * @return object $entity
  501. * The Entity, that is added to the Transition.
  502. */
  503. public function setEntity($entity_type, $entity) {
  504. if (!is_object($entity)) {
  505. $entity_id = $entity;
  506. // Use node API or Entity API to load the object first.
  507. $entity = entity_load_single($entity_type, $entity_id);
  508. }
  509. $this->entity = $entity;
  510. $this->entity_type = $entity_type;
  511. list($this->entity_id, $this->revision_id,) = entity_extract_ids($entity_type, $entity);
  512. // For backwards compatibility, set nid.
  513. $this->nid = $this->entity_id;
  514. return $this->entity;
  515. }
  516. public function getUser() {
  517. if (!isset($this->user) || ($this->user->uid != $this->uid)) {
  518. $this->user = user_load($this->uid);
  519. }
  520. return $this->user;
  521. }
  522. /**
  523. * {@inheritdoc}
  524. */
  525. public function getFieldName() {
  526. return $this->field_name;
  527. }
  528. /**
  529. * Functions, common to the WorkflowTransitions.
  530. */
  531. public function getOldState() {
  532. return workflow_state_load_single($this->old_sid);
  533. }
  534. public function getNewState() {
  535. return workflow_state_load_single($this->new_sid);
  536. }
  537. /**
  538. * {@inheritdoc}
  539. */
  540. public function getComment() {
  541. return $this->comment;
  542. }
  543. /**
  544. * Returns the time on which the transitions was or will be executed.
  545. *
  546. * @return mixed
  547. */
  548. public function getTimestamp() {
  549. return $this->stamp;
  550. }
  551. /**
  552. * {@inheritdoc}
  553. */
  554. public function getTimestampFormatted() {
  555. $timestamp = $this->stamp;
  556. return format_date($timestamp);;
  557. }
  558. /**
  559. * {@inheritdoc}
  560. */
  561. public function setTimestamp($value) {
  562. $this->stamp = $value;
  563. return $this;
  564. }
  565. /**
  566. * Returns if this is a Scheduled Transition.
  567. */
  568. public function isScheduled() {
  569. return $this->is_scheduled;
  570. }
  571. public function schedule($schedule = TRUE) {
  572. return $this->is_scheduled = $schedule;
  573. }
  574. public function isExecuted() {
  575. return $this->is_executed;
  576. }
  577. /**
  578. * A transition may be forced skipping checks.
  579. */
  580. public function isForced() {
  581. return (bool) $this->force;
  582. }
  583. public function force($force = TRUE) {
  584. return $this->force = $force;
  585. }
  586. /**
  587. * Helper debugging function to easily show the contents fo a transition.
  588. */
  589. public function dpm($function = '') {
  590. $transition = $this;
  591. $entity = $transition->getEntity();
  592. $entity_type = $transition->entity_type;
  593. list($entity_id, , $entity_bundle) = ($entity) ? entity_extract_ids($entity_type, $entity) : array('', '', '');
  594. $time = $transition->getTimestampFormatted();
  595. // Do this extensive $user_name lines, for some troubles with Action.
  596. $user = $transition->getUser();
  597. $user_name = ($user) ? $user->name : 'unknown username';
  598. $t_string = get_class($this) . ' ' . (isset($this->hid) ? $this->hid : '') . ' ' . ($function ? ("in function '$function'") : '');
  599. $output[] = 'Entity = ' . ((!$entity) ? 'NULL' : ($entity_type . '/' . $entity_bundle . '/' . $entity_id));
  600. $output[] = 'Field = ' . $transition->getFieldName();
  601. $output[] = 'From/To = ' . $transition->old_sid . ' > ' . $transition->new_sid . ' @ ' . $time;
  602. $output[] = 'Comment = ' . $user_name . ' says: ' . $transition->getComment();
  603. $output[] = 'Forced = ' . ($transition->isForced() ? 'yes' : 'no');
  604. if (function_exists('dpm')) { dpm($output, $t_string); }
  605. }
  606. }
  607. /**
  608. * Implements a controller class for WorkflowTransition.
  609. *
  610. * The 'true' controller class is 'Workflow'.
  611. */
  612. class WorkflowTransitionController extends EntityAPIController {
  613. /**
  614. * Overrides DrupalDefaultEntityController::cacheGet().
  615. *
  616. * Override default function, due to core issue #1572466.
  617. */
  618. protected function cacheGet($ids, $conditions = array()) {
  619. // Load any available entities from the internal cache.
  620. if ($ids === FALSE && !$conditions) {
  621. return $this->entityCache;
  622. }
  623. return parent::cacheGet($ids, $conditions);
  624. }
  625. /**
  626. * Insert (no update) a transition.
  627. *
  628. * deprecated workflow_insert_workflow_node_history() --> WorkflowTransition::save()
  629. */
  630. public function save($entity, DatabaseTransaction $transaction = NULL) {
  631. // Check for no transition.
  632. if ($entity->old_sid == $entity->new_sid) {
  633. if (!$entity->comment) {
  634. // Write comment into history though.
  635. return;
  636. }
  637. }
  638. if (empty($entity->hid)) {
  639. // Insert the transition. Make sure it hasn't already been inserted.
  640. $last_history = workflow_transition_load_single($entity->entity_type, $entity->entity_id, $entity->field_name, $entity->language);
  641. if ($last_history &&
  642. $last_history->stamp == REQUEST_TIME &&
  643. $last_history->new_sid == $entity->new_sid) {
  644. return;
  645. }
  646. else {
  647. unset($entity->hid);
  648. $entity->stamp = isset($entity->stamp) ? $entity->stamp : REQUEST_TIME;
  649. return parent::save($entity, $transaction);
  650. }
  651. }
  652. else {
  653. // Update the transition.
  654. return parent::save($entity, $transaction);
  655. }
  656. }
  657. }