/** * {@inheritdoc} */ public function load() { $entities = array(); // TODO: D8-port: get entity from proper core methods. /* @var $entity EntityInterface */ $entity = $this->workflow_entity; // N.B. This is a custom variable. $field_name = workflow_url_get_field_name(); $entity_type = $entity->getEntityTypeId(); $entity_id = $entity->id(); // @todo D8-port: document $limit. // @todo d8-port: $limit should be used in pager, not in load(). $this->limit = \Drupal::config('workflow.settings')->get('workflow_states_per_page'); $limit = $this->limit; // Get Transitions with highest timestamp first. $entities = WorkflowTransition::loadMultipleByProperties($entity_type, array($entity_id), [], $field_name, '', $limit, 'DESC'); return $entities; }
/** * {@inheritdoc} */ public function build() { $form = []; // Get the entity for this form. /* @var $entity EntityInterface */ if (!($entity = workflow_url_get_entity())) { return $form; } // Get the field name. Avoid error on Node Add page. if (!($field_name = workflow_get_field_name($entity))) { return $form; } /* * Output: generate the Transition Form. */ // Create a transition, to pass to the form. No need to use setValues(). $current_sid = workflow_node_current_state($entity, $field_name); $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); // Add the WorkflowTransitionForm to the page. $form = $this->entityFormBuilder()->getForm($transition, 'add'); return $form; }
/** * Deactivate a Workflow State, moving existing content to a given State. * * @param int $new_sid * The state ID, to which all affected entities must be moved. */ public function deactivate($new_sid) { $current_sid = $this->id(); $force = TRUE; // Notify interested modules. We notify first to allow access to data before we zap it. // - re-parents any entity that we don't want to orphan, whilst deactivating a State. // - delete any lingering entity to state values. // \Drupal::moduleHandler()->invokeAll('workflow', ['state delete', $current_sid, $new_sid, NULL, $force]); // Invoke the hook. \Drupal::moduleHandler()->invokeAll('entity_' . $this->getEntityTypeId() . '_predelete', array($this, $current_sid, $new_sid)); // Re-parent any entity that we don't want to orphan, whilst deactivating a State. // TODO D8-port: State should not know about Transition: move this to Workflow->DeactivateState. if ($new_sid) { // A candidate for the batch API. // @TODO: Future updates should seriously consider setting this with batch. $user = \Drupal::currentUser(); // We can use global, since deactivate() is a UI-only function. $comment = t('Previous state deleted'); foreach (_workflow_info_fields() as $field_name => $field_info) { $entity = NULL; $entity_type = $field_info->getTargetEntityTypeId(); $field_name = $field_info->getName(); $query = \Drupal::entityQuery($entity_type); $query->condition($field_name, $current_sid, '='); $result = $entity_type == 'comment' ? array() : $query->execute(); foreach ($result as $entity_id) { $entity = \Drupal::entityManager()->getStorage($entity_type)->load($entity_id); $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); $transition->setValues($new_sid, $user->id(), REQUEST_TIME, $comment, TRUE); $transition->force($force); // Execute Transition, invoke 'pre' and 'post' events, save new state in Field-table, save also in workflow_transition_history. // For Workflow Node, only {workflow_node} and {workflow_transition_history} are updated. For Field, also the Entity itself. $new_sid = workflow_execute_transition($transition, $force); } } } // Delete the transitions this state is involved in. $workflow = Workflow::load($this->wid); foreach ($workflow->getTransitionsByStateId($current_sid, '') as $transition) { $transition->delete(); } foreach ($workflow->getTransitionsByStateId('', $current_sid) as $transition) { $transition->delete(); } // Delete the state. -- We don't actually delete, just deactivate. // This is a matter up for some debate, to delete or not to delete, since this // causes name conflicts for states. In the meantime, we just stick with what we know. // If you really want to delete the states, use workflow_cleanup module, or delete(). $this->status = FALSE; $this->save(); // Clear the cache. self::loadMultiple([], 0, TRUE); }
/** * {@inheritdoc} */ public static function getPreviousStateId(EntityInterface $entity, $field_name = '') { $sid = ''; if (!$entity) { return $sid; } // If $field_name is not known, yet, determine it. $field_name = $field_name ? $field_name : workflow_get_field_name($entity, $field_name); // If $field_name is found, get more details. if (!$field_name) { // Return the initial value. return $sid; } if (isset($entity->original)) { // A changed node. workflow_debug(__FILE__, __FUNCTION__, __LINE__, $sid); // @todo D8-port: still test this snippet. } // A node may not have a Workflow attached. if ($entity->isNew()) { // A new Node. D7: $is_new is not set when saving terms, etc. $sid = self::getCreationStateId($entity, $field_name); } elseif (!$sid) { // @todo?: Read the history with an explicit langcode. $langcode = ''; // $entity->language()->getId(); $entity_type = $entity->getEntityTypeId(); if ($last_transition = WorkflowTransition::loadByProperties($entity_type, $entity->id(), [], $field_name, $langcode, 'DESC')) { $sid = $last_transition->getFromSid(); } } if (!$sid) { // No history found on an existing entity. $sid = self::getCreationStateId($entity, $field_name); } return $sid; }
/** * {@inheritdoc} * * Be careful: Widget may be shown in very different places. Test carefully!! * - On a entity add/edit page * - On a entity preview page * - On a entity view page * - On a entity 'workflow history' tab * - On a comment display, in the comment history * - On a comment form, below the comment history * * @todo D8: change "array $items" to "FieldInterface $items" */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $wid = $this->getFieldSetting('workflow_type'); if (!($workflow = Workflow::load($wid))) { // @todo: add error message. return $element; } /* @var $item \Drupal\workflowfield\Plugin\Field\FieldType\WorkflowItem */ $item = $items[$delta]; /* @var $field_config \Drupal\field\Entity\FieldConfig */ $field_config = $item->getFieldDefinition(); /* @var $field_storage \Drupal\field\Entity\FieldStorageConfig */ $field_storage = $field_config->getFieldStorageDefinition(); $field_name = $field_storage->get('field_name'); $entity = $item->getEntity(); $from_sid = workflow_node_current_state($entity, $field_name); // Create a transition, to pass to the form. No need to use setValues(). /* @var $transition WorkflowTransition */ $transition = WorkflowTransition::create([$from_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); if (!$this->isDefaultValueWidget($form_state)) { // Here, not the $element is added, but the entity form. // Option 1: use the Element. $element['#default_value'] = $transition; $element += WorkflowTransitionElement::transitionElement($element, $form_state, $form); // Option 2: use the Form, in order to get extra fields. //$entity_form_builder = \Drupal::getContainer()->get('entity.form_builder'); //$element += $entity_form_builder->getForm($transition, 'add'); //// Remove the action button. The Entity itself has one. //unset($element['actions']); // Option 3: use the true Element. // $form = $this->element($form, $form_state, $transition); //$element['workflow_transition'] = array( // '#type' => 'workflow_transition', // '#title' => t('Workflow transition'), // '#default_value' => $transition, // ); } else { // @todo D8: add a default value, so people can set a default comment. // On the Field settings page, User may not set a default value // (this is done by the Workflow module). // @see WorkflowState::getOptions(); // @see WorkflowDefaultWidget::formElement(); $element = array(); return $element; workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', ''); // @todo D8-port: still test this snippet. // @see workflowfield_form_field_config_edit_form_alter for other settings // The Workflow field must have a value, so set to required. // Unfortunately, we need hook_form_alter for this. //$form['required']['#default_value'] = 1; //$form['required']['#disabled'] = TRUE; // Explicitly set default value to 'creation' and show element, // so people can set a default comment. $transition->to_sid = $workflow->getCreationSid(); $transition->setExecuted(TRUE); // @TODO D8-port: use a proper WorkflowTransitionElement call. $element['#default_value'] = $transition; $element += WorkflowTransitionElement::transitionElement($element, $form_state, $form); // No action buttons on default field settings page. // This is evaluated in hook_form_alter. _workflow_use_action_buttons(FALSE); // Make sure the options box is not hidden (when action buttons active). //$element['to_sid']['#type'] = 'select'; $element['to_sid']['#title'] = 'Initial state'; $element['to_sid']['#access'] = TRUE; unset($element['workflow_current_state']); return $element; } return $element; }
/** * Prepares a transition to be reverted. * * @param \Drupal\workflow\Entity\WorkflowTransitionInterface $transition * The transition to be reverted. * * @return \Drupal\workflow\Entity\WorkflowTransitionInterface * The prepared transition ready to be stored. */ protected function prepareRevertedTransition(WorkflowTransitionInterface $transition) { $user = \Drupal::currentUser(); $entity = $transition->getTargetEntity(); $field_name = $transition->getFieldName(); $current_sid = workflow_node_current_state($entity, $field_name); $previous_sid = $transition->getFromSid(); $comment = t('State reverted.'); $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); $transition->setValues($previous_sid, $user->id(), REQUEST_TIME, $comment); return $transition; }
/** * @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; }
/** * Generates an overview table of older revisions of a node, * but only if this::historyAccess() allows it. * * @param \Drupal\node\NodeInterface $node * A node object. * * @return array * An array as expected by drupal_render(). */ public function historyOverview(EntityInterface $node = NULL) { $form = array(); /* * Get data from parameters. */ // TODO D8-port: make Workflow History tab happen for every entity_type. // For workflow_tab_page with multiple workflows, use a separate view. See [#2217291]. // @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController. // workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet. // ATM it only works for Nodes and Terms. // This is a hack. The Route should always pass an object. // On view tab, $entity is object, // On workflow tab, $entity is id(). // Get the entity for this form. if (!($entity = workflow_url_get_entity($node))) { return $form; } /* * Get derived data from parameters. */ if (!($field_name = workflow_get_field_name($entity, workflow_url_get_field_name()))) { return $form; } /* * Step 1: generate the Transition Form. */ // Create a transition, to pass to the form. No need to use setValues(). $current_sid = workflow_node_current_state($entity, $field_name); $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); // Add the WorkflowTransitionForm to the page. $form = $this->entityFormBuilder()->getForm($transition, 'add'); /* * Step 2: generate the Transition History List. */ $entity_type = 'workflow_transition'; // $form = $this->listing('workflow_transition'); $list_builder = $this->entityManager()->getListBuilder($entity_type); // Add the Node explicitly, since $list_builder expects a Transition. $list_builder->workflow_entity = $entity; $form += $list_builder->render(); /* * Finally: sort the elements (overriding their weight). */ // $form['#weight'] = 10; $form['actions']['#weight'] = 100; $form['workflow_list_title']['#weight'] = 200; $form['table']['#weight'] = 201; return $form; }
/** * Define the fields. Modify the parent fields. * {@inheritdoc} */ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields = array(); // Add the specific ID-field : tid vs. hid. $fields['tid'] = BaseFieldDefinition::create('integer')->setLabel(t('Transition ID'))->setDescription(t('The transition ID.'))->setReadOnly(TRUE)->setSetting('unsigned', TRUE); // Get the rest of the fields. $fields += parent::baseFieldDefinitions($entity_type); // The timestamp has a different description. $fields['timestamp'] = []; // Reset old value. $fields['timestamp'] = BaseFieldDefinition::create('created')->setLabel(t('Scheduled'))->setDescription(t('The date+time this transition is scheduled for.'))->setQueryable(FALSE)->setRevisionable(TRUE); // Remove the specific ID-field : tid vs. hid. unset($fields['hid']); return $fields; }
/** * {@inheritdoc} * * N.B. A large part of this function is taken from CommentDefaultFormatter. */ public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $output = array(); $field_name = $this->fieldDefinition->getName(); $entity = $items->getEntity(); $entity_type = $entity->getEntityTypeId(); $status = $items->status; $workflow_settings = $this->getFieldSettings(); $user = \Drupal::currentUser(); // @todo #2287057: OK? // @todo: Perhaps global user is not always the correct user. // E.g., on ScheduledTransition->execute()? But this function is mostly used in UI. $current_sid = WorkflowManager::getCurrentStateId($entity, $field_name); /* @var $current_state WorkflowState */ $current_state = WorkflowState::load($current_sid); // First compose the current value with the normal formatter from list.module. $elements = workflow_state_formatter($entity, $field_name, $current_sid); // The state must not be deleted, or corrupted. if (!$current_state) { return $elements; } // Check permission, so that even with state change rights, // the form can be suppressed from the entity view (#1893724). $type_id = $current_state->getWorkflowId(); if (!\Drupal::currentUser()->hasPermission("access {$type_id} workflow_transition form")) { return $elements; } // Workflows are added to the search results and search index by // workflow_node_update_index() instead of by this formatter, so don't // return anything if the view mode is search_index or search_result. if (in_array($this->viewMode, array('search_result', 'search_index'))) { return $elements; } if ($entity_type == 'comment') { // No Workflow form allowed on a comment display. // (Also, this avoids a lot of error messages.) return $elements; } // Only build form if user has possible target state(s). if (!$current_state->showWidget($entity, $field_name, $user, FALSE)) { return $elements; } // Create a transition, to pass to the form. No need to use setValues(). $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]); $transition->setTargetEntity($entity); // Remove the default formatter. We are now building the widget. $elements = array(); // BEGIN Copy from CommentDefaultFormatter $elements['#cache']['contexts'][] = 'user.permissions'; $output['workflows'] = []; // Add the WorkflowTransitionForm to the page. // $build = $this->viewBuilder->viewMultiple($workflows); $build = $this->entityFormBuilder()->getForm($transition, 'add'); $output['workflows'] += $build; // Only show the add workflow form if the user has permission. $elements['#cache']['contexts'][] = 'user.roles'; // Do not show the form for the print view mode. $elements[] = $output + array('#workflow_type' => $this->getFieldSetting('workflow_type'), '#workflow_display_mode' => $this->getFieldSetting('default_mode'), 'workflows' => array()); // END Copy from CommentDefaultFormatter return $elements; }