/** * @return WorkflowTransitionInterface */ protected function getTransitionForExecution(EntityInterface $entity) { $user = workflow_current_user(); if (!$entity) { \Drupal::logger('workflow_action')->notice('Unable to get current entity - entity is not defined.', []); return NULL; } // Get the entity type and numeric ID. $entity_id = $entity->id(); if (!$entity_id) { \Drupal::logger('workflow_action')->notice('Unable to get current entity ID - entity is not yet saved.', []); return NULL; } // In 'after saving new content', the node is already saved. Avoid second insert. // Todo: clone? $entity->enforceIsNew(FALSE); $config = $this->configuration; $field_name = workflow_get_field_name($entity, $config['field_name']); $current_sid = workflow_node_current_state($entity, $field_name); if (!$current_sid) { \Drupal::logger('workflow_action')->notice('Unable to get current workflow state of entity %id.', array('%id' => $entity_id)); return NULL; } $to_sid = isset($config['to_sid']) ? $config['to_sid'] : ''; // Get the Comment. Parse the $comment variables. $comment_string = $this->configuration['comment']; $comment = t($comment_string, array('%title' => $entity->label(), '%state' => workflow_get_sid_name($to_sid), '%user' => $user->getUsername())); $force = $this->configuration['force']; $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); $transition->setValues($to_sid, $user->id(), REQUEST_TIME, $comment); $transition->force($force); return $transition; }
/** * {@inheritdoc} */ public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE) { $result = AccessResult::neutral(); $account = $user = workflow_current_user($account); // This is only for Edit/Delete transition. For Add/create, use createAccess. switch ($entity->getEntityTypeId()) { case 'workflow_transition': case 'workflow_scheduled_transition': /* @var $transition WorkflowTransitionInterface */ $transition = $entity; switch ($operation) { case 'revert': $is_owner = WorkflowManager::isOwner($user, $transition); $type_id = $transition->getWorkflowId(); if ($transition->getFromSid() == $transition->getToSid()) { // No access for same state transitions. $result = AccessResult::forbidden(); } elseif ($user->hasPermission("revert any {$type_id} workflow_transition")) { // OK, add operation. $result = AccessResult::allowed(); } elseif ($is_owner && $user->hasPermission("revert own {$type_id} workflow_transition")) { // OK, add operation. $result = AccessResult::allowed(); } else { // No access. $result = AccessResult::forbidden(); } break; default: $result = parent::access($entity, $operation, $account, $return_as_object)->cachePerPermissions(); break; } // End of switch ($operation). break; // case // case default: // $entity_type $result = AccessResult::forbidden(); } // End of switch($entity->getEntityTypeId()). return $return_as_object ? $result : $result->isAllowed(); }
/** * {@inheritdoc} */ public function setValues($to_sid, $uid = NULL, $timestamp = REQUEST_TIME, $comment = '', $force_create = FALSE) { // Normally, the values are passed in an array, and set in parent::__construct, but we do it ourselves. $uid = $uid === NULL ? workflow_current_user()->id() : $uid; $from_sid = $this->getFromSid(); $this->set('to_sid', $to_sid); $this->setOwnerId($uid); $this->setTimestamp($timestamp); $this->setComment($comment); // If constructor is called with new() and arguments. if (!$from_sid && !$to_sid && !$this->getTargetEntity()) { // If constructor is called without arguments, e.g., loading from db. } elseif ($from_sid && $this->getTargetEntity()) { // Caveat: upon entity_delete, $to_sid is '0'. // If constructor is called with new() and arguments. } elseif (!$from_sid) { // Not all parameters are passed programmatically. if (!$force_create) { drupal_set_message(t('Wrong call to constructor Workflow*Transition(%from_sid to %to_sid)', array('%from_sid' => $from_sid, '%to_sid' => $to_sid)), 'error'); } } return $this; }
/** * Returns the allowed transitions for the current state. * * @param \Drupal\Core\Entity\EntityInterface|NULL $entity * The entity at hand. May be NULL (E.g., on a Field settings page). * @param string $field_name * @param \Drupal\Core\Session\AccountInterface|NULL $account * @param bool|FALSE $force * * @return \Drupal\workflow\Entity\WorkflowConfigTransition[] * An array of id=>transition pairs with allowed transitions for State. */ public function getTransitions(EntityInterface $entity = NULL, $field_name = '', AccountInterface $account = NULL, $force = FALSE) { $transitions = array(); if (!($workflow = $this->getWorkflow())) { // No workflow, no options ;-) return $transitions; } // @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController /** * Get permissions of user, adding a Role to user, depending on situation. */ // Load a User object, since we cannot add Roles to AccountInterface. /* @var $user \Drupal\user\UserInterface */ $user = workflow_current_user($account); // Determine if user is owner of the entity. $is_owner = WorkflowManager::isOwner($user, $entity); // Check allow-ability of state change if user is not superuser (might be cron) $type_id = $this->getWorkflowId(); if ($user->hasPermission("bypass {$type_id} workflow_transition access")) { // Superuser is special. And $force allows Rules to cause transition. $force = TRUE; } elseif ($is_owner) { $user->addRole(WORKFLOW_ROLE_AUTHOR_RID); } /** * Get the object and its permissions. */ /* @var $transitions WorkflowConfigTransition[] */ $transitions = $workflow->getTransitionsByStateId($this->id(), ''); /** * Determine if user has Access. */ // Use default module permissions. foreach ($transitions as $key => $transition) { if (!$transition->isAllowed($user, $force)) { unset($transitions[$key]); } } // Let custom code add/remove/alter the available transitions, // using the new drupal_alter. // Modules may veto a choice by removing a transition from the list. // Lots of data can be fetched via the $transition object. $context = array('user' => $user, 'workflow' => $workflow, 'state' => $this, 'force' => $force); \Drupal::moduleHandler()->alter('workflow_permitted_state_transitions', $transitions, $context); /** * Determine if user has Access. */ // As of 8.x-1.x, below hook() is removed, in favour of above alter(). // Let custom code change the options, using old_style hook. // Above drupal_alter() calls hook_workflow_permitted_state_transitions_alter() only once. // foreach ($transitions as $transition) { // $to_sid = $transition->to_sid; // $permitted = array(); // // // We now have a list of config_transitions. Check each against the Entity. // // Invoke a callback indicating that we are collecting state choices. // // Modules may veto a choice by returning FALSE. // // In this case, the choice is never presented to the user. // if (!$force) { // // TODO: D8-port: simplify interface for workflow_hook. Remove redundant context. // $permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition permitted', $transition, $user]); // } // // // If vetoed by a module, remove from list. // if (in_array(FALSE, $permitted, TRUE)) { // unset($transitions[$transition->id()]); // } // } return $transitions; }
/** * Implements ContentEntityForm::copyFormValuesToEntity(), and is called from: * - WorkflowTransitionForm::buildEntity() * - WorkflowDefaultWidget * * N.B. in contrary to ContentEntityForm::copyFormValuesToEntity(), * - parameter 1 is returned as result, to be able to create a new Transition object. * - parameter 3 is not $form_state (from Form), but an $item array (from Widget). * * @param \Drupal\Core\Entity\EntityInterface $entity * @param array $form * @param array $item * * @return \Drupal\workflow\Entity\WorkflowTransitionInterface */ public static function copyFormItemValuesToEntity(EntityInterface $entity, array $form, array $item) { /** * Input */ $user = workflow_current_user(); // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed. /* @var $transition WorkflowTransitionInterface */ $transition = $entity; /** * Derived input */ // Make sure we have subset ['workflow_scheduled_date_time'] if (isset($item['to_sid'])) { // In WorkflowTransitionForm, we receive the complete $form_state. // Remember, the workflow_scheduled element is not set on 'add' page. $scheduled = !empty($item['workflow_scheduling']['scheduled']); $schedule_values = $scheduled ? $item['workflow_scheduling']['date_time'] : []; } else { $entity_id = $transition->getTargetEntityId(); drupal_set_message(t('Error: content !id has no workflow attached. The data is not saved.', array('!id' => $entity_id)), 'error'); // The new state is still the previous state. return $transition; } // Get user input from element. $to_sid = $item['to_sid']; $comment = $item['comment']; $force = FALSE; // @todo D8: add the VBO use case. // // Determine if the transition is forced. // // This can be set by a 'workflow_vbo action' in an additional form element. // $force = isset($form_state['input']['workflow_force']) ? $form_state['input']['workflow_force'] : FALSE; // if (!$entity) { // // E.g., on VBO form. // } // @todo D8-port: add below exception. /* // Extract the data from $items, depending on the type of widget. // @todo D8: use MassageFormValues($item, $form, $form_state). $old_sid = workflow_node_previous_state($entity, $entity_type, $field_name); if (!$old_sid) { // At this moment, $old_sid should have a value. If the content does not // have a state yet, old_sid contains '(creation)' state. But if the // content is not associated to a workflow, old_sid is now 0. This may // happen in workflow_vbo, if you assign a state to non-relevant nodes. $entity_id = entity_id($entity_type, $entity); drupal_set_message(t('Error: content !id has no workflow attached. The data is not saved.', array('!id' => $entity_id)), 'error'); // The new state is still the previous state. $new_sid = $old_sid; return $new_sid; } */ $timestamp = REQUEST_TIME; if ($scheduled) { // Fetch the (scheduled) timestamp to change the state. // Override $timestamp. $scheduled_date_time = implode(' ', array($schedule_values['workflow_scheduled_date'], $schedule_values['workflow_scheduled_hour'])); $timezone = $schedule_values['workflow_scheduled_timezone']; $old_timezone = date_default_timezone_get(); date_default_timezone_set($timezone); $timestamp = strtotime($scheduled_date_time); date_default_timezone_set($old_timezone); if (!$timestamp) { // Time should have been validated in form/widget. $timestamp = REQUEST_TIME; } } /** * Process */ /* * Create a new ScheduledTransition. */ if ($scheduled) { $transition_entity = $transition->getTargetEntity(); $field_name = $transition->getFieldName(); $from_sid = $transition->getFromSid(); /* @var $transition WorkflowTransitionInterface */ $transition = WorkflowScheduledTransition::create([$from_sid, 'field_name' => $field_name]); $transition->setTargetEntity($transition_entity); $transition->setValues($to_sid, $user->id(), $timestamp, $comment); } if (!$transition->isExecuted()) { // Set new values. // When editing an existing Transition, only comments may change. $transition->set('to_sid', $to_sid); $transition->setOwner($user); $transition->setTimestamp($timestamp); $transition->schedule($scheduled); $transition->force($force); } $transition->setComment($comment); // Explicitely set $entity in case of ScheduleTransition. It is now returned as parameter, not result. $entity = $transition; return $transition; }
/** * {@inheritdoc} * * Implements workflow_transition() -> WorkflowDefaultWidget::submit(). * * Overrides submit(array $form, array &$form_state). * Contains 2 extra parameters for D7 * * @param array $form * @param array $form_state * @param array $items * The value of the field. * @param bool $force * TRUE if all access must be overridden, e.g., for Rules. * * @return int * If update succeeded, the new State Id. Else, the old Id is returned. * * This is called from function _workflowfield_form_submit($form, &$form_state) * It is a replacement of function workflow_transition($entity, $to_sid, $force, $field) * It performs the following actions; * - save a scheduled action * - update history * - restore the normal $items for the field. * @todo: remove update of {node_form} table. (separate task, because it has features, too) */ public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { $user = workflow_current_user(); // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed. // Set the new value. // Beware: We presume cardinality = 1 !! // The widget form element type has transformed the value to a // WorkflowTransition object at this point. We need to convert it // back to the regular 'value' string format. foreach ($values as &$item) { if (!empty($item)) { // } && $item['value'] instanceof DrupalDateTime) { // The following can NOT be retrieved from the WorkflowTransition. /* @var $entity EntityInterface */ $entity = $form_state->getFormObject()->getEntity(); /* @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */ $transition = $item['workflow_transition']; $field_name = $transition->getFieldName(); // N.B. Use a proprietary version of copyFormValuesToEntity, // where $entity/$transition is passed by reference. // $this->copyFormValuesToEntity($entity, $form, $form_state); /* @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */ $transition = WorkflowTransitionElement::copyFormItemValuesToEntity($transition, $form, $item); // Try to execute the transition. Return $from_sid when error. if (!$transition) { // This should not be possible (perhaps when testing/developing). drupal_set_message(t('Error: the transition from %from_sid to %to_sid could not be generated.'), 'error'); // The current value is still the previous state. $to_sid = $from_sid; } else { // The transition may be scheduled or not. Save the result, and // rely upon hook workflow_entity_insert/update($entity) in // file workflow.module to save/execute the transition. // - validate option; add hook to let other modules change comment. // - add to history; add to watchdog // Return the new State ID. (Execution may fail and return the old Sid.) // Get the new value from an action button if set in the workflow settings. $action_info = _workflow_transition_form_get_triggering_button($form_state); if ($field_name == $action_info['field_name']) { $transition->to_sid->value = $action_info['to_sid']; } $force = FALSE; // @TODO D8-port: add to form for usage in VBO. // Now, save/execute the transition. $from_sid = $transition->getFromSid(); $force = $force || $transition->isForced(); if (!$transition->isAllowed($user, $force)) { // Transition is not allowed. $to_sid = $from_sid; } elseif (!$entity || !$entity->id()) { // Entity is inserted. The Id is not yet known. // So we can't yet save the transition right now, but must rely on // function/hook workflow_entity_insert($entity) in file workflow.module. // $to_sid = $transition->execute($force); $to_sid = $transition->getToSid(); } else { // Entity is updated. To stay in sync with insert, we rely on // function/hook workflow_entity_update($entity) in file workflow.module. // $to_sid = $transition->execute($force); $to_sid = $transition->getToSid(); } } // Now the data is captured in the Transition, and before calling the // Execution, restore the default values for Workflow Field. // For instance, workflow_rules evaluates this. // // Set the transition back, to be used in hook_entity_update(). $item['workflow_transition'] = $transition; // // Set the value at the proper location. $item['value'] = $to_sid; } } return $values; }