Unverified Commit 418e4991 authored by larowlan's avatar larowlan
Browse files

Issue #2862041 by Sam152, amateescu, timmillwood, jibran, hamrant, dawehner,...

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 45662bb9
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'
<?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 @@ public function getViewsData() {
'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 @@ public function getViewsData() {
'default_formatter' => 'content_moderation_state',
'field_name' => 'moderation_state',
],
'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
];
}
......
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: { }