Issue #2862041 by Sam152, amateescu, timmillwood, jibran, hamrant, dawehner, plach, Manuel Garcia, Jo Fitzgerald, mgalalm, xjm, larowlan, webchick, Wim Leers, gambry, kkus, tim.plunkett, tedbow, CatherineOmega: Provide useful Views filters for Content Moderation State fields
parent
45662bb9e2
commit
418e4991b5
|
@ -0,0 +1,18 @@
|
|||
views.filter.latest_revision:
|
||||
type: views_filter
|
||||
label: 'Latest revision'
|
||||
mapping:
|
||||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
|
||||
views.filter.moderation_state_filter:
|
||||
type: views.filter.in_operator
|
||||
label: 'Moderation state filter'
|
||||
mapping:
|
||||
limit_workflows:
|
||||
type: sequence
|
||||
label: 'Workflow'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Workflow'
|
|
@ -0,0 +1,291 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\views\filter;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\views\Plugin\DependentWithRemovalPluginInterface;
|
||||
use Drupal\views\Plugin\views\filter\InOperator;
|
||||
use Drupal\views\Views;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a filter for the moderation state of an entity.
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*
|
||||
* @ViewsFilter("moderation_state_filter")
|
||||
*/
|
||||
class ModerationStateFilter extends InOperator implements DependentWithRemovalPluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $valueFormType = 'select';
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The bundle information service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* The storage handler of the workflow entity type.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $workflowStorage;
|
||||
|
||||
/**
|
||||
* Creates an instance of ModerationStateFilter.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, EntityStorageInterface $workflow_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
$this->workflowStorage = $workflow_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('entity_type.manager')->getStorage('workflow')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return Cache::mergeTags(parent::getCacheTags(), $this->entityTypeManager->getDefinition('workflow')->getListCacheTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return Cache::mergeContexts(parent::getCacheContexts(), $this->entityTypeManager->getDefinition('workflow')->getListCacheContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValueOptions() {
|
||||
if (isset($this->valueOptions)) {
|
||||
return $this->valueOptions;
|
||||
}
|
||||
$this->valueOptions = [];
|
||||
|
||||
// Find all workflows which are moderating entity types of the same type the
|
||||
// view is displaying.
|
||||
foreach ($this->workflowStorage->loadByProperties(['type' => 'content_moderation']) as $workflow) {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $workflow_type */
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
if (in_array($this->getEntityType(), $workflow_type->getEntityTypes(), TRUE)) {
|
||||
foreach ($workflow_type->getStates() as $state_id => $state) {
|
||||
$this->valueOptions[$workflow->label()][implode('-', [$workflow->id(), $state_id])] = $state->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->valueOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ensureMyTable() {
|
||||
if (!isset($this->tableAlias)) {
|
||||
$table_alias = $this->query->ensureTable($this->table, $this->relationship);
|
||||
|
||||
// Filter the moderation states of the content via the
|
||||
// ContentModerationState field revision table, joining either the entity
|
||||
// field data or revision table. This allows filtering states against
|
||||
// either the default or latest revision, depending on the relationship of
|
||||
// the filter.
|
||||
$left_entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
|
||||
$entity_type = $this->entityTypeManager->getDefinition('content_moderation_state');
|
||||
$configuration = [
|
||||
'table' => $entity_type->getRevisionDataTable(),
|
||||
'field' => 'content_entity_revision_id',
|
||||
'left_table' => $table_alias,
|
||||
'left_field' => $left_entity_type->getKey('revision'),
|
||||
'extra' => [
|
||||
[
|
||||
'field' => 'content_entity_type_id',
|
||||
'value' => $left_entity_type->id(),
|
||||
],
|
||||
],
|
||||
];
|
||||
if ($left_entity_type->isTranslatable()) {
|
||||
$configuration['extra'][] = [
|
||||
'field' => $entity_type->getKey('langcode'),
|
||||
'left_field' => $left_entity_type->getKey('langcode'),
|
||||
];
|
||||
}
|
||||
$join = Views::pluginManager('join')->createInstance('standard', $configuration);
|
||||
$this->tableAlias = $this->query->addRelationship('content_moderation_state', $join, 'content_moderation_state_field_revision');
|
||||
}
|
||||
|
||||
return $this->tableAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function opSimple() {
|
||||
if (empty($this->value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ensureMyTable();
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
|
||||
if ($entity_type->hasKey('bundle')) {
|
||||
// Get a list of bundles that are being moderated by the workflows
|
||||
// configured in this filter.
|
||||
$workflow_ids = $this->getWorkflowIds();
|
||||
$moderated_bundles = [];
|
||||
foreach ($this->bundleInfo->getBundleInfo($this->getEntityType()) as $bundle_id => $bundle) {
|
||||
if (isset($bundle['workflow']) && in_array($bundle['workflow'], $workflow_ids, TRUE)) {
|
||||
$moderated_bundles[] = $bundle_id;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a list of moderated bundles, restrict the query to show only
|
||||
// entities in those bundles.
|
||||
if ($moderated_bundles) {
|
||||
$entity_base_table_alias = $this->table;
|
||||
|
||||
// The bundle field of an entity type is not revisionable so we need to
|
||||
// join the data table.
|
||||
$entity_base_table = $entity_type->isTranslatable() ? $entity_type->getDataTable() : $entity_type->getBaseTable();
|
||||
$entity_revision_base_table = $entity_type->isTranslatable() ? $entity_type->getRevisionDataTable() : $entity_type->getRevisionTable();
|
||||
if ($this->table === $entity_revision_base_table) {
|
||||
$configuration = [
|
||||
'table' => $entity_base_table,
|
||||
'field' => $entity_type->getKey('id'),
|
||||
'left_table' => $entity_revision_base_table,
|
||||
'left_field' => $entity_type->getKey('id'),
|
||||
'type' => 'INNER',
|
||||
];
|
||||
if ($entity_type->isTranslatable()) {
|
||||
$configuration['extra'][] = [
|
||||
'field' => $entity_type->getKey('langcode'),
|
||||
'left_field' => $entity_type->getKey('langcode'),
|
||||
];
|
||||
}
|
||||
|
||||
$join = Views::pluginManager('join')->createInstance('standard', $configuration);
|
||||
$entity_base_table_alias = $this->query->addRelationship($entity_base_table, $join, $entity_revision_base_table);
|
||||
}
|
||||
|
||||
$this->query->addWhere($this->options['group'], "$entity_base_table_alias.{$entity_type->getKey('bundle')}", $moderated_bundles, 'IN');
|
||||
}
|
||||
// Otherwise, force the query to return an empty result.
|
||||
else {
|
||||
$this->query->addWhereExpression($this->options['group'], '1 = 0');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->operator === 'in') {
|
||||
$operator = "=";
|
||||
}
|
||||
else {
|
||||
$operator = "<>";
|
||||
}
|
||||
|
||||
// The values are strings composed from the workflow ID and the state ID, so
|
||||
// we need to create a complex WHERE condition.
|
||||
$field = new Condition('OR');
|
||||
foreach ((array) $this->value as $value) {
|
||||
list($workflow_id, $state_id) = explode('-', $value, 2);
|
||||
|
||||
$and = new Condition('AND');
|
||||
$and
|
||||
->condition("$this->tableAlias.workflow", $workflow_id, '=')
|
||||
->condition("$this->tableAlias.$this->realField", $state_id, $operator);
|
||||
|
||||
$field->condition($and);
|
||||
}
|
||||
|
||||
$this->query->addWhere($this->options['group'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
if ($workflow_ids = $this->getWorkflowIds()) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
foreach ($this->workflowStorage->loadMultiple($workflow_ids) as $workflow) {
|
||||
$dependencies[$workflow->getConfigDependencyKey()][] = $workflow->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
// See if this handler is responsible for any of the dependencies being
|
||||
// removed. If this is the case, indicate that this handler needs to be
|
||||
// removed from the View.
|
||||
$remove = FALSE;
|
||||
// Get all the current dependencies for this handler.
|
||||
$current_dependencies = $this->calculateDependencies();
|
||||
foreach ($current_dependencies as $group => $dependency_list) {
|
||||
// Check if any of the handler dependencies match the dependencies being
|
||||
// removed.
|
||||
foreach ($dependency_list as $config_key) {
|
||||
if (isset($dependencies[$group]) && array_key_exists($config_key, $dependencies[$group])) {
|
||||
// This handlers dependency matches a dependency being removed,
|
||||
// indicate that this handler needs to be removed.
|
||||
$remove = TRUE;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $remove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of Workflow IDs configured for this filter.
|
||||
*
|
||||
* @return array
|
||||
* And array of workflow IDs.
|
||||
*/
|
||||
protected function getWorkflowIds() {
|
||||
$workflow_ids = [];
|
||||
foreach ((array) $this->value as $value) {
|
||||
list($workflow_id) = explode('-', $value, 2);
|
||||
$workflow_ids[] = $workflow_id;
|
||||
}
|
||||
|
||||
return array_unique($workflow_ids);
|
||||
}
|
||||
|
||||
}
|
|
@ -83,6 +83,7 @@ class ViewsData {
|
|||
'default_formatter' => 'content_moderation_state',
|
||||
'field_name' => 'moderation_state',
|
||||
],
|
||||
'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
|
||||
];
|
||||
|
||||
$revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
|
||||
|
@ -106,6 +107,7 @@ class ViewsData {
|
|||
'default_formatter' => 'content_moderation_state',
|
||||
'field_name' => 'moderation_state',
|
||||
],
|
||||
'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- node
|
||||
- user
|
||||
id: test_content_moderation_state_filter_base_table
|
||||
label: test_content_moderation_state_filter_base_table
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_data
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Default Revision State'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: default_revision_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
moderation_state_1:
|
||||
id: moderation_state_1
|
||||
table: node_field_data
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: 'not empty'
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: filter-test-path
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- node
|
||||
- user
|
||||
id: test_content_moderation_state_filter_base_table_filter_on_revision
|
||||
label: test_content_moderation_state_filter_base_table_filter_on_revision
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: filter-on-revision-test-path
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- entity_test
|
||||
id: test_content_moderation_state_filter_entity_test
|
||||
label: test_content_moderation_state_filter_entity_test
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: entity_test_no_bundle
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
entity_id:
|
||||
id: entity_id
|
||||
table: content_revision_tracker
|
||||
field: entity_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: entity_test_no_bundle
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: moderation_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: entity_test_no_bundle
|
||||
plugin_id: moderation_state_filter
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
tags: { }
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- user
|
||||
id: test_content_moderation_state_filter_revision_table
|
||||
label: test_content_moderation_state_filter_revision_table
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_revision
|
||||
base_field: vid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'view all revisions'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_revision
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: moderation_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts:
|
||||
vid:
|
||||
id: vid
|
||||
table: node_field_revision
|
||||
field: vid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: node
|
||||
entity_field: vid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
|
|
@ -8,3 +8,4 @@ dependencies:
|
|||
- content_moderation
|
||||
- node
|
||||
- views
|
||||
- entity_test
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Functional\ViewTestBase;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the views 'moderation_state_filter' filter plugin.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\views\filter\ModerationStateFilter
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ViewsModerationStateFilterTest extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation_test_views',
|
||||
'node',
|
||||
'content_moderation',
|
||||
'workflows',
|
||||
'workflow_type_test',
|
||||
'entity_test',
|
||||
'language',
|
||||
'content_translation',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp(FALSE);
|
||||
|
||||
NodeType::create([
|
||||
'type' => 'example_a',
|
||||
])->save();
|
||||
NodeType::create([
|
||||
'type' => 'example_b',
|
||||
])->save();
|
||||
|
||||
$new_workflow = Workflow::create([
|
||||
'type' => 'content_moderation',
|
||||
'id' => 'new_workflow',
|
||||
'label' => 'New workflow',
|
||||
]);
|
||||
$new_workflow->getTypePlugin()->addState('bar', 'Bar');
|
||||
$new_workflow->save();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer workflows', 'administer views']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the dependency handling of the moderation state filter.
|
||||
*
|
||||
* @covers ::calculateDependencies
|
||||
* @covers ::onDependencyRemoval
|
||||
*/
|
||||
public function testModerationStateFilterDependencyHandling() {
|
||||
// First, check that the view doesn't have any config dependency when there
|
||||
// are no states configured in the filter.
|
||||
$view_id = 'test_content_moderation_state_filter_base_table';
|
||||
$view = Views::getView($view_id);
|
||||
|
||||
$this->assertWorkflowDependencies([], $view);
|
||||
$this->assertTrue($view->storage->status());
|
||||
|
||||
// Configure the Editorial workflow for a node bundle, set the filter value
|
||||
// to use one of its states and check that the workflow is now a dependency
|
||||
// of the view.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
], 'Save');
|
||||
|
||||
$edit['options[value][]'] = ['editorial-published'];
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->assertWorkflowDependencies(['editorial'], $view);
|
||||
$this->assertTrue($view->storage->status());
|
||||
|
||||
// Create another workflow and repeat the checks above.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/add', [
|
||||
'label' => 'Translation',
|
||||
'id' => 'translation',
|
||||
'workflow_type' => 'content_moderation',
|
||||
], 'Save');
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/translation/add_state', [
|
||||
'label' => 'Needs Review',
|
||||
'id' => 'needs_review',
|
||||
], 'Save');
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/translation/type/node', [
|
||||
'bundles[example_b]' => TRUE,
|
||||
], 'Save');
|
||||
|
||||
$edit['options[value][]'] = ['editorial-published', 'translation-needs_review'];
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->assertWorkflowDependencies(['editorial', 'translation'], $view);
|
||||
$this->assertTrue(isset($view->storage->getDisplay('default')['display_options']['filters']['moderation_state']));
|
||||
$this->assertTrue($view->storage->status());
|
||||
|
||||
// Remove the 'Translation' workflow.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/translation/delete', [], 'Delete');
|
||||
|
||||
// Check that the view has been disabled, the filter has been deleted, the
|
||||
// view can be saved and there are no more config dependencies.
|
||||
$view = Views::getView($view_id);
|
||||
$this->assertFalse($view->storage->status());
|
||||
$this->assertFalse(isset($view->storage->getDisplay('default')['display_options']['filters']['moderation_state']));
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
$this->assertWorkflowDependencies([], $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the moderation state filter when the configured workflow is changed.
|
||||
*
|
||||
* @dataProvider providerTestWorkflowChanges
|
||||
*/
|
||||
public function testWorkflowChanges($view_id, $filter_name) {
|
||||
// Update the view and make the default filter not exposed anymore,
|
||||
// otherwise all results will be shown when there are no more moderated
|
||||
// bundles left.
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", [], 'Hide filter');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
// First, apply the Editorial workflow to both of our content types.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
'bundles[example_b]' => TRUE,
|
||||
], 'Save');
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
// Add a few nodes in various moderation states.
|
||||
$this->createNode(['type' => 'example_a', 'moderation_state' => 'published']);
|
||||
$this->createNode(['type' => 'example_b', 'moderation_state' => 'published']);
|
||||
$archived_node_a = $this->createNode(['type' => 'example_a', 'moderation_state' => 'archived']);
|
||||
$archived_node_b = $this->createNode(['type' => 'example_b', 'moderation_state' => 'archived']);
|
||||
|
||||
// Configure the view to only show nodes in the 'archived' moderation state.
|
||||
$edit['options[value][]'] = ['editorial-archived'];
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
// Check that only the archived nodes from both bundles are displayed by the
|
||||
// view.
|
||||
$view = Views::getView($view_id);
|
||||
$this->executeView($view);
|
||||
$this->assertIdenticalResultset($view, [['nid' => $archived_node_a->id()], ['nid' => $archived_node_b->id()]], ['nid' => 'nid']);
|
||||
|
||||
// Remove the Editorial workflow from one of the bundles.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
'bundles[example_b]' => FALSE,
|
||||
], 'Save');
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->executeView($view);
|
||||
$this->assertIdenticalResultset($view, [['nid' => $archived_node_a->id()]], ['nid' => 'nid']);
|
||||
|
||||
// Check that the view can still be edited and saved without any
|
||||
// intervention.
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
// Remove the Editorial workflow from both bundles.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => FALSE,
|
||||
'bundles[example_b]' => FALSE,
|
||||
], 'Save');
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->executeView($view);
|
||||
|
||||
// Check that the view doesn't return any result.
|
||||
$this->assertEmpty($view->result);
|
||||
|
||||
// Check that the view can not be edited without any intervention anymore
|
||||
// because the user needs to fix the filter.
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
$this->assertSession()->pageTextContains("No valid values found on filter: $filter_name.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testWorkflowChanges.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of view IDs.
|
||||
*/
|
||||
public function providerTestWorkflowChanges() {
|
||||
return [
|
||||
'view on base table, filter on base table' => [
|
||||
'test_content_moderation_state_filter_base_table',
|
||||
'Content: Moderation state'
|
||||
],
|
||||
'view on base table, filter on revision table' => [
|
||||
'test_content_moderation_state_filter_base_table_filter_on_revision',
|
||||
'Content revision: Moderation state'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content moderation state filter caching is correct.
|
||||
*/
|
||||
public function testFilterRenderCache() {
|
||||
// Initially all states of the workflow are displayed.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
], 'Save');
|
||||
$this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived']);
|
||||
|
||||
// Adding a new state to the editorial workflow will display that state in
|
||||
// the list of filters.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
|
||||
'label' => 'Foo',
|
||||
'id' => 'foo',
|
||||
], 'Save');
|
||||
$this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo']);
|
||||
|
||||
// Adding a second workflow to nodes will also show new states.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/new_workflow/type/node', [
|
||||
'bundles[example_b]' => TRUE,
|
||||
], 'Save');
|
||||
$this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo', 'new_workflow-draft', 'new_workflow-published', 'new_workflow-bar']);
|
||||
|
||||
// Add a few more states and change the exposed filter to allow multiple
|
||||
// selections so we can check that the size of the select element does not
|
||||
// exceed 8 options.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
|
||||
'label' => 'Foo 2',
|
||||
'id' => 'foo2',
|
||||
], 'Save');
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
|
||||
'label' => 'Foo 3',
|
||||
'id' => 'foo3',
|
||||
], 'Save');
|
||||
|
||||
$view_id = 'test_content_moderation_state_filter_base_table';
|
||||
$edit['options[expose][multiple]'] = TRUE;
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
$this->assertFilterStates(['editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo', 'editorial-foo2', 'editorial-foo3', 'new_workflow-draft', 'new_workflow-published', 'new_workflow-bar'], TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the states which appear in the filter.
|
||||
*
|
||||
* @param array $states
|
||||
* The states which should appear in the filter.
|
||||
* @param bool $check_size
|
||||
* (optional) Whether to check that size of the select element is not
|
||||
* greater than 8. Defaults to FALSE.
|
||||
*/
|
||||
protected function assertFilterStates($states, $check_size = FALSE) {
|
||||
$this->drupalGet('/filter-test-path');
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Check that the select contains the correct number of options.
|
||||
$assert_session->elementsCount('css', '#edit-default-revision-state option', count($states));
|
||||
|
||||
// Check that the size of the select element does not exceed 8 options.
|
||||
if ($check_size) {
|
||||
$this->assertGreaterThan(8, count($states));
|
||||
$assert_session->elementAttributeContains('css', '#edit-default-revision-state', 'size', 8);
|
||||
}
|
||||
|
||||
// Check that an option exists for each of the expected states.
|
||||
foreach ($states as $state) {
|
||||
$assert_session->optionExists('Default Revision State', $state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the views dependencies on workflow config entities.
|
||||
*
|
||||
* @param string[] $workflow_ids
|
||||
* An array of workflow IDs to check.
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* An executable View object.
|
||||
*/
|
||||
protected function assertWorkflowDependencies(array $workflow_ids, ViewExecutable $view) {
|
||||
$dependencies = $view->getDependencies();
|
||||
|
||||
$expected = [];
|
||||
foreach (Workflow::loadMultiple($workflow_ids) as $workflow) {
|
||||
$expected[] = $workflow->getConfigDependencyName();
|
||||
}
|
||||
|
||||
if ($expected) {
|
||||
$this->assertSame($expected, $dependencies['config']);
|
||||
}
|
||||
else {
|
||||
$this->assertTrue(!isset($dependencies['config']));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestNoBundle;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the views 'moderation_state_filter' filter plugin.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\views\filter\ModerationStateFilter
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation_test_views',
|
||||
'node',
|
||||
'content_moderation',
|
||||
'workflows',
|
||||
'workflow_type_test',
|
||||
'entity_test',
|
||||
'language',
|
||||
'content_translation',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp(FALSE);
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installEntitySchema('entity_test_no_bundle');
|
||||
$this->installSchema('node', 'node_access');
|
||||
$this->installConfig('content_moderation_test_views');
|
||||
$this->installConfig('content_moderation');
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'another_example',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example_non_moderated',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content moderation state filter.
|
||||
*/
|
||||
public function testStateFilterViewsRelationship() {
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->getTypePlugin()->addState('translated_draft', 'Bar');
|
||||
$configuration = $workflow->getTypePlugin()->getConfiguration();
|
||||
$configuration['states']['translated_draft'] += [
|
||||
'published' => FALSE,
|
||||
'default_revision' => FALSE,
|
||||
];
|
||||
$workflow->getTypePlugin()->setConfiguration($configuration);
|
||||
$workflow->save();
|
||||
|
||||
// Create a published default revision and one forward draft revision.
|
||||
$node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Test Node',
|
||||
'moderation_state' => 'published',
|
||||
]);
|
||||
$node->save();
|
||||
$node->setNewRevision();
|
||||
$node->moderation_state = 'draft';
|
||||
$node->save();
|
||||
|
||||
// Create a draft default revision.
|
||||
$second_node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Second Node',
|
||||
'moderation_state' => 'draft',
|
||||
]);
|
||||
$second_node->save();
|
||||
|
||||
// Create a published default revision.
|
||||
$third_node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Third node',
|
||||
'moderation_state' => 'published',
|
||||
]);
|
||||
$third_node->save();
|
||||
|
||||
// Add a non-moderated node.
|
||||
$fourth_node = Node::create([
|
||||
'type' => 'example_non_moderated',
|
||||
'title' => 'Fourth node',
|
||||
]);
|
||||
$fourth_node->save();
|
||||
|
||||
// Create a translated published revision.
|
||||
$translated_forward_revision = $third_node->addTranslation('fr');
|
||||
$translated_forward_revision->title = 'Translated Node';
|
||||
$translated_forward_revision->setNewRevision(TRUE);
|
||||
$translated_forward_revision->moderation_state = 'translated_draft';
|
||||
$translated_forward_revision->save();
|
||||
|
||||
// Four revisions for the nodes when no filter.
|
||||
$this->assertNodesWithFilters([$node, $second_node, $third_node, $third_node], []);
|
||||
|
||||
// The default revision of node one and three is published.
|
||||
$this->assertNodesWithFilters([$node, $third_node], [
|
||||
'default_revision_state' => 'editorial-published',
|
||||
]);
|
||||
|
||||
// The default revision of node two is draft.
|
||||
$this->assertNodesWithFilters([$second_node], [
|
||||
'default_revision_state' => 'editorial-draft',
|
||||
]);
|
||||
|
||||
// Test the same three revisions on a view displaying content revisions.
|
||||
// Both nodes have one draft revision.
|
||||
$this->assertNodesWithFilters([$node, $second_node], [
|
||||
'moderation_state' => 'editorial-draft',
|
||||
], 'test_content_moderation_state_filter_revision_table');
|
||||
// Creating a new forward revision of node three, creates a second published
|
||||
// revision of of the original language, hence there are two published
|
||||
// revisions of node three.
|
||||
$this->assertNodesWithFilters([$node, $third_node, $third_node], [
|
||||
'moderation_state' => 'editorial-published',
|
||||
], 'test_content_moderation_state_filter_revision_table');
|
||||
// There is a single forward translated revision with a new state, which is
|
||||
// also filterable.
|
||||
$this->assertNodesWithFilters([$translated_forward_revision], [
|
||||
'moderation_state' => 'editorial-translated_draft',
|
||||
], 'test_content_moderation_state_filter_revision_table');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the moderation filter with a non-translatable entity type.
|
||||
*/
|
||||
public function testNonTranslatableEntityType() {
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
|
||||
$workflow->save();
|
||||
|
||||
$test_entity = EntityTestNoBundle::create([
|
||||
'moderation_state' => 'draft',
|
||||
]);
|
||||
$test_entity->save();
|
||||
|
||||
$view = Views::getView('test_content_moderation_state_filter_entity_test');
|
||||
$view->setExposedInput([
|
||||
'moderation_state' => 'editorial-draft',
|
||||
]);
|
||||
$view->execute();
|
||||
$this->assertIdenticalResultset($view, [['id' => $test_entity->id()]], ['id' => 'id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the list of states in the filter plugin.
|
||||
*/
|
||||
public function testStateFilterStatesList() {
|
||||
// By default a view of nodes will not have states to filter.
|
||||
$this->assertPluginStates([]);
|
||||
|
||||
// Adding a content type to the editorial workflow will enable all of the
|
||||
// editorial states.
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
]);
|
||||
|
||||
// Adding a workflow which is not content moderation will not add any
|
||||
// additional states to the views filter.
|
||||
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test']);
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
]);
|
||||
|
||||
// Adding a new content moderation workflow will add additional states to
|
||||
// filter.
|
||||
$workflow = Workflow::create(['id' => 'moderation_test', 'type' => 'content_moderation', 'label' => 'Moderation test']);
|
||||
$workflow->getTypePlugin()->addState('foo', 'Foo State');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
'Moderation test' => [
|
||||
'moderation_test-foo' => 'Foo State',
|
||||
'moderation_test-draft' => 'Draft',
|
||||
'moderation_test-published' => 'Published',
|
||||
],
|
||||
]);
|
||||
|
||||
// Deleting a workflow will remove the states from the filter.
|
||||
$workflow = Workflow::load('moderation_test');
|
||||
$workflow->delete();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
]);
|
||||
|
||||
// Deleting a state from a workflow will remove the state from the filter.
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->deleteState('archived');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the plugin states.
|
||||
*
|
||||
* @param string[] $states
|
||||
* The states which should appear in the filter.
|
||||
*/
|
||||
protected function assertPluginStates($states) {
|
||||
$plugin = Views::pluginManager('filter')->createInstance('moderation_state_filter', []);
|
||||
$view = Views::getView('test_content_moderation_state_filter_base_table');
|
||||
$plugin->init($view, $view->getDisplay());
|
||||
$this->assertEquals($states, $plugin->getValueOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the nodes appear when the test view is executed.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface[] $nodes
|
||||
* Nodes to assert are in the views result.
|
||||
* @param array $filters
|
||||
* An array of filters to apply to the view.
|
||||
* @param string $view_id
|
||||
* The view to execute for the results.
|
||||
*/
|
||||
protected function assertNodesWithFilters(array $nodes, array $filters, $view_id = 'test_content_moderation_state_filter_base_table') {
|
||||
$view = Views::getView($view_id);
|
||||
$view->setExposedInput($filters);
|
||||
$view->execute();
|
||||
|
||||
// Verify the join configuration.
|
||||
$query = $view->getQuery();
|
||||
$join = $query->getTableInfo('content_moderation_state')['join'];
|
||||
$configuration = $join->configuration;
|
||||
$this->assertEquals('content_moderation_state_field_revision', $configuration['table']);
|
||||
$this->assertEquals('content_entity_revision_id', $configuration['field']);
|
||||
$this->assertEquals('vid', $configuration['left_field']);
|
||||
$this->assertEquals('content_entity_type_id', $configuration['extra'][0]['field']);
|
||||
$this->assertEquals('node', $configuration['extra'][0]['value']);
|
||||
$this->assertEquals('langcode', $configuration['extra'][1]['field']);
|
||||
$this->assertEquals('langcode', $configuration['extra'][1]['left_field']);
|
||||
|
||||
$expected_result = [];
|
||||
foreach ($nodes as $node) {
|
||||
$expected_result[] = ['nid' => $node->id()];
|
||||
}
|
||||
$this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid']);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,9 @@ namespace Drupal\entity_test\Entity;
|
|||
* id = "entity_test_no_bundle",
|
||||
* label = @Translation("Entity Test without bundle"),
|
||||
* base_table = "entity_test_no_bundle",
|
||||
* handlers = {
|
||||
* "views_data" = "Drupal\views\EntityViewsData"
|
||||
* },
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "revision" = "revision_id",
|
||||
|
|
|
@ -229,7 +229,9 @@ class InOperator extends FilterPluginBase {
|
|||
'#default_value' => $default_value,
|
||||
// These are only valid for 'select' type, but do no harm to checkboxes.
|
||||
'#multiple' => TRUE,
|
||||
'#size' => count($options) > 8 ? 8 : count($options),
|
||||
// The value options can be a multidimensional array if the value form
|
||||
// type is a select list, so make sure that they are counted correctly.
|
||||
'#size' => min(count($options, COUNT_RECURSIVE), 8),
|
||||
];
|
||||
$user_input = $form_state->getUserInput();
|
||||
if ($exposed && !isset($user_input[$identifier])) {
|
||||
|
|
|
@ -124,7 +124,7 @@ interface WorkflowTypeInterface extends PluginWithFormsInterface, DerivativeInsp
|
|||
* A list of state IDs to get. If NULL then all states will be returned.
|
||||
*
|
||||
* @return \Drupal\workflows\StateInterface[]
|
||||
* An array of workflow states.
|
||||
* An array of workflow states, keyed by state IDs.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown if $state_ids contains a state ID that does not exist.
|
||||
|
|
Loading…
Reference in New Issue