loadInclude('workflow', 'inc', 'workflow.form'); \Drupal::moduleHandler()->loadInclude('workflow', 'inc', 'workflow.field'); /********************************************************************** * Info hooks. */ /** * Implements hook_help(). * * @param string $route_name * A route. * * @return string * The html output. */ function workflow_help($route_name) { $output = ''; switch ($route_name) { case 'help.page.workflow': $output .= '

' . t('About') . '

'; $output .= '

' . t('The Workflow module adds a field to Entities to store field values as Workflow states. You can control "state transitions" and add action to specific transitions.') . '

'; } return $output; } /** * Implements hook_hook_info(). * * Allow adopters to place their hook implementations in either * their main module or in a module.workflow.inc file. */ function workflow_hook_info() { $hooks['workflow'] = ['group' => 'workflow']; return $hooks; } /********************************************************************** * CRUD hooks. */ /** * Implements hook_user_cancel(). * * Update tables for deleted account, move account to user 0 (anon.) * ALERT: This may cause previously non-Anonymous posts to suddenly * be accessible to Anonymous. * * @param $edit * @param \Drupal\Core\Session\AccountInterface $account * An account object. * @param string $method * * @see hook_user_cancel() */ function workflow_user_cancel($edit, AccountInterface $account, $method) { WorkflowManager::cancelUser($edit, $account, $method); } /** * Implements hook_user_delete(). * * @param \Drupal\Core\Session\AccountInterface $account * An account object. * * @todo: hook_user_delete does not exist. hook_ENTITY_TYPE_delete? */ function workflow_user_delete($account) { WorkflowManager::deleteUser($account); } /** * Implements hook_ENTITY_TYPE_insert(). * * Is called when adding a new Workflow type. * The technical name for the Workflow entity is 'workflow_type'. * * @param \Drupal\Core\Entity\EntityInterface $entity * An Entity. */ function workflow_workflow_type_insert(EntityInterface $entity) { WorkflowManager::participateUserRoles($entity); } /** * Implements hook_entity_insert(). * * @param \Drupal\Core\Entity\EntityInterface $entity * An Entity. */ function workflow_entity_insert(EntityInterface $entity) { // Execute updates in hook_presave() to revert executions, // Execute inserts in hook_insert, to have the Entity ID determined. _workflow_execute_transitions($entity); } /** * Implements hook_entity_presave(). * * @param \Drupal\Core\Entity\EntityInterface $entity * An EntityInterface object. */ function workflow_entity_presave(EntityInterface $entity) { if (!$entity->isNew()) { // Avoid a double call by hook_entity_presave and hook_entity_insert. _workflow_execute_transitions($entity); } } /** * Execute transitions. if prohibited, restore original field value. * - insert: use hook_insert(), to have the Entity ID determined when saving transitions. * - update: use hook_presave() to revert executions, * - so, do not use hook_update(). * * @param \Drupal\Core\Entity\EntityInterface $entity * An Entity. */ function _workflow_execute_transitions(EntityInterface $entity) { // Avoid this hook on workflow objects. if (WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) { return; } // Execute/save the transitions fom the widgets in the entity form. WorkflowManager::executeTransitionsOfEntity($entity); } /** * Implements hook_entity_delete(). * * Delete the corresponding workflow table records. * * @param \Drupal\Core\Entity\EntityInterface $entity * An Entity. */ function workflow_entity_delete(EntityInterface $entity) { // @todo: test with multiple workflows. if (get_class($entity) == 'Drupal\field\Entity\FieldConfig' || get_class($entity) == 'Drupal\field\Entity\FieldStorageConfig') { // A Workflow Field is removed from an entity. $field_config = $entity; /** @var \Drupal\Core\Entity\ContentEntityBase $field_config */ $entity_type = $field_config->get('entity_type'); $field_name = $field_config->get('field_name'); /** @var $transition Drupal\workflow\Entity\WorkflowTransitionInterface */ foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [], [], $field_name) as $transition) { $transition->delete(); } foreach (WorkflowTransition::loadMultipleByProperties($entity_type, [], [], $field_name) as $transition) { $transition->delete(); } } elseif (!WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) { // A 'normal' entity is deleted. foreach ($fields = _workflow_info_fields($entity) as $field_id => $field_storage) { $entity_id = $entity->id(); $entity_type = $field_storage->getTargetEntityTypeId(); $field_name = $field_storage->getName(); /** @var $transition Drupal\workflow\Entity\WorkflowTransitionInterface */ foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $transition) { $transition->delete(); } foreach (WorkflowTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $transition) { $transition->delete(); } } } } /** * Implements hook_cron(). * * Given a time frame, execute all scheduled transitions. */ function workflow_cron() { WorkflowManager::executeScheduledTransitionsBetween(0, \Drupal::time()->getRequestTime()); } /** * Business related functions, the API. */ /** * @param \Drupal\workflow\Entity\WorkflowTransitionInterface $transition * A WorkflowTransition. * @param bool $force * Indicator if the transition must be forces. * * @return string * A string. */ function workflow_execute_transition(WorkflowTransitionInterface $transition, $force = FALSE) { // Execute transition and update the attached entity. return $transition->executeAndUpdateEntity($force); } /** * Functions to get an options list (to show in a Widget). * To be used in non-OO modules, like workflow_rules, workflow_views. * * The naming convention is workflow_get__names. * (A bit different from 'user_role_names'.) * Can be used for hook_allowed_values from list.module: * - user_role * - workflow * - workflow_state * - sid. */ /** * Retrieves the names of roles matching specified conditions. * * Deprecated D8: workflow_get_roles --> workflow_get_user_role_names. * * Usage: * D7: $roles = workflow_get_user_role_names('participate in workflow'); * D8: $type_id = $workflow->id(); * D8: $roles = workflow_get_user_role_names("create $type_id workflow_transition"); * * @param string $permission * (optional) A string containing a permission. If set, only roles * containing that permission are returned. Defaults to NULL, which * returns all roles. * Normal usage for filtering roles that are enabled in a workflow_type * would be: $permission = 'create $type_id transition'. * * @return array * Array of role names keyed by role ID, including the 'author' role. */ function workflow_get_user_role_names($permission) { static $roles = NULL; if ($roles[$permission]) { return $roles[$permission]; } // Copied from AccountForm::form(). $roles[$permission] = array_map(['\Drupal\Component\Utility\Html', 'escape'], [WORKFLOW_ROLE_AUTHOR_RID => '(' . t(WORKFLOW_ROLE_AUTHOR_NAME) . ')'] + user_role_names(FALSE, $permission)); return $roles[$permission]; } /** * Get an options list for workflow states. * * @param mixed $wid * The Workflow ID. * @param bool $grouped * Indicates if the value must be grouped per workflow. * This influences the rendering of the select_list options. * * @return array * An array of $sid => state->label(), grouped per Workflow. */ function workflow_get_workflow_state_names($wid = '', $grouped = FALSE) { $options = []; // @todo: implement $add parameter. // // @todo: follow Options pattern // @see callback_allowed_values_function() // @see options_allowed_values() // Get the (user-dependent) options. // Since this function is only used in UI, it is save to use the global $user. $user = workflow_current_user(); /** @var $workflows Workflow[] */ $workflows = Workflow::loadMultiple($wid ? [$wid] : NULL); // Do not group if only 1 Workflow is configured or selected. $grouped = count($workflows) == 1 ? FALSE : $grouped; foreach ($workflows as $wid => $workflow) { /** @var $state WorkflowState */ $state = WorkflowState::create(['wid' => $wid]); $workflow_options = $state->getOptions(NULL, '', $user, FALSE); if (!$grouped) { $options += $workflow_options; } else { // Make a group for each Workflow. $options[$workflow->label()] = $workflow_options; } } return $options; } /** * Get an options list for workflows. Include an initial empty value * if requested. Validate each workflow, and generate a message if not complete. * * @param bool $required * Indicates if the resulting list contains a options value. * * @return array * An array of $wid => workflow->label(). */ function workflow_get_workflow_names($required = TRUE) { $options = []; if (!$required) { $options[''] = t('- Select a value -'); } foreach (Workflow::loadMultiple() as $wid => $workflow) { /** @var $workflow Workflow */ if ($workflow->isValid()) { $options[$wid] = $workflow->label(); } } return $options; } /** * Gets an Options list of field names. * * @param \Drupal\Core\Entity\EntityInterface $entity * An entity. * @param string $entity_type * An entity_type. * @param string $entity_bundle * An entity. * @param string $field_name * A field name. * * @return array * An list of field names. */ function workflow_get_workflow_field_names(EntityInterface $entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') { $result = []; foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name) as $definition) { $field_name2 = $definition->getName(); $result[$field_name2] = $field_name2; } return $result; } /** * Helper function, to get the label of a given State Id. * deprecated: workflow_get_sid_label() --> workflow_get_sid_name() * * @param string $sid * A State ID. * * @return string * An translated label. */ function workflow_get_sid_name($sid) { if (empty($sid)) { $label = 'No state'; } /** @noinspection PhpAssignmentInConditionInspection */ elseif ($state = WorkflowState::load($sid)) { $label = $state->label(); } else { $label = 'Unknown state'; } return t($label); } /** * Determines the Workflow field_name of an entity. * If an entity has multiple workflows, only returns the first one. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity at hand. * @param string $field_name * The field name. If given, will be passed as return value. * * @return string * The found Field name. */ function workflow_get_field_name(EntityInterface $entity, $field_name = '') { if (!$entity) { // $entity may be empty on Entity Add page. return ''; } if ($field_name) { return $field_name; } $fields = workflow_get_workflow_field_names($entity); $field_name = reset($fields); return $field_name; } /** * Functions to get the state of an entity. */ /** * Wrapper function to get a UserInterface object. * We use UserInterface to check permissions. * * @param \Drupal\Core\Session\AccountInterface|null $account * An Account. * * @return \Drupal\user\UserInterface * A User. */ function workflow_current_user(AccountInterface $account = NULL) { $account = ($account) ? $account : \Drupal::currentUser(); return User::load($account->id()); } /** * Gets the current state ID of a given entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * An entity. * @param string $field_name * A Field name. * * @return string * The current State ID. */ function workflow_node_current_state(EntityInterface $entity, $field_name = '') { return WorkflowManager::getCurrentStateId($entity, $field_name); } /** * Gets the previous state ID of a given entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * An entity. * @param string $field_name * A field_name. * * @return string * The previous State ID. */ function workflow_node_previous_state(EntityInterface $entity, $field_name = '') { return WorkflowManager::getPreviousStateId($entity, $field_name); } /** * Get a specific workflow, given an entity type. * Only one workflow is possible per node type. * Caveat: gives undefined results with multiple workflows per entity. * @todo: support multiple workflows per entity. * * @param string $entity_bundle * An entity bundle. * @param string $entity_type * An entity type. This is passed when also the Field API must be checked. * * @return \Drupal\workflow\Entity\Workflow * A Workflow object, or NULL if no workflow is retrieved. */ function workflow_get_workflows_by_type($entity_bundle, $entity_type) { static $map = []; if (isset($map[$entity_type][$entity_bundle])) { return $map[$entity_type][$entity_bundle]; } $wid = FALSE; if (isset($entity_type)) { foreach (_workflow_info_fields(NULL, $entity_type, $entity_bundle) as $field_info) { $wid = $field_info->getSetting('workflow_type'); } } // Set the cache with a workflow object. $map[$entity_type][$entity_bundle] = NULL; if ($wid) { $map[$entity_type][$entity_bundle] = Workflow::load($wid); } return $map[$entity_type][$entity_bundle]; } /** * Finds the Workflow fields on a given Entity type. * * @return mixed * * @see EntityFieldManager::getFieldMapByFieldType */ function workflow_get_workflow_fields_by_entity_type() { return \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow'); } /** * Gets the workflow field names, if not known already. * * @param \Drupal\Core\Entity\EntityInterface $entity * Object to work with. May be empty, e.g., on menu build. * @param string $entity_type * Entity type of object. Optional, but required if $entity provided. * @param string $entity_bundle * Bundle of entity. Optional. * @param string $field_name * Field name. Optional. * * @return Drupal\field\Entity\FieldStorageConfig[] * An array of FieldStorageConfig objects. */ function _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') { $field_info = []; // Figure out the $entity's bundle and id. if ($entity) { $entity_type = $entity->getEntityTypeId(); $entity_bundle = $entity->bundle(); } else { // Entity type and bundle should be specified. } $field_list = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow'); foreach ($field_list as $e_type => $data) { if (!$entity_type || ($entity_type == $e_type)) { foreach ($data as $f_name => $value) { if (!$entity_bundle || isset($value['bundles'][$entity_bundle])) { if (!$field_name || ($field_name == $f_name)) { // Do not use the field_name as ID, but the // unique . since you cannot share the // same field on multiple entity_types (unlike D7). $field_config = FieldStorageConfig::loadByName($e_type, $f_name); if ($field_config) { $field_info[$field_config->id()] = $field_config; } else { // The field is a base/extra field. // not a configurable Field via Field UI. // Re-fetch the field definitions, with extra data. $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($e_type, $entity_bundle); // @todo: ?? Loop over bundles? /** @var \Drupal\Core\Field\BaseFieldDefinition $field_config */ $field_config = $field_definitions[$f_name]; if ($field_config) { $field_info[$field_config->getUniqueStorageIdentifier()] = $field_config; } else { // @todo: ?? Loop over bundles? } } } } } } } return $field_info; } /** * Helper function to get the entity from a route. * * This is a hack. It should be solved by using $route_match. * * @param \Drupal\Core\Entity\EntityInterface $entity * An optional entity. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * A route. * * @return \Drupal\Core\Entity\EntityInterface * Entity from the route. */ function workflow_url_get_entity(EntityInterface $entity = NULL, RouteMatchInterface $route_match = NULL) { if ($entity) { return $entity; } if (!$route_match) { $route_match = \Drupal::routeMatch(); } $entities = []; foreach ($route_match->getParameters() as $param) { if ($param instanceof EntityInterface) { $entities[] = $param; } } $value = reset($entities); if ($value && is_object($value)) { return $value; } if ($value && !is_object($value)) { // On workflow tab, we'd get an id. // This is an indicator that the route is mal-configured. workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'route declaration is not optimal.'); /* Return $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($value); */ return NULL; } return $value; } /** * Helper function to get the field name from a route. * * For now only used for ../{entity_id}/workflow history tab. * * @return string|null * Return $field_name */ function workflow_url_get_field_name() { return workflow_url_get_parameter('field_name'); } /** * Helper function to get the entity from a route. * * @return mixed|string * Return $operation */ function workflow_url_get_operation() { $url = Url::fromRoute(''); // The last part of the path is the operation: edit, workflow, devel. $url_parts = explode('/', $url->toString()); $operation = array_pop($url_parts); // Except for view pages. if (is_numeric($operation) || $operation == 'view') { $operation = ''; } return $operation; } /** * Helper function to get arbitrary parameter from a route. * * @param string $parameter * The requested parameter. * * @return string * field_name */ function workflow_url_get_parameter($parameter) { return \Drupal::routeMatch()->getParameter($parameter); // Return \Drupal::request()->get($parameter); } /** * Helper function to determine Workflow from Workflow UI URL. * * @return \Drupal\workflow\Entity\Workflow * Workflow Object. */ function workflow_url_get_workflow() { /** @var $workflows \Drupal\workflow\Entity\Workflow[] */ static $workflows = []; $wid = workflow_url_get_parameter('workflow_type'); if (is_object($wid)) { // $wid is a Workflow object. return $wid; } if (!isset($workflows[$wid])) { // $wid is a string. $workflows[$wid] = $wid ? Workflow::load($wid) : NULL; } return $workflows[$wid]; } /** * Helper function to determine the title of the page. * * Used in file workflow_ui.routing.yml. * * @return \Drupal\Core\StringTranslation\TranslatableMarkup * the page title. */ function workflow_url_get_title() { $label = ''; // Get the Workflow from the page. /** @var $workflow \Drupal\workflow\Entity\Workflow */ /** @noinspection PhpAssignmentInConditionInspection */ if ($workflow = workflow_url_get_workflow()) { $label = $workflow->label(); } $title = t('Edit @entity %label', ['@entity' => 'Workflow', '%label' => $label]); return $title; } /** * Helper function to determine Workflow from Workflow UI URL. * * @param string $url * URL. * * @return mixed * the Workflow type. */ function workflow_url_get_form_type($url = '') { // For some reason, $_SERVER is not allowed as default. $url = ($url == '') ? $_SERVER['REQUEST_URI'] : $url; $base_url = '/config/workflow/workflow/'; $string = substr($url, strpos($url, $base_url) + strlen($base_url)); $type = explode('/', $string)[1]; return $type; } /** * Helper function for D8-port: Get some info on screen. * * @param string $class_name * @param string $function_name * @param string $line_nr * @param string $value1 * @param string $value2 * * @see workflow_devel-module * * Usage: * workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', ''); */ function workflow_debug($class_name, $function_name, $line_nr = '', $value1 = '', $value2 = '') { $debug_switch = FALSE; // $debug_switch = TRUE; if (!$debug_switch) { return; } $class_name_elements = explode("\\", $class_name); $output = 'Testing... function ' . end($class_name_elements) . '::' . $function_name . '/' . $line_nr; if ($value1) { $output .= ' = ' . $value1; } if ($value2) { $output .= ' > ' . $value2; } drupal_set_message($output, 'warning', TRUE); }