/** * {@inheritdoc} */ public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE) { $result = AccessResult::neutral(); $account = $this->prepareUser($account); // $account = workflow_current_user($account); /* @var $transition WorkflowTransitionInterface */ $transition = $entity; // This is only for Edit/Delete transition. For Add/create, use createAccess. switch ($entity->getEntityTypeId()) { case 'workflow_transition': case 'workflow_scheduled_transition': switch ($operation) { case 'update': $is_owner = WorkflowManager::isOwner($account, $transition); $type_id = $transition->getWorkflowId(); if ($account->hasPermission("bypass {$type_id} workflow_transition access")) { $result = AccessResult::allowed()->cachePerPermissions(); } elseif ($account->hasPermission("edit any {$type_id} workflow_transition")) { $result = AccessResult::allowed()->cachePerPermissions(); } elseif ($is_owner && $account->hasPermission("edit own {$type_id} workflow_transition")) { $result = AccessResult::allowed()->cachePerPermissions(); } return $return_as_object ? $result : $result->isAllowed(); break; case 'delete': // The delete operation is not defined for Transitions. $result = AccessResult::forbidden(); break; case 'revert': // @see workflow_operations. // @see workflow_operations. default: $type_id = $transition->getWorkflowId(); $result = parent::access($entity, $account, $return_as_object); //if ($account->hasPermission("bypass $type_id workflow_transition access")) { // $result = AccessResult::allowed()->cachePerPermissions(); //} break; } // End of switch ($operation). break; case 'workflow_config_transition': workflow_debug(__FILE__, __FUNCTION__, __LINE__, $account->id(), $transition->getOwnerId()); // @todo D8-port: still test this snippet. break; } $result = parent::access($entity, $operation, $account, TRUE)->cachePerPermissions(); return $return_as_object ? $result : $result->isAllowed(); }
/** * {@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(); }
/** * 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; }
/** * {@inheritdoc} */ public function getNextSid($entity, $field_name, $user, $force = FALSE) { $new_sid = FALSE; $current_sid = WorkflowManager::getCurrentStateId($entity, $field_name); $current_state = WorkflowState::load($current_sid); $options = $current_state->getOptions($entity, $field_name, $user, $force); // Loop over every option. To find the next one. $flag = $current_state->isCreationState(); $new_sid = $current_state->id(); foreach ($options as $sid => $name) { if ($flag) { $new_sid = $sid; break; } if ($sid == $current_state->id()) { $flag = TRUE; } } return $new_sid; }
/** * Menu access control callback. Checks access to Workflow tab. * * This used to be D7-function workflow_tab_access($user, $entity). * * The History tab should not be used with multiple workflows per entity. * Use the dedicated view for this use case. * @todo D8: remove this in favour of View 'Workflow history per entity'. * @todo D8-port: make this workf for non-Node entity types. * * @param \Drupal\workflow\Controller\AccountInterface $account * Run access checks for this account. * * @return \Drupal\Core\Access\AccessResult */ public function historyAccess(AccountInterface $account) { static $access = array(); $uid = $account ? $account->id() : -1; // TODO D8-port: make Workflow History tab happen for every entity_type. // @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController. // 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. $entity = workflow_url_get_entity(); /* @var $entity EntityInterface */ // Figure out the $entity's bundle and id. $entity_type = $entity->getEntityTypeId(); $entity_bundle = $entity->bundle(); $entity_id = $entity ? $entity->id() : ''; $field_name = workflow_url_get_field_name(); if (isset($access[$uid][$entity_type][$entity_id][$field_name ? $field_name : 'no_field'])) { return $access[$uid][$entity_type][$entity_id][$field_name ? $field_name : 'no_field']; } $access_result = AccessResult::forbidden(); // When having multiple workflows per bundle, use Views display // 'Workflow history per entity' instead! $fields = _workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name); if (!$fields) { return AccessResult::forbidden(); } else { // @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController $uid = $account ? $account->id() : -1; $entity_id = $entity ? $entity->id() : ''; // Determine if user is owner of the entity. $is_owner = WorkflowManager::isOwner($account, $entity); /** * Determine if user has Access. Fill the cache. */ // @todo: what to do with multiple workflow_fields per bundle? Use Views instead! Or introduce a setting. // @TODO D8-port: workflow_tab_access: use proper 'WORKFLOW_TYPE' permissions foreach ($fields as $definition) { $type_id = $definition->getSetting('workflow_type'); if ($account->hasPermission("access any {$type_id} workflow_transion overview")) { $access_result = AccessResult::allowed(); } elseif ($is_owner && $account->hasPermission("access own {$type_id} workflow_transion overview")) { $access_result = AccessResult::allowed(); } elseif ($account->hasPermission('administer nodes')) { $access_result = AccessResult::allowed(); } $access[$uid][$entity_type][$entity_id][$field_name ? $field_name : 'no_field'] = $access_result; } } return $access_result; }
/** * {@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; }