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);
}