updated contrib modules : translation_views, views_bulk_edit, views_bulk_operations, workflow, addtoany, redis, url_to_video_filter

This commit is contained in:
2018-09-12 14:51:18 +02:00
parent 4caa864a8f
commit b8e8cc8cee
99 changed files with 4762 additions and 954 deletions
@@ -66,17 +66,7 @@ field.storage_settings.workflow:
label:
type: label
label: 'Label'
allowed_values_function:
type: string
label: 'Allowed values function'
field.field_settings.workflow:
type: mapping
label: 'Workflow settings'
workflow.settings:
type: config_object
mapping:
workflow_states_per_page:
type: integer
label: 'Number of workflow states displayed per page.'
@@ -6,8 +6,8 @@ package: Workflow
# core: 8.x
type: module
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -66,7 +66,7 @@ function workflow_access_workflow_operations($op, EntityInterface $entity = NULL
}
/**
* Implements hook_entity_insert().
* Implements hook_ENTITY_TYPE_insert().
*
* We use the Role weight as an id.
* In contrary to content_access module, that uses a 'content_access_roles_gids'
@@ -78,11 +78,11 @@ function workflow_access_user_role_insert(EntityInterface $entity) {
// Attend user to Rebuild data, because the weight of a role
// is the key for workflow_Access.
/** @var $entity \Drupal\user\RoleInterface */
node_access_needs_rebuild(TRUE);
node_access_needs_rebuild(TRUE);
}
/**
* Implements hook_access_entity_update().
* Implements hook_access_ENTITY_TYPE_update().
*
* @param EntityInterface $entity
*/
@@ -167,8 +167,7 @@ function workflow_access_node_access_records(\Drupal\node\NodeInterface $node) {
// - when rebuilding permissions via batch for Workflow Fields.
// In that case, we need to create the workflow_transitions ourselves to
// calculate the grants.
foreach (_workflow_info_fields($node, $entity_type) as $field) {
$field_name = $field->getName(); // Do not use id().
foreach (workflow_get_workflow_field_names($node, $entity_type) as $field_name) {
$old_sid = $new_sid = $node->$field_name->value;
// Create a dummy transition, just to set $node->workflow_transitions.
@@ -6,8 +6,8 @@ package: Workflow
dependencies:
- workflow
configure: workflow.cleanup.settings
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -7,8 +7,8 @@ type: module
dependencies:
- workflow
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -5,8 +5,8 @@ type: module
# core: 8.x
hidden: TRUE
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -7,8 +7,8 @@ type: module
dependencies:
- workflow
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -24,16 +24,16 @@ class WorkflowStateListBuilder extends DraggableListBuilder {
$entities = [];
// Get the Workflow from the page.
/** @var $workflow \Drupal\workflow\Entity\Workflow */
if (!$workflow = workflow_url_get_workflow()) {
// @todo: Generate error message.
return $entities;
}
$wid = $url_wid = $workflow->id();
$wid = $workflow->id();
/** @var WorkflowState[] $entities */
$entities = parent::load();
foreach ($entities as $key => $entity) {
if (!isset($entity->wid) || $entity->wid != $wid) {
if ($entity->getWorkflowId() != $wid) {
unset($entities[$key]);
}
}
@@ -99,20 +99,19 @@ class WorkflowConfigTransitionRoleForm extends WorkflowConfigTransitionFormBase
continue;
}
$to_sid = $to_state->id();
$stay_on_this_state = ($to_sid == $from_sid);
// Load existing config_transitions. Create if not found.
$config_transitions = $workflow->getTransitionsByStateId($from_sid, $to_sid);
if (!$config_transition = reset($config_transitions)) {
$config_transition = $workflow->createTransition($from_sid, $to_sid);
}
$stay_on_this_state = !$config_transition->hasStateChange();
$row[$to_sid]['workflow_config_transition'] = ['#type' => 'value', '#value' => $config_transition, ];
$row[$to_sid]['workflow_config_transition'] = ['#type' => 'value', '#value' => $config_transition,];
$row[$to_sid]['roles'] = [
'#type' => $stay_on_this_state ? 'checkboxes' : 'checkboxes',
'#type' => 'checkboxes',
'#options' => $stay_on_this_state ? [] : $roles,
'#disabled' => $stay_on_this_state,
// When $stay_on_this_state, allow all roles.
'#default_value' => $stay_on_this_state ? $allow_all_roles : $config_transition->roles,
];
}
@@ -127,24 +126,30 @@ class WorkflowConfigTransitionRoleForm extends WorkflowConfigTransitionFormBase
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$workflow = $this->workflow;
// If only the 'Creation' state is available,
if (count($form_state->getValue($this->entitiesKey)) < 2) {
$form_state->setErrorByName('id', t('Please create at least one other state.',
[]));
}
// Make sure 'author' is checked for (creation) -> [something].
$creation_state = $workflow->getCreationState();
$creation_state_id = $workflow->getCreationState()->id();
$author_has_permission = FALSE;
foreach ($form_state->getValue($this->entitiesKey) as $from_sid => $to_data) {
foreach ($to_data as $to_sid => $transition_data) {
if (empty($form_state->getValue($this->entitiesKey))) {
$author_has_permission = TRUE;
}
else {
$author_has_permission = FALSE;
foreach ($form_state->getValue($this->entitiesKey) as $from_sid => $to_data) {
foreach ($to_data as $to_sid => $transition_data) {
if ($from_sid == $to_sid) {
// Same-state-transition do not count.
}
elseif (!empty($transition_data['roles'][WORKFLOW_ROLE_AUTHOR_RID])) {
$author_has_permission = TRUE;
if ($from_sid == $creation_state_id) {
// Same-state-transitions do not count.
if ($from_sid != $to_sid) {
if (!empty($transition_data['roles'][WORKFLOW_ROLE_AUTHOR_RID])) {
$author_has_permission = TRUE;
}
break;
}
}
}
}
if (!$author_has_permission) {
@@ -152,7 +157,7 @@ class WorkflowConfigTransitionRoleForm extends WorkflowConfigTransitionFormBase
['%creation' => $creation_state->label()]));
}
return;
parent::validateForm($form, $form_state);
}
/**
@@ -10,8 +10,8 @@ configure: entity.workflow_type.collection
dependencies:
- workflow
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -70,8 +70,8 @@ function workflow_ui_help($route_name, RouteMatchInterface $route_match) {
*
* @deprecated : @see workflow_url_get_workflow .
*/
function workflow_ui_url_get_workflow($url = '' ) {
return workflow_url_get_workflow($url);
function workflow_ui_url_get_workflow() {
return workflow_url_get_workflow();
}
/**
@@ -88,6 +88,6 @@ function workflow_ui_url_get_title() {
*
* @deprecated : @see workflow_url_get_form_type .
*/
function workflow_ui_url_get_form_type($url = '' ) {
function workflow_ui_url_get_form_type($url = '') {
return workflow_url_get_form_type($url);
}
@@ -23,7 +23,10 @@ entity.workflow_state.collection:
_title_callback: 'workflow_url_get_title'
requirements:
_permission: 'administer workflow'
options:
parameters:
workflow_type:
type: entity:workflow_type
### Workflow Transitions
entity.workflow_transition.collection:
@@ -34,6 +37,10 @@ entity.workflow_transition.collection:
_title_callback: 'workflow_url_get_title'
requirements:
_permission: 'administer workflow'
options:
parameters:
workflow_type:
type: entity:workflow_type
### Workflow Labels
entity.workflow_label.collection:
@@ -44,3 +51,7 @@ entity.workflow_label.collection:
_title_callback: 'workflow_url_get_title'
requirements:
_permission: 'administer workflow'
options:
parameters:
workflow_type:
type: entity:workflow_type
@@ -0,0 +1,79 @@
<?php
namespace Drupal\workflow\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\workflow\Entity\WorkflowManager;
use Symfony\Component\Routing\Route;
/**
* Checks access to Workflow tab.
*/
class WorkflowHistoryAccess implements AccessInterface {
/**
* Check if the user has permissions to view this workflow.
*
* @param \Drupal\Core\Session\AccountInterface $account
* Current user account.
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* Current routeMatch.
* @param \Symfony\Component\Routing\Route $route
* Current route.
*
* @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden
* If the user can access to this workflow.
*/
public function access(AccountInterface $account, RouteMatchInterface $routeMatch, Route $route) {
static $access = [];
$uid = ($account) ? $account->id() : -1;
$entity = workflow_url_get_entity();
$entity_id = $entity->id();
$entity_type = $entity->getEntityTypeId();
$entity_bundle = $entity->bundle();
$field_name = workflow_url_get_parameter('field_name'); // @todo: this doesn't work.
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();
}
// @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController
// 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;
}
}
@@ -58,7 +58,7 @@ class WorkflowTransitionListController extends EntityListController implements C
/**
* Generates an overview table of older revisions of a node,
* but only if this::historyAccess() allows it.
* but only if WorkflowHistoryAccess::access() allows it.
*
* @param EntityInterface $node
* A node object.
@@ -137,81 +137,4 @@ class WorkflowTransitionListController extends EntityListController implements C
return $form;
}
/**
* 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 workflow for non-Node entity types.
*
* @param \Drupal\Core\Session\AccountInterface $account
* Run access checks for this account.
*
* @return \Drupal\Core\Access\AccessResult
*/
public function historyAccess(AccountInterface $account) {
static $access = [];
$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;
}
}
@@ -41,7 +41,7 @@ class WorkflowTransitionElement extends FormElement {
* @param array $complete_form
*/
public static function validateTransition(&$element, FormStateInterface $form_state, &$complete_form) {
workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
}
/**
@@ -57,7 +57,7 @@ class WorkflowTransitionElement extends FormElement {
* The Workflow element
*/
public static function processTransition(&$element, FormStateInterface $form_state, &$complete_form) {
workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
return self::transitionElement($element, $form_state, $complete_form);
}
@@ -159,13 +159,6 @@ class WorkflowTransitionElement extends FormElement {
$default_value = FALSE;
}
// Fetch the form ID. This is unique for each entity, to allow multiple form per page (Views, etc.).
// Make it uniquer by adding the field name, or else the scheduling of
// multiple workflow_fields is not independent of each other.
// If we are indeed on a Transition form (so, not a Node Form with widget)
// then change the form id, too.
$form_id = self::getFormId();
/*
* Output: generate the element.
*/
@@ -230,7 +223,7 @@ class WorkflowTransitionElement extends FormElement {
// Add a state formatter before the rest of the form,
// when transition is scheduled or widget is hidden.
if ( (!$show_widget) || $transition_is_scheduled || $transition->isExecuted()) {
if ((!$show_widget) || $transition_is_scheduled || $transition->isExecuted()) {
$element['workflow_current_state'] = workflow_state_formatter($entity, $field_name, $current_sid);
// Set a proper weight, which works for Workflow Options in select list AND action buttons.
$element['workflow_current_state']['#weight'] = -0.005;
@@ -316,6 +309,14 @@ class WorkflowTransitionElement extends FormElement {
'#type' => 'container',
'#tree' => TRUE,
];
// Define class for '#states' behaviour.
// Fetch the form ID. This is unique for each entity, to allow multiple form per page (Views, etc.).
// Make it uniquer by adding the field name, or else the scheduling of
// multiple workflow_fields is not independent of each other.
// If we are indeed on a Transition form (so, not a Node Form with widget)
// then change the form id, too.
$form_id = $form_state->getBuildInfo()['form_id'];
$class_identifier = Html::getClass('scheduled_' . Html::getUniqueId($form_id).'-'.$field_name);
$element['workflow_scheduling']['scheduled'] = [
'#type' => 'radios',
'#title' => t('Schedule'),
@@ -326,7 +327,7 @@ class WorkflowTransitionElement extends FormElement {
'#default_value' => $transition_is_scheduled ? '1' : '0',
'#attributes' => [
// 'id' => 'scheduled_' . $form_id,
'class' => [Html::getClass('scheduled_' . $form_id)],
'class' => [$class_identifier],
],
];
$element['workflow_scheduling']['date_time'] = [
@@ -337,18 +338,18 @@ class WorkflowTransitionElement extends FormElement {
'#suffix' => '</div>',
'#states' => [
//'visible' => array(':input[id="' . 'scheduled_' . $form_id . '"]' => array('value' => '1')),
'visible' => ['input.' . Html::getClass('scheduled_' . $form_id) => ['value' => '1']],
'visible' => ['input.' . $class_identifier => ['value' => '1']],
],
];
$element['workflow_scheduling']['date_time']['workflow_scheduled_date'] = [
'#type' => 'date',
'#prefix' => t('At'),
'#default_value' => implode( '-', [
'#default_value' => implode('-', [
'year' => date('Y', $timestamp),
'month' => date('m', $timestamp),
'day' => date('d', $timestamp),
]
)
),
];
$element['workflow_scheduling']['date_time']['workflow_scheduled_hour'] = [
'#type' => 'textfield',
@@ -419,7 +420,7 @@ class WorkflowTransitionElement extends FormElement {
return 'workflow_transition_form'; //@todo D8-port: add $form_id for widget and History tab.
}
/**
/**
* Implements ContentEntityForm::copyFormValuesToEntity(), and is called from:
* - WorkflowTransitionForm::copyFormValuesToEntity()
* - WorkflowDefaultWidget
@@ -84,6 +84,13 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface {
public $states = [];
public $transitions = [];
/**
* The module implementing this object, for config_export.
*
* @var string
*/
protected $module = 'workflow';
/**
* CRUD functions.
*/
@@ -135,7 +142,10 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface {
*/
public function delete() {
if (!$this->isDeletable()) {
// @todo: throw error if not workflow->isDeletable().
$message = t('Workflow %workflow is not Deletable. Please delete the field where this workflow type is reffered',
['%workflow' => $this->label()]);
drupal_set_message($message, 'error');
return;
}
else {
// Delete associated state (also deletes any associated transitions).
@@ -5,6 +5,7 @@ namespace Drupal\workflow\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\user\UserInterface;
use Drupal\workflow\WorkflowTypeAttributeTrait;
/**
* Workflow configuration entity to persistently store configuration.
@@ -45,6 +46,10 @@ use Drupal\user\UserInterface;
* )
*/
class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfigTransitionInterface {
/*
* Add variables and get/set methods for Workflow property.
*/
use WorkflowTypeAttributeTrait;
// Transition data.
public $id;
@@ -52,11 +57,12 @@ class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfi
public $to_sid;
public $roles = [];
// Extra fields.
protected $wid;
// The following must explicitly defined, and not be public, to avoid errors
// when exporting with json_encode().
protected $workflow = NULL;
/**
* The module implementing this object, for config_export.
*
* @var string
*/
protected $module = 'workflow';
/*
* Entity class functions.
@@ -67,7 +73,11 @@ class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfi
*/
public function __construct(array $values = [], $entityType = NULL) {
// Please be aware that $entity_type and $entityType are different things!
return parent::__construct($values, $entity_type = 'workflow_config_transition');
parent::__construct($values, $entity_type = 'workflow_config_transition');
$state = WorkflowState::load($this->to_sid ? $this->to_sid : $this->from_sid);
if($state) {
$this->setWorkflow($state->getWorkflow());
}
}
/**
@@ -76,8 +86,10 @@ class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfi
* @param $to_sid
*/
public function setValues($from_sid, $to_sid) {
$this->from_sid = $from_sid;
$this->to_sid = $to_sid;
$state = WorkflowState::load($this->to_sid ? $this->to_sid : $this->from_sid);
if($state) {
$this->setWorkflow($state->getWorkflow());
}
}
/**
@@ -148,29 +160,6 @@ class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfi
* Property functions.
*/
/**
* {@inheritdoc}
*/
public function getWorkflow() {
if (!$this->workflow && $wid = $this->getWorkflowId()) {
$this->workflow = Workflow::load($wid);
}
return $this->workflow;
}
/**
* {@inheritdoc}
*/
public function getWorkflowId() {
if (!$this->wid) {
$from_sid = $this->getFromSid();
$to_sid = $this->getToSid();
$state = WorkflowState::load($to_sid ? $to_sid : $from_sid);
$this->wid = $state->getWorkflowId();
}
return $this->wid;
}
/**
* {@inheritdoc}
*/
@@ -219,4 +208,15 @@ class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfi
return TRUE == array_intersect($user->getRoles(), $this->roles);
}
/**
* Determines if the State changes by this Transition.
* @return bool
*/
public function hasStateChange() {
if ($this->from_sid == $this->to_sid) {
return FALSE;
}
return TRUE;
}
}
@@ -31,7 +31,7 @@ interface WorkflowConfigTransitionInterface {
public function isAllowed(UserInterface $user, $force = FALSE);
/**
* Returns the Workflow object of this State.
* Returns the Workflow object of this object.
*
* @return Workflow
* Workflow object.
@@ -39,10 +39,10 @@ interface WorkflowConfigTransitionInterface {
public function getWorkflow();
/**
* Returns the Workflow ID of this Transition
* Returns the Workflow ID of this object.
*
* @return string
* Workflow Id.
* Workflow ID.
*/
public function getWorkflowId();
@@ -66,4 +66,9 @@ interface WorkflowConfigTransitionInterface {
*/
public function getToSid();
/**
* Determines if the State changes by this Transition.
* @return bool
*/
public function hasStateChange();
}
@@ -130,7 +130,7 @@ interface WorkflowInterface {
* @param string $to_sid
* @param array $values
*
* @return mixed|null|static
* @return WorkflowConfigTransitionInterface
*/
public function createTransition($from_sid, $to_sid, $values = []);
@@ -140,21 +140,13 @@ class WorkflowManager implements WorkflowManagerInterface {
public static function executeTransitionsOfEntity(EntityInterface $entity) {
// Avoid this hook on workflow objects.
if (in_array($entity->getEntityTypeId(), [
'workflow_type',
'workflow_state',
'workflow_config_transition',
'workflow_transition',
'workflow_scheduled_transition',
])) {
if (WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) {
return;
}
$user = workflow_current_user();
foreach (_workflow_info_fields($entity) as $field_info) {
$field_name = $field_info->getName();
foreach (workflow_get_workflow_field_names($entity) as $field_name) {
// Transition is created in widget or WorkflowTransitionForm.
/** @var $transition WorkflowTransitionInterface */
$transition = $entity->$field_name->__get('workflow_transition');
@@ -345,7 +337,7 @@ class WorkflowManager implements WorkflowManagerInterface {
/**
* {@inheritdoc}
*/
public static function getWorkflowTransitionForm(EntityInterface $entity, string $field_name) {
public static function getWorkflowTransitionForm(EntityInterface $entity, $field_name, array $form_state_additions = []) {
// 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]);
@@ -353,7 +345,7 @@ class WorkflowManager implements WorkflowManagerInterface {
// Create the WorkflowTransitionForm.
/** @var \Drupal\Core\Entity\EntityFormBuilder $entity_form_builder */
$entity_form_builder = \Drupal::getContainer()->get('entity.form_builder');
$form = $entity_form_builder->getForm($transition, 'add');
$form = $entity_form_builder->getForm($transition, 'add', $form_state_additions);
return $form;
}
@@ -455,7 +447,7 @@ class WorkflowManager implements WorkflowManagerInterface {
/**
* {@inheritdoc}
*/
public static function isWorkflowEntityType(string $entity_type_id) {
public static function isWorkflowEntityType($entity_type_id) {
return in_array($entity_type_id, [
'workflow_type',
'workflow_state',
@@ -108,7 +108,7 @@ interface WorkflowManagerInterface {
*
* @return
*/
public static function getWorkflowTransitionForm(EntityInterface $entity, string $field_name);
public static function getWorkflowTransitionForm(EntityInterface $entity, $field_name, array $form_state_additions = []);
/**
* Returns the attached fields (via Field UI)
@@ -168,6 +168,6 @@ interface WorkflowManagerInterface {
*
* @return bool
*/
public static function isWorkflowEntityType(string $entity_type_id);
public static function isWorkflowEntityType($entity_type_id);
}
@@ -47,7 +47,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
class WorkflowScheduledTransition extends WorkflowTransition {
/**
* Constructor.
* @inheritdoc.
*/
public function __construct(array $values = [], $entityType = 'workflow_scheduled_transition', $bundle = FALSE, $translations = []) {
// Please be aware that $entity_type and $entityType are different things!
@@ -70,7 +70,7 @@ class WorkflowScheduledTransition extends WorkflowTransition {
* {@inheritdoc}
*
* This is a hack to avoid the following error, because ScheduledTransition is not a bundle of Workflow:
* Drupal\Component\Plugin\Exception\PluginNotFoundException: The "entity:workflow_scheduled_transition:eerste" plugin does not exist. in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 60 of core\lib\Drupal\Component\Plugin\Discovery\DiscoveryTrait.php).
* Drupal\Component\Plugin\Exception\PluginNotFoundException: The "entity:workflow_scheduled_transition:first" plugin does not exist. in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 60 of core\lib\Drupal\Component\Plugin\Discovery\DiscoveryTrait.php).
*/
function validate() {
// Since this function generates an error in one use case (using WorkflowTransitionForm)
@@ -176,11 +176,13 @@ class WorkflowScheduledTransition extends WorkflowTransition {
*
* @param int $start
* @param int $end
* @param string $from_sid
* @param string $to_sid
*
* @return WorkflowScheduledTransition[]
* An array of transitions.
*/
public static function loadBetween($start = 0, $end = 0) {
public static function loadBetween($start = 0, $end = 0, $from_sid = '', $to_sid = '') {
$transition_type = 'workflow_scheduled_transition'; // @todo: get this from annotation.
/* @var $query \Drupal\Core\Entity\Query\QueryInterface */
@@ -193,6 +195,12 @@ class WorkflowScheduledTransition extends WorkflowTransition {
if ($end) {
$query->condition('timestamp', $end, '<');
}
if ($from_sid) {
$query->condition('from_sid', $from_sid, '=');
}
if ($to_sid) {
$query->condition('to_sid', $from_sid, '=');
}
$ids = $query->execute();
$transitions = self::loadMultiple($ids);
@@ -233,7 +241,8 @@ class WorkflowScheduledTransition extends WorkflowTransition {
$fields['timestamp'] = BaseFieldDefinition::create('created')
->setLabel(t('Scheduled'))
->setDescription(t('The date+time this transition is scheduled for.'))
->setQueryable(FALSE)
// ->setQueryable(FALSE)
// ->setCustomStorage(FALSE)
// ->setTranslatable(TRUE)
// ->setDisplayOptions('view', array(
// 'label' => 'hidden',
@@ -8,6 +8,7 @@ use Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\workflow\WorkflowTypeAttributeTrait;
/**
* Workflow configuration entity to persistently store configuration.
@@ -54,6 +55,11 @@ use Drupal\Core\Language\LanguageInterface;
*/
class WorkflowState extends ConfigEntityBase {
/*
* Add variables and get/set methods for Workflow property.
*/
use WorkflowTypeAttributeTrait;
/**
* The machine name.
*
@@ -68,13 +74,6 @@ class WorkflowState extends ConfigEntityBase {
*/
public $label;
/**
* The machine_name of the attached Workflow.
*
* @var string
*/
public $wid;
/**
* The weight of this Workflow state.
*
@@ -89,11 +88,11 @@ class WorkflowState extends ConfigEntityBase {
public $status = 1;
/**
* The attached Workflow.
* The module implementing this object, for config_export.
*
* @var Workflow
* @var string
*/
protected $workflow;
protected $module = 'workflow';
/**
* CRUD functions.
@@ -133,7 +132,7 @@ class WorkflowState extends ConfigEntityBase {
// Create the machine_name for new states.
// N.B.: Keep machine_name in WorkflowState and ~ListBuilder aligned.
$sid = $this->id();
$wid = $this->wid;
$wid = $this->getWorkflowId();
$label = $this->label();
// Set the workflow-including machine_name.
@@ -185,7 +184,7 @@ class WorkflowState extends ConfigEntityBase {
$result = [];
foreach ($states as $state) {
/** @var WorkflowState $state */
if ((!$wid) || ($wid == $state->wid)) {
if ((!$wid) || ($wid == $state->getWorkflowId())) {
$result[$state->id()] = $state;
}
}
@@ -198,8 +197,8 @@ class WorkflowState extends ConfigEntityBase {
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
/** @var WorkflowState $a */
/** @var WorkflowState $b */
$a_wid = $a->wid;
$b_wid = $b->wid;
$a_wid = $a->getWorkflowId();
$b_wid = $b->getWorkflowId();
if ($a_wid == $b_wid) {
$a_weight = $a->getWeight();
$b_weight = $b->getWeight();
@@ -263,7 +262,7 @@ class WorkflowState extends ConfigEntityBase {
}
// Delete the transitions this state is involved in.
$workflow = Workflow::load($this->wid);
$workflow = Workflow::load($this->getWorkflowId());
/** @var WorkflowInterface $workflow */
/** @var WorkflowTransitionInterface $transition */
foreach ($workflow->getTransitionsByStateId($current_sid, '') as $transition) {
@@ -295,39 +294,6 @@ class WorkflowState extends ConfigEntityBase {
return $this->weight;
}
/**
* Returns the Workflow ID of this State.
*
* @return string
* Workflow Id.
*/
public function getWorkflowId() {
return $this->wid;
}
/**
* Returns the Workflow object of this State.
*
* @return Workflow
* Workflow object.
*/
public function getWorkflow() {
if (!isset($this->workflow)) {
$this->workflow = Workflow::load($this->wid);
}
return $this->workflow;
}
/**
* @param Workflow $workflow
*/
public function setWorkflow(Workflow $workflow) {
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
$this->wid = $workflow->id();
$this->workflow = $workflow;
}
/**
* Returns the Workflow object of this State.
*
@@ -396,15 +362,16 @@ class WorkflowState extends ConfigEntityBase {
// No workflow, no options ;-)
return $transitions;
}
// Load a User object, since we cannot add Roles to AccountInterface.
if (!$user = workflow_current_user($account)) {
// In some edge cases, no user is provided.
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 \Drupal\user\UserInterface $user */
$user = workflow_current_user($account);
// Determine if user is owner of the entity.
$is_owner = WorkflowManager::isOwner($user, $entity);
@@ -423,6 +390,7 @@ class WorkflowState extends ConfigEntityBase {
* Get the object and its permissions.
*/
/** @var WorkflowConfigTransition[] $transitions */
/** @var Workflow $workflow */
$transitions = $workflow->getTransitionsByStateId($this->id(), '');
/**
@@ -518,6 +486,7 @@ class WorkflowState extends ConfigEntityBase {
// We cannot use getTransitions, since there are no ConfigTransitions
// from State with ID 0, and we do not want to repeat States.
/** @var WorkflowState $state */
/** @var Workflow $workflow */
foreach ($workflow->getStates() as $state) {
$options[$state->id()] = html_entity_decode(t('@label', ['@label' => $state->label()]));
}
@@ -8,6 +8,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\Language;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\workflow\WorkflowTypeAttributeTrait;
/**
* Implements an actual, executed, Transition.
@@ -57,6 +58,11 @@ use Drupal\user\UserInterface;
*/
class WorkflowTransition extends ContentEntityBase implements WorkflowTransitionInterface {
/*
* Add variables and get/set methods for Workflow property.
*/
use WorkflowTypeAttributeTrait;
/*
* Transition data: are provided via baseFieldDefinitions().
*/
@@ -64,7 +70,6 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
/*
* Cache data.
*/
protected $workflow; // Use WorkflowTransition->getWorkflow() to fetch this.
protected $entity = NULL; // Use WorkflowTransition->getTargetEntity() to fetch this.
protected $user = NULL; // Use WorkflowTransition->getOwner() to fetch this.
@@ -116,17 +121,15 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
public static function create(array $values = []) {
if (is_array($values) && isset($values[0])) {
$value = $values[0];
$values['wid'] = '';
$values['from_sid'] = '';
if (is_string($value) && $state = WorkflowState::load($value)) {
$values['wid'] = $state->getWorkflowId();
$values['from_sid'] = $state->id();
$state = NULL;
if (is_string($value)) {
$state = WorkflowState::load($value);
}
elseif (is_object($value) && $value instanceof WorkflowState) {
$state = $value;
$values['wid'] = $state->getWorkflowId();
$values['from_sid'] = $state->id();
}
$values['wid'] = $state ? $state->getWorkflowId() : '';
$values['from_sid'] = $state ? $state->id() : '';
}
// Add default values.
@@ -185,6 +188,10 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
public function save() {
// return parent::save();
// Set Target Entity to be used by Rules.
$reference = $this->get('entity_id')->first();
$reference->set('entity', $this->getTargetEntity());
// Avoid custom actions for subclass WorkflowScheduledTransition.
if ($this->isScheduled()) {
return parent::save();
@@ -257,7 +264,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
/** @var $query \Drupal\Core\Entity\Query\QueryInterface */
$query = \Drupal::entityQuery($transition_type)
->condition('entity_type', $entity_type)
->sort('timestamp', $sort) // 'DESC' || 'ASC'
->sort('timestamp', $sort)// 'DESC' || 'ASC'
->addTag($transition_type);
if (!empty($entity_ids)) {
$query->condition('entity_id', $entity_ids, 'IN');
@@ -387,6 +394,17 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
return $result;
}
/**
* Determines if the State changes by this Transition.
* @return bool
*/
public function hasStateChange() {
if ($this->from_sid->target_id == $this->to_sid->target_id) {
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
@@ -409,7 +427,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
static $static_info = NULL;
if (isset($static_info[$entity_id][$field_name][$label])) {
if (isset($static_info[$entity_id][$field_name][$label]) && !$this->isEmpty()) {
// Error: this Transition is already executed.
// On the development machine, execute() is called twice, when
// on an Edit Page, the entity has a scheduled transition, and
@@ -451,8 +469,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
// @todo: move below code to $this->isAllowed().
// If the state has changed, check the permissions.
// No need to check if Comments or attached fields are filled.
$state_changed = ($from_sid != $to_sid);
if ($state_changed) {
if ($this->hasStateChange()) {
// Make sure this transition is allowed by workflow module Admin UI.
if (!$force) {
$user->addRole(WORKFLOW_ROLE_AUTHOR_RID);
@@ -503,7 +520,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
$this->save();
// Register state change with watchdog.
if ($state_changed && !empty($this->getWorkflow()->options['watchdog_log'])) {
if ($this->hasStateChange() && !empty($this->getWorkflow()->options['watchdog_log'])) {
if ($this->getEntityTypeId() == 'workflow_scheduled_transition') {
$message = 'Scheduled state change of @entity_type_label %entity_label to %sid2 executed';
}
@@ -597,31 +614,6 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
}
}
/**
* {@inheritdoc}
*/
public function getWorkflow() {
if (!$this->workflow && $wid = $this->getWorkflowId()) {
$this->workflow = Workflow::load($wid);
}
return $this->workflow;
}
/**
* {@inheritdoc}
*/
public function getWorkflowId() {
if (!$this->wid->target_id && $state = $this->getFromState()) {
// Fallback.
$state = ($state) ? $state : $this->getToState();
$wid = ($state) ? $state->getWorkflowId() : '';
$this->set('wid', $wid);
}
return $this->wid->target_id;
}
/**
* {@inheritdoc}
*/
@@ -1074,12 +1066,14 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
$user = $transition->getOwner();
$user_name = ($user) ? $user->getAccountName() : 'unknown username';
$t_string = $this->getEntityTypeId() . ' ' . $this->id() . ' for workflow_type <i>' . $this->getWorkflowId() . '</i> ' . ($function ? ("in function '$function'") : '');
$output[] = 'Entity = ' . $this->getTargetEntityTypeId() . '/' . (($entity) ? ($entity->bundle() . '/' . $entity->id()) : '___/0') ;
$output[] = 'Entity = ' . $this->getTargetEntityTypeId() . '/' . (($entity) ? ($entity->bundle() . '/' . $entity->id()) : '___/0');
$output[] = 'Field = ' . $transition->getFieldName();
$output[] = 'From/To = ' . $transition->getFromSid() . ' > ' . $transition->getToSid() . ' @ ' . $time;
$output[] = 'Comment = ' . $user_name . ' says: ' . $transition->getComment();
$output[] = 'Forced = ' . ($transition->isForced() ? 'yes' : 'no') .'; ' . 'Scheduled = ' . ($transition->isScheduled() ? 'yes' : 'no');
if (function_exists('dpm')) { dpm($output, $t_string); }
$output[] = 'Forced = ' . ($transition->isForced() ? 'yes' : 'no') . '; ' . 'Scheduled = ' . ($transition->isScheduled() ? 'yes' : 'no');
if (function_exists('dpm')) { // in Workflow->dpm().
dpm($output, $t_string); // in Workflow->dpm().
} // in Workflow->dpm().
}
}
@@ -45,13 +45,11 @@ class WorkflowTransitionForm extends ContentEntityForm {
// $entity_type = $transition->getTargetEntityTypeId();
// $entity_id = $transition->getTargetEntityId();;
$suffix = 'form';
// Emulate nodeForm convention.
if ($transition->id()) {
$suffix = 'edit_form';
}
else {
$suffix = 'form';
}
$form_id = implode('_', ['workflow_transition', $field_name, $suffix]);
$form_id = Html::getUniqueId($form_id);
@@ -73,7 +73,7 @@ class WorkflowNodeGivenStateAction extends WorkflowStateActionBase {
}
// Fire the transition.
workflow_execute_transition($transition, $force);
workflow_execute_transition($transition, $force = FALSE);
}
}
@@ -109,7 +109,6 @@ abstract class WorkflowStateActionBase extends ConfigurableActionBase implements
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = [];
// If we are on admin/config/system/actions and use CREATE AN ADVANCED ACTION
// Then $context only contains:
@@ -126,18 +125,18 @@ abstract class WorkflowStateActionBase extends ConfigurableActionBase implements
$wids = workflow_get_workflow_names();
if (empty($field_name) && count($wids) > 1) {
drupal_set_message('You have more then one workflow in the system. Please first select the field name
drupal_set_message('You have multiple workflows in the system. Please first select the field name
and save the form. Then, revisit the form to set the correct state value.', 'warning');
}
if (empty($field_name)) {
$wid = count($wids) ? array_keys($wids)[0] : '';
}
else {
$wid = count($wids) ? array_keys($wids)[0] : '';
if (!empty($field_name)) {
$fields = _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name);
$wid = count($fields) ? reset($fields)->getSetting('workflow_type') : '';
}
// Get the common Workflow, or create a dummy Workflow.
/** @var Workflow $workflow */
$workflow = $wid ? Workflow::load($wid) : Workflow::create(['id' => 'dummy_action', 'label' => 'dummy_action']);
$current_state = $workflow->getCreationState();
@@ -183,7 +182,7 @@ abstract class WorkflowStateActionBase extends ConfigurableActionBase implements
\Drupal::time()->getRequestTime(),
$comment = $config['comment'],
$force = $config['force']
);
);
// Add the WorkflowTransitionForm to the page. @todo
@@ -0,0 +1,75 @@
<?php
namespace Drupal\workflow\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local task definitions for all entity bundles.
*
* @see \Drupal\workflow\Controller\EntityDebugController
* @see \Drupal\workflow\Routing\RouteSubscriber
*/
class WorkflowLocalTask extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Creates an WorkflowLocalTask object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
$field_list = workflow_get_workflow_fields_by_entity_type();
foreach ($field_list as $entity_type_id => $fields) {
$this->derivatives["entity.$entity_type_id.workflow_history"] = [
'route_name' => "entity.$entity_type_id.workflow_history",
'title' => $this->t('Workflow'),
'base_route' => "entity.$entity_type_id.canonical",
'weight' => 100,
];
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}
@@ -156,7 +156,6 @@ class WorkflowItem extends ListItemBase {
return [
'workflow_type' => '',
'allowed_values_function' => 'workflow_state_allowed_values',
] + parent::defaultStorageSettings();
}
@@ -78,7 +78,7 @@ class WorkflowDefaultWidget extends WidgetBase {
$field_storage = $field_config->getFieldStorageDefinition();
$entity = $item->getEntity();
$field_name = $field_storage->get('field_name');
$field_name = $field_storage->getName();
// Create a transition, to pass to the form. No need to use setValues().
$from_sid = workflow_node_current_state($entity, $field_name);
@@ -3,10 +3,8 @@
namespace Drupal\workflow\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\user\UserStorageInterface;
use Drupal\workflow\Entity\WorkflowTransitionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
@@ -54,6 +52,7 @@ class WorkflowFieldConstraintValidator extends ConstraintValidator implements Co
*/
public function validate($entity, Constraint $constraint) {
// Workflow field name on CommentForm has special requirements.
/** @var FieldStorageConfig $field_storage */
$field_storage = $entity->getFieldDefinition()->getFieldStorageDefinition();
if ($field_storage->getTargetEntityTypeId() == 'comment') {
if (!$this->isValidFieldname($field_storage)) {
@@ -76,7 +75,7 @@ class WorkflowFieldConstraintValidator extends ConstraintValidator implements Co
return TRUE;
}
$field_name = $field_storage->get('field_name');
$field_name = $field_storage->getName();
// Check if the 'comment' field name exists on the 'commented' entity type.
// @todo: Still not waterproof. You could have a field on a non-relevant entity_type.
@@ -0,0 +1,136 @@
<?php
namespace Drupal\workflow\Routing;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for Workflow routes.
*
* @see \Drupal\workflow\Plugin\Derivative\WorkflowLocalTask
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new RouteSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_manager) {
$this->entityTypeManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$field_list = workflow_get_workflow_fields_by_entity_type();
foreach ($field_list as $entityTypeId => $fields) {
/*
* @todo Create a Entity workflow field list page and a route
* that redirect to the page when a entity have more than
* one workflow fields.
*/
// Only 1 field. Workflow is redirect to workflow/{field_name}.
if (count($fields) < 2) {
$path = "/$entityTypeId/{{$entityTypeId}}/workflow";
$route = $this->getEntityLoadRoute($entityTypeId, $path);
$collection->add("entity.$entityTypeId.workflow_history", $route);
}
// Generate one route for each workflow field.
foreach ($fields as $field_name => $field) {
$path = "/$entityTypeId/{{$entityTypeId}}/workflow/$field_name";
$route = $this->getEntityLoadRoute($entityTypeId, $path);
$collection->add("entity.$entityTypeId.workflow_history.$field_name", $route);
}
}
}
/**
* Gets the entity load route.
*
* @param string $entityTypeId
* The entity type id.
* @param string $path
* The Path of the route.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available.
*/
protected function getEntityLoadRoute($entityTypeId, $path) {
/*
* @todo Create the Route for taxonomy term like
* '/taxonomy/term/{taxonomy_term}/workflow/{field_name}'
*
*/
$route = new Route(
$path,
[
'_controller' => '\Drupal\workflow\Controller\WorkflowTransitionListController::historyOverview',
'_title' => 'Workflow history',
],
[
'_custom_access' => '\Drupal\workflow\Access\WorkflowHistoryAccess::access',
],
[
'_admin_route' => TRUE,
'_workflow_entity_type_id' => $entityTypeId, // @todo: remove
'parameters' => [
$entityTypeId => ['type' => 'entity:' . $entityTypeId],
],
]
);
return $route;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::ALTER] = ['onAlterRoutes', 100];
return $events;
}
/**
* Get all field of type workflow.
*
* @return array
* Return all workflow fields.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function listWorkflowFields() {
$fieldType = 'workflow';
$fieldStorageConfigs = $this->entityTypeManager->getStorage('field_storage_config')->loadByProperties(['type' => $fieldType]);
if (!$fieldStorageConfigs) {
return [];
}
$availableItems = [];
foreach ($fieldStorageConfigs as $fieldStorage) {
$availableItems[] = $fieldStorage;
}
return $availableItems;
}
}
@@ -3,8 +3,8 @@
namespace Drupal\workflow;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\workflow\Entity\WorkflowTransition;
/**
@@ -55,6 +55,7 @@ class WorkflowTransitionListBuilder extends EntityListBuilder {
$field_name = workflow_url_get_field_name();
// @todo D8-port: document $limit. Should be used in pager, not in load().
// N.B. Using the provided default History view is recommended.
$this->limit = \Drupal::config('workflow.settings')->get('workflow_states_per_page');
$limit = $this->limit;
// Get Transitions with highest timestamp first.
@@ -202,7 +203,7 @@ class WorkflowTransitionListBuilder extends EntityListBuilder {
'class' => ['footer-class'],
'data' => [
[
'data' => WORKFLOW_MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.'),
'data' => self::WORKFLOW_MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.'),
'colspan' => count($build['table']['#header']),
],
],
@@ -210,7 +211,7 @@ class WorkflowTransitionListBuilder extends EntityListBuilder {
];
*/
$build['workflow_footer'] = [
'#markup' => WORKFLOW_MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.'),
'#markup' => self::WORKFLOW_MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.'),
'#weight' => 500, // @todo Make this better.
];
}
@@ -0,0 +1,103 @@
<?php
namespace Drupal\workflow;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\workflow\Entity\Workflow;
/**
* Wrapper methods for Workflow* objects
*
* Using this trait will add getWorkflow(), getWorkflowID() and setWorkflow()
* methods to the class.
*
* @ingroup workflow
*/
trait WorkflowTypeAttributeTrait {
/**
* The machine_name of the attached Workflow.
*
* @var string
*/
protected $wid = '';
/**
* The attached Workflow.
* It must explicitly be defined, and not be public, to avoid errors
* when exporting with json_encode().
*
* @var Workflow
*/
protected $workflow = NULL;
/**
* @param Workflow $workflow
*/
public function setWorkflow(Workflow $workflow) {
$this->wid = '';
$this->workflow = NULL;
if ($workflow) {
$this->wid = $workflow->id();
$this->workflow = $workflow;
}
}
/**
* Returns the Workflow object of this object.
*
* @return Workflow
* Workflow object.
*/
public function getWorkflow() {
if (!empty($this->workflow)) {
return $this->workflow;
}
/** @noinspection PhpAssignmentInConditionInspection */
if ($wid = $this->getWorkflowId()) {
$this->workflow = Workflow::load($wid);
}
return $this->workflow;
}
/**
* Sets the Workflow ID of this object.
*
* @return object
*/
public function setWorkflowId($wid) {
$this->wid = $wid;
$this->workflow = NULL;
return $this;
}
/**
* Returns the Workflow ID of this object.
*
* @return string
* Workflow Id.
*/
public function getWorkflowId() {
/** @var ContentEntityBase $this */
if (!empty($this->wid)) {
return $this->wid;
}
$value = $this->get('wid');
if (is_string($value)) {
$this->wid = $value;
}
elseif (is_object($value)) {
$wid = isset($value->getValue()[0]['target_id']) ? $value->getValue()[0]['target_id'] : '';
// or: $this->set('wid', $wid);
$this->wid = $wid; // in WorkflowTransition.
}
else {
workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
}
return $this->wid;
}
}
@@ -95,6 +95,7 @@ function workflow_state_formatter($entity, $field_name, $current_sid = '') {
// If user creates a node, and only 1 option is available, the formatter
// is shown with key, not value, because creation state does not count.
// In this case, hide the formatter.
/** @var \Drupal\workflow\Entity\WorkflowState $state */
$state = \Drupal\workflow\Entity\WorkflowState::load($current_sid);
if ($state->isCreationState()) {
return $element;
@@ -117,3 +118,18 @@ function workflow_state_formatter($entity, $field_name, $current_sid = '') {
return $element;
}
/**
* This function is a residue for installations that have v8.x-1.0
* The setting for options_allowed_values is still in the Field confuguration
* Saving the field data again does not remove the settings.
* So, we copy code from options_allowed_values().
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
*
* @return mixed
*/
function workflow_state_allowed_values(\Drupal\Core\Field\FieldStorageDefinitionInterface $definition) {
$values = $definition->getSetting('allowed_values');
return $values;
}
@@ -190,7 +190,7 @@ function _workflow_transition_form_get_action_buttons(array $form, array $workfl
if (isset($form['#form_id']) && substr($form['#form_id'], 0, 24) == 'workflow_transition_form') {
// Hide same-state-button on the transition-form (that is:
// view page or workflow history tab) if there is nothing to do.
// However, a Transition may be fieldable.
// However, a Transition may be fieldable (have attached fields).
if ($form['comment']['#access'] == FALSE) {
$workflow_submit_action['#access'] = FALSE;
}
@@ -10,8 +10,8 @@ dependencies:
- options
- user
# Information added by Drupal.org packaging script on 2017-12-17
version: '8.x-1.0'
# Information added by Drupal.org packaging script on 2018-04-29
version: '8.x-1.1'
core: '8.x'
project: 'workflow'
datestamp: 1513552400
datestamp: 1525039388
@@ -18,13 +18,6 @@ function workflow_install() {
drupal_set_message($message);
}
/**
* Implements hook_uninstall().
*/
function workflow_uninstall() {
// \Drupal::config('workflow.settings')->clear('workflow_states_per_page')->save(); // @FIXME
}
/**
* Implements hook_requirements().
*
@@ -18,15 +18,6 @@ entity.workflow_type.delete_form:
title: 'Delete'
weight: 10
# @todo D8-port: make Workflow History tab happen for every entity_type.
# @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController.
entity.node.workflow_history:
route_name: entity.node.workflow_history
base_route: entity.node.canonical
title: 'Workflow'
weight: 30
entity.taxonomy_term.edit_form:
route_name: entity.taxonomy_term.workflow_history
base_route: entity.taxonomy_term.canonical
title: 'Workflow'
weight: 30
workflow.entities:
deriver: 'Drupal\workflow\Plugin\Derivative\WorkflowLocalTask'
weight: 100
@@ -176,9 +176,9 @@ function workflow_entity_delete(\Drupal\Core\Entity\EntityInterface $entity) {
elseif (!WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) {
// A 'normal' entity is deleted.
foreach ($fields = _workflow_info_fields($entity) as $field_id => $field_storage) {
$entity_type = $field_storage->getTargetEntityTypeId();
$entity_id = $entity->id();
$field_name = $field_storage->get('field_name');
$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();
@@ -347,8 +347,8 @@ function workflow_get_workflow_names($required = TRUE) {
function workflow_get_workflow_field_names($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') {
$result = [];
foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name) as $definition) {
$field_name = $definition->getName();
$result[$field_name] = $definition->getName();
$field_name2 = $definition->getName();
$result[$field_name2] = $field_name2;
}
return $result;
}
@@ -397,10 +397,8 @@ function workflow_get_field_name(\Drupal\Core\Entity\EntityInterface $entity, $f
if ($field_name) {
return $field_name;
}
$fields = _workflow_info_fields($entity);
$field = reset($fields);
$field_name = $field->getName();
$fields = workflow_get_workflow_field_names($entity);
$field_name = reset($fields);
return $field_name;
}
@@ -428,8 +426,6 @@ function workflow_current_user(\Drupal\Core\Session\AccountInterface $account =
* @param string $field_name
*
* @return string $current_sid
*
* @deprecated : use WorkflowManager::getCurrentStateId()
*/
function workflow_node_current_state(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') {
return WorkflowManager::getCurrentStateId($entity, $field_name);
@@ -442,8 +438,6 @@ function workflow_node_current_state(\Drupal\Core\Entity\EntityInterface $entity
* @param string $field_name
*
* @return string $previous_sid
*
* @deprecated : use WorkflowManager::getPreviousStateId()
*/
function workflow_node_previous_state(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') {
return WorkflowManager::getPreviousStateId($entity, $field_name);
@@ -486,6 +480,10 @@ function workflow_get_workflows_by_type($entity_bundle, $entity_type) {
return $map[$entity_type][$entity_bundle];
}
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.
*
@@ -522,7 +520,24 @@ function _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle
// Do not use the field_name as ID, but the unique <entity_type>.<field_name>
// since you cannot share the same field on multiple entity_types (unlike D7).
$field_config = \Drupal\field\Entity\FieldStorageConfig::loadByName($e_type, $f_name);
$field_info[$field_config->id()] = $field_config;
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.
// Refetch 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?
}
}
}
}
}
@@ -545,36 +560,27 @@ function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = N
return $entity;
}
$route_match = \Drupal::routeMatch();
// @todo: get entity for any route.
// On node pages, we'd get an object.
$entity = $route_match->getParameter('node');
if ($entity && is_object($entity)) {
return $entity;
$entities = [];
foreach (\Drupal::routeMatch()->getParameters() as $param) {
if ($param instanceof \Drupal\Core\Entity\EntityInterface) {
$entities[] = $param;
}
}
if ($entity && !is_object($entity)) {
$value = reset($entities);
if ($value && is_object($value)) {
return $value;
}
if ($value && !is_object($value)) {
// On workflow tab, we'd get an id.
$entity = \Drupal\node\Entity\Node::load($entity);
// This is an indicator that the route is mal-configured.
workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'route declaration is not optimal.');
$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($value);
return $entity;
}
// It was not a Node, try a Term.
// On term pages, we get objects, or id's.
$entity = $route_match->getParameter('taxonomy_term');
if ($entity && is_object($entity)) {
return $entity;
}
elseif ($entity && !is_object($entity)) {
$entity = \Drupal\taxonomy\Entity\Term::load($entity);
return $entity;
}
if (!$entity) {
// We may be on a entity add page/
// Or we may be on a page of some unknown entity.
}
return $entity;
return $value;
}
/**
@@ -585,10 +591,7 @@ function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = N
* @return string|null $field_name
*/
function workflow_url_get_field_name() {
/** @var $route_match \Drupal\Core\Routing\RouteMatchInterface */
$route_match = \Drupal::routeMatch();
$field_name = $route_match->getParameter('field_name');
return $field_name;
return workflow_url_get_parameter('field_name');
}
/**
@@ -609,32 +612,37 @@ function workflow_url_get_operation() {
}
/**
* Helper function to determine Workflow from Workflow UI URL.
* Helper function to get arbitrary parameter from a route.
*
* @param string $url
* @param $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 Workflow
*/
function workflow_url_get_workflow($url = '' ) {
function workflow_url_get_workflow() {
/** @var $workflows \Drupal\workflow\Entity\Workflow[] */
static $workflows = [];
// For some reason, $_SERVER is not allowed as default.
$url = ($url == '') ? $_SERVER['REQUEST_URI'] : $url;
// The URL may have prefixes: /
// ex.1: /en/admin/config/workflow/workflow/MY_WORKFLOW/states
// ex.2: /admin/config/workflow/workflow/MY_WORKFLOW/states
$base_url = '/config/workflow/workflow/';
$string = substr($url, strpos($url, $base_url) + strlen($base_url));
$wid = explode('/', $string)[0];
if (!isset($workflows[$wid])) {
$workflows[$wid] = Workflow::load($wid);
$wid = workflow_url_get_parameter('workflow_type');
if (is_object($wid)) { // $wid is a Workflow object.
return $wid;
}
// $wid is a string
if (!isset($workflows[$wid])) {
$workflows[$wid] = $wid ? Workflow::load($wid) : NULL;
}
return $workflows[$wid];
}
/**
* Helper function to determine the title of the page.
*
@@ -109,36 +109,3 @@ entity.workflow_scheduled_transition.delete_form:
_admin_route: TRUE
requirements:
_entity_access: 'workflow_transition.delete'
### The Workflow Transition History List (Tab)
# @todo D8-port: make Workflow History tab happen for every entity_type.
# @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController.
# A route for showing the Workflow history tab.
entity.node.workflow_history:
path: '/node/{node}/workflow/{field_name}'
defaults:
_title: 'Workflow history'
_controller: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyOverview'
field_name: ~
requirements:
_custom_access: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyAccess'
_module_dependencies: 'node'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type}
entity.taxonomy_term.workflow_history:
path: '/taxonomy/term/{taxonomy_term}/workflow/{field_name}'
defaults:
_title: 'Workflow history'
_controller: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyOverview'
field_name: ~
requirements:
_custom_access: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyAccess'
_module_dependencies: 'taxonomy'
options:
parameters:
entity:
type: entity:{entity_type}
@@ -3,8 +3,8 @@ services:
class: Drupal\workflow\Entity\WorkflowManager
arguments: ['@entity_type.manager', '@entity.query', '@config.factory', '@string_translation', '@module_handler', '@current_user']
# workflow.route_subscriber:
# class: Drupal\workflow\Routing\RouteSubscriber
# arguments: ['@entity.manager']
# tags:
# - { name: event_subscriber }
workflow.route_subscriber:
class: Drupal\workflow\Routing\RouteSubscriber
arguments: ['@entity.manager']
tags:
- { name: event_subscriber }