Skip to content
Snippets Groups Projects
Commit 8eac2a1d authored by Angie Byron's avatar Angie Byron
Browse files

Issue #2902187 by amateescu, timmillwood, Sam152, webchick, Manuel Garcia,...

Issue #2902187 by amateescu, timmillwood, Sam152, webchick, Manuel Garcia, xjm, plach, DuneBL, larowlan, Bojhan, jibran, Berdir, jojototh: Provide a way for users to moderate content
parent 59a12084
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
langcode: en
status: true
dependencies:
module:
- content_moderation
- node
- user
id: moderated_content
label: 'Moderated content'
module: views
description: 'Find and moderate content.'
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 any unpublished 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: Filter
reset_button: true
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 50
offset: 0
id: 0
total_pages: null
tags:
previous: ' Previous'
next: 'Next ›'
first: '« First'
last: 'Last »'
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
quantity: 9
style:
type: table
options:
grouping: { }
row_class: ''
default_row_class: true
override: true
sticky: true
caption: ''
summary: ''
description: ''
columns:
title: title
type: type
name: name
moderation_state: moderation_state
changed: changed
info:
title:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
type:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
name:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
moderation_state:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
changed:
sortable: true
default_sort_order: desc
align: ''
separator: ''
empty_column: false
responsive: ''
default: changed
empty_table: true
row:
type: fields
fields:
title:
id: title
table: node_field_revision
field: title
relationship: none
group_type: group
admin_label: ''
label: Title
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: false
ellipsis: false
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: true
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: string
settings:
link_to_entity: 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: title
plugin_id: field
type:
id: type
table: node_field_data
field: type
relationship: nid
group_type: group
admin_label: ''
label: 'Content type'
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: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: entity_reference_label
settings:
link: false
group_column: target_id
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: type
plugin_id: field
name:
id: name
table: users_field_data
field: name
relationship: uid
group_type: group
admin_label: ''
label: Author
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: true
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: user_name
settings:
link_to_entity: 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: user
entity_field: name
plugin_id: field
moderation_state:
id: moderation_state
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: ''
label: 'Moderation state'
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: true
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: content_moderation_state
settings: { }
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
plugin_id: field
changed:
id: changed
table: node_field_revision
field: changed
relationship: none
group_type: group
admin_label: ''
label: Updated
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: false
ellipsis: false
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: true
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: timestamp
settings:
date_format: short
custom_date_format: ''
timezone: ''
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: changed
plugin_id: field
operations:
id: operations
table: node_revision
field: operations
relationship: none
group_type: group
admin_label: ''
label: Operations
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: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
entity_type: node
plugin_id: entity_operations
filters:
latest_revision:
id: latest_revision
table: node_revision
field: latest_revision
relationship: none
group_type: group
admin_label: ''
operator: '='
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
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: latest_revision
title:
id: title
table: node_field_revision
field: title
relationship: none
group_type: group
admin_label: ''
operator: contains
value: ''
group: 1
exposed: true
expose:
operator_id: title_op
label: Title
description: ''
use_operator: false
operator: title_op
identifier: title
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
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
entity_field: title
plugin_id: string
type:
id: type
table: node_field_data
field: type
relationship: nid
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: type_op
label: 'Content type'
description: ''
use_operator: false
operator: type_op
identifier: type
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
entity_field: type
plugin_id: bundle
moderation_state:
id: moderation_state
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: ''
operator: in
value:
editorial-draft: editorial-draft
editorial-archived: editorial-archived
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: true
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
langcode:
id: langcode
table: node_field_revision
field: langcode
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: langcode_op
label: Language
description: ''
use_operator: false
operator: langcode_op
identifier: langcode
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
entity_field: langcode
plugin_id: language
moderation_state_1:
id: moderation_state_1
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: ''
operator: 'not in'
value:
editorial-published: editorial-published
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: { }
title: 'Moderated content'
header: { }
footer: { }
empty:
area_text_custom:
id: area_text_custom
table: views
field: area_text_custom
relationship: none
group_type: group
admin_label: ''
empty: true
tokenize: false
content: 'No moderated content available. Only pending versions of content, such as drafts, are listed here.'
plugin_id: text_custom
relationships:
nid:
id: nid
table: node_field_revision
field: nid
relationship: none
group_type: group
admin_label: 'Get the actual content from a content revision.'
required: false
entity_type: node
entity_field: nid
plugin_id: standard
uid:
id: uid
table: node_field_revision
field: uid
relationship: none
group_type: group
admin_label: User
required: false
entity_type: node
entity_field: uid
plugin_id: standard
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups:
1: AND
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
moderated_content:
display_plugin: page
id: moderated_content
display_title: 'Moderated content'
position: 1
display_options:
display_extenders: { }
path: admin/content/moderated
display_description: ''
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
content_moderation.workflows:
deriver: 'Drupal\content_moderation\Plugin\Derivative\DynamicLocalTasks'
weight: 100
content_moderation.content:
title: 'Overview'
route_name: system.admin_content
parent_id: system.admin_content
content_moderation.moderated_content:
title: 'Moderated content'
route_name: content_moderation.admin_moderated_content
parent_id: system.admin_content
weight: 1
content_moderation.admin_moderated_content:
path: '/admin/content/moderated'
defaults:
_controller: '\Drupal\content_moderation\Controller\ModeratedContentController::nodeListing'
_title: 'Moderated content'
requirements:
_permission: 'view any unpublished content'
content_moderation.workflow_type_edit_form:
path: '/admin/config/workflow/workflows/manage/{workflow}/type/{entity_type_id}'
defaults:
......
<?php
namespace Drupal\content_moderation\Controller;
use Drupal\content_moderation\ModeratedNodeListBuilder;
use Drupal\Core\Controller\ControllerBase;
/**
* Defines a controller to list moderated nodes.
*/
class ModeratedContentController extends ControllerBase {
/**
* Provides the listing page for moderated nodes.
*
* @return array
* A render array as expected by drupal_render().
*/
public function nodeListing() {
$entity_type = $this->entityTypeManager()->getDefinition('node');
return $this->entityTypeManager()->createHandlerInstance(ModeratedNodeListBuilder::class, $entity_type)->render();
}
}
<?php
namespace Drupal\content_moderation;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\node\NodeListBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class to build a listing of moderated node entities.
*/
class ModeratedNodeListBuilder extends NodeListBuilder {
/**
* The entity storage class.
*
* @var \Drupal\Core\Entity\RevisionableStorageInterface
*/
protected $storage;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new ModeratedNodeListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, RedirectDestinationInterface $redirect_destination, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($entity_type, $storage, $date_formatter, $redirect_destination);
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('date.formatter'),
$container->get('redirect.destination'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function load() {
$revision_ids = $this->getEntityRevisionIds();
return $this->storage->loadMultipleRevisions($revision_ids);
}
/**
* Loads entity revision IDs using a pager sorted by the entity revision ID.
*
* @return array
* An array of entity revision IDs.
*/
protected function getEntityRevisionIds() {
$query = $this->entityTypeManager->getStorage('content_moderation_state')->getAggregateQuery()
->aggregate('content_entity_id', 'MAX')
->groupBy('content_entity_revision_id')
->condition('content_entity_type_id', $this->entityTypeId)
->condition('moderation_state', 'published', '<>')
->sort('content_entity_revision_id', 'DESC');
// Only add the pager if a limit is specified.
if ($this->limit) {
$query->pager($this->limit);
}
$result = $query->execute();
return $result ? array_column($result, 'content_entity_revision_id') : [];
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header = parent::buildHeader();
$header['status'] = $this->t('Moderation state');
return $header;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row = parent::buildRow($entity);
$row['status'] = $entity->moderation_state->value;
return $row;
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$build['table']['#empty'] = $this->t('There is no moderated @label yet. Only pending versions of @label, such as drafts, are listed here.', ['@label' => $this->entityType->getLabel()]);
return $build;
}
}
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\workflows\Entity\Workflow;
/**
* Tests moderated content administration page functionality.
*
* @group content_moderation
*/
class ModeratedContentViewTest extends BrowserTestBase {
/**
* A user with permission to bypass access content.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
public static $modules = ['content_moderation', 'node', 'views'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page'])->save();
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article'])->save();
$this->drupalCreateContentType(['type' => 'unmoderated_type', 'name' => 'Unmoderated type'])->save();
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
$workflow->save();
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'view any unpublished content', 'administer nodes', 'bypass node access']);
}
/**
* Tests the moderated content page.
*/
public function testModeratedContentPage() {
$assert_sesison = $this->assertSession();
$this->drupalLogin($this->adminUser);
// Use an explicit changed time to ensure the expected order in the content
// admin listing. We want these to appear in the table in the same order as
// they appear in the following code, and the 'moderated_content' view has a
// table style configuration with a default sort on the 'changed' field
// descending.
$time = \Drupal::time()->getRequestTime();
$excluded_nodes['published_page'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time--, 'moderation_state' => 'published']);
$excluded_nodes['published_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'published']);
$excluded_nodes['unmoderated_type'] = $this->drupalCreateNode(['type' => 'unmoderated_type', 'changed' => $time--]);
$excluded_nodes['unmoderated_type']->setNewRevision(TRUE);
$excluded_nodes['unmoderated_type']->isDefaultRevision(FALSE);
$excluded_nodes['unmoderated_type']->changed->value = $time--;
$excluded_nodes['unmoderated_type']->save();
$nodes['published_then_draft_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'published', 'title' => 'first article - published']);
$nodes['published_then_draft_article']->setNewRevision(TRUE);
$nodes['published_then_draft_article']->setTitle('first article - draft');
$nodes['published_then_draft_article']->moderation_state->value = 'draft';
$nodes['published_then_draft_article']->changed->value = $time--;
$nodes['published_then_draft_article']->save();
$nodes['published_then_archived_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'published']);
$nodes['published_then_archived_article']->setNewRevision(TRUE);
$nodes['published_then_archived_article']->moderation_state->value = 'archived';
$nodes['published_then_archived_article']->changed->value = $time--;
$nodes['published_then_archived_article']->save();
$nodes['draft_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'draft']);
$nodes['draft_page_1'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time--, 'moderation_state' => 'draft']);
$nodes['draft_page_2'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time, 'moderation_state' => 'draft']);
// Verify view, edit, and delete links for any content.
$this->drupalGet('admin/content/moderated');
$assert_sesison->statusCodeEquals(200);
// Check that nodes with pending revisions appear in the view.
$node_type_labels = $this->xpath('//td[contains(@class, "views-field-type")]');
$delta = 0;
foreach ($nodes as $node) {
$assert_sesison->linkByHrefExists('node/' . $node->id());
$assert_sesison->linkByHrefExists('node/' . $node->id() . '/edit');
$assert_sesison->linkByHrefExists('node/' . $node->id() . '/delete');
// Verify that we can see the content type label.
$this->assertEquals($node->type->entity->label(), trim($node_type_labels[$delta]->getText()));
$delta++;
}
// Check that nodes that are not moderated or do not have a pending revision
// do not appear in the view.
foreach ($excluded_nodes as $node) {
$assert_sesison->linkByHrefNotExists('node/' . $node->id());
}
// Check that the latest revision is displayed.
$assert_sesison->pageTextContains('first article - draft');
$assert_sesison->pageTextNotContains('first article - published');
// Verify filtering by moderation state.
$this->drupalGet('admin/content/moderated', ['query' => ['moderation_state' => 'editorial-draft']]);
$assert_sesison->linkByHrefExists('node/' . $nodes['published_then_draft_article']->id() . '/edit');
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_article']->id() . '/edit');
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_1']->id() . '/edit');
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_1']->id() . '/edit');
$assert_sesison->linkByHrefNotExists('node/' . $nodes['published_then_archived_article']->id() . '/edit');
// Verify filtering by moderation state and content type.
$this->drupalGet('admin/content/moderated', ['query' => ['moderation_state' => 'editorial-draft', 'type' => 'page']]);
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_1']->id() . '/edit');
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_2']->id() . '/edit');
$assert_sesison->linkByHrefNotExists('node/' . $nodes['published_then_draft_article']->id() . '/edit');
$assert_sesison->linkByHrefNotExists('node/' . $nodes['published_then_archived_article']->id() . '/edit');
$assert_sesison->linkByHrefNotExists('node/' . $nodes['draft_article']->id() . '/edit');
}
}
......@@ -183,6 +183,13 @@ public function getViewsData() {
'id' => 'entity_operations',
],
];
$data[$revision_table]['operations'] = [
'field' => [
'title' => $this->t('Operations links'),
'help' => $this->t('Provides links to perform entity operations.'),
'id' => 'entity_operations',
],
];
}
if ($this->entityType->hasViewBuilderClass()) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment