diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 05718a8..8e278cd 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -304,6 +304,7 @@ Contact module Content Moderation module - Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood +- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan Content Translation module - Francesco Placella 'plach' https://www.drupal.org/u/plach diff --git a/core/modules/content_moderation/config/schema/content_moderation.schema.yml b/core/modules/content_moderation/config/schema/content_moderation.schema.yml index cb966ea..cb51ea5 100644 --- a/core/modules/content_moderation/config/schema/content_moderation.schema.yml +++ b/core/modules/content_moderation/config/schema/content_moderation.schema.yml @@ -70,6 +70,7 @@ block_content.type.*.third_party.content_moderation: views.filter.latest_revision: type: views_filter label: 'Latest revision' + type: mapping mapping: value: type: string diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module index d3aa1c1..1436809 100644 --- a/core/modules/content_moderation/content_moderation.module +++ b/core/modules/content_moderation/content_moderation.module @@ -215,6 +215,8 @@ function content_moderation_action_info_alter(&$definitions) { /** * Implements hook_views_data_alter(). + * + * @todo Use \Drupal\content_moderation\ViewsData */ function content_moderation_views_data_alter(array &$data) { diff --git a/core/modules/content_moderation/content_moderation.permissions.yml b/core/modules/content_moderation/content_moderation.permissions.yml index 34cf91a..440a764 100644 --- a/core/modules/content_moderation/content_moderation.permissions.yml +++ b/core/modules/content_moderation/content_moderation.permissions.yml @@ -2,6 +2,10 @@ view any unpublished content: title: View any unpublished content description: This permission is necessary for any users that may moderate content. +'view moderation states': + title: View moderation states + description: View moderation states. + 'administer moderation states': title: Administer moderation states description: Create and edit moderation states. diff --git a/core/modules/content_moderation/content_moderation.routing.yml b/core/modules/content_moderation/content_moderation.routing.yml index 1ecc3cc..ea71032 100644 --- a/core/modules/content_moderation/content_moderation.routing.yml +++ b/core/modules/content_moderation/content_moderation.routing.yml @@ -1,5 +1,5 @@ content_moderation.overview: - path: '/admin/structure/content-moderation' + path: '/admin/config/workflow/moderation' defaults: _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage' _title: 'Content moderation' @@ -8,7 +8,7 @@ content_moderation.overview: # ModerationState routing definition entity.moderation_state.collection: - path: '/admin/structure/content-moderation/states' + path: '/admin/config/workflow/moderation/states' defaults: _entity_list: 'moderation_state' _title: 'Moderation states' @@ -18,7 +18,7 @@ entity.moderation_state.collection: _admin_route: TRUE entity.moderation_state.add_form: - path: '/admin/structure/content-moderation/states/add' + path: '/admin/config/workflow/moderation/states/add' defaults: _entity_form: 'moderation_state.add' _title: 'Add Moderation state' @@ -28,7 +28,7 @@ entity.moderation_state.add_form: _admin_route: TRUE entity.moderation_state.edit_form: - path: '/admin/structure/content-moderation/states/{moderation_state}' + path: '/admin/config/workflow/moderation/states/{moderation_state}' defaults: _entity_form: 'moderation_state.edit' _title: 'Edit Moderation state' @@ -38,7 +38,7 @@ entity.moderation_state.edit_form: _admin_route: TRUE entity.moderation_state.delete_form: - path: '/admin/structure/content-moderation/states/{moderation_state}/delete' + path: '/admin/config/workflow/moderation/states/{moderation_state}/delete' defaults: _entity_form: 'moderation_state.delete' _title: 'Delete Moderation state' @@ -49,7 +49,7 @@ entity.moderation_state.delete_form: # ModerationStateTransition routing definition entity.moderation_state_transition.collection: - path: '/admin/structure/content-moderation/transitions' + path: '/admin/config/workflow/moderation/transitions' defaults: _entity_list: 'moderation_state_transition' _title: 'Moderation state transitions' @@ -59,7 +59,7 @@ entity.moderation_state_transition.collection: _admin_route: TRUE entity.moderation_state_transition.add_form: - path: '/admin/structure/content-moderation/transitions/add' + path: '/admin/config/workflow/moderation/transitions/add' defaults: _entity_form: 'moderation_state_transition.add' _title: 'Add Moderation state transition' @@ -69,7 +69,7 @@ entity.moderation_state_transition.add_form: _admin_route: TRUE entity.moderation_state_transition.edit_form: - path: '/admin/structure/content-moderation/transitions/{moderation_state_transition}' + path: '/admin/config/workflow/moderation/transitions/{moderation_state_transition}' defaults: _entity_form: 'moderation_state_transition.edit' _title: 'Edit Moderation state transition' @@ -79,7 +79,7 @@ entity.moderation_state_transition.edit_form: _admin_route: TRUE entity.moderation_state_transition.delete_form: - path: '/admin/structure/content-moderation/transitions/{moderation_state_transition}/delete' + path: '/admin/config/workflow/moderation/transitions/{moderation_state_transition}/delete' defaults: _entity_form: 'moderation_state_transition.delete' _title: 'Delete Moderation state transition' diff --git a/core/modules/content_moderation/content_moderation.services.yml b/core/modules/content_moderation/content_moderation.services.yml index 0c3c872..9982fc8 100644 --- a/core/modules/content_moderation/content_moderation.services.yml +++ b/core/modules/content_moderation/content_moderation.services.yml @@ -33,3 +33,6 @@ services: arguments: ['@database'] tags: - { name: backend_overridable } + content_moderation.views_data: + class: \Drupal\content_moderation\ViewsData + arguments: ['@entity_type.manager', '@content_moderation.moderation_information'] diff --git a/core/modules/content_moderation/content_moderation.views.inc b/core/modules/content_moderation/content_moderation.views.inc index e69de29..355978d 100644 --- a/core/modules/content_moderation/content_moderation.views.inc +++ b/core/modules/content_moderation/content_moderation.views.inc @@ -0,0 +1,10 @@ +getViewsData(); +} diff --git a/core/modules/content_moderation/src/Entity/ModerationState.php b/core/modules/content_moderation/src/Entity/ModerationState.php index e205a11..015d966 100644 --- a/core/modules/content_moderation/src/Entity/ModerationState.php +++ b/core/modules/content_moderation/src/Entity/ModerationState.php @@ -13,6 +13,7 @@ * label = @Translation("Moderation state"), * handlers = { * "list_builder" = "Drupal\content_moderation\ModerationStateListBuilder", + * "access" = "Drupal\content_moderation\ModerationStateAccessControlHandler", * "form" = { * "add" = "Drupal\content_moderation\Form\ModerationStateForm", * "edit" = "Drupal\content_moderation\Form\ModerationStateForm", @@ -20,7 +21,6 @@ * }, * }, * config_prefix = "state", - * admin_permission = "administer moderation states", * entity_keys = { * "id" = "id", * "label" = "label", diff --git a/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php b/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php index e69de29..729ba1e 100644 --- a/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php +++ b/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php @@ -0,0 +1,41 @@ +orIf($admin_access); + } + + return $admin_access; + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'administer moderation states'); + } + +} diff --git a/core/modules/content_moderation/src/ViewsData.php b/core/modules/content_moderation/src/ViewsData.php index e69de29..3742765 100644 --- a/core/modules/content_moderation/src/ViewsData.php +++ b/core/modules/content_moderation/src/ViewsData.php @@ -0,0 +1,192 @@ +entityTypeManager = $entity_type_manager; + $this->moderationInformation = $moderation_information; + } + + /** + * Returns the views data. + * + * @return array + * The views data. + */ + public function getViewsData() { + $data = []; + + $data['workbench_revision_tracker']['table']['group'] = $this->t('Workbench moderation'); + + $data['workbench_revision_tracker']['entity_type'] = [ + 'title' => $this->t('Entity type'), + 'field' => [ + 'id' => 'standard', + ], + 'filter' => [ + 'id' => 'string', + ], + 'argument' => [ + 'id' => 'string', + ], + 'sort' => [ + 'id' => 'standard', + ], + ]; + + $data['workbench_revision_tracker']['entity_id'] = [ + 'title' => $this->t('Entity ID'), + 'field' => [ + 'id' => 'standard', + ], + 'filter' => [ + 'id' => 'numeric', + ], + 'argument' => [ + 'id' => 'numeric', + ], + 'sort' => [ + 'id' => 'standard', + ], + ]; + + $data['workbench_revision_tracker']['langcode'] = [ + 'title' => $this->t('Entity language'), + 'field' => [ + 'id' => 'standard', + ], + 'filter' => [ + 'id' => 'language', + ], + 'argument' => [ + 'id' => 'language', + ], + 'sort' => [ + 'id' => 'standard', + ], + ]; + + $data['workbench_revision_tracker']['revision_id'] = [ + 'title' => $this->t('Latest revision ID'), + 'field' => [ + 'id' => 'standard', + ], + 'filter' => [ + 'id' => 'numeric', + ], + 'argument' => [ + 'id' => 'numeric', + ], + 'sort' => [ + 'id' => 'standard', + ], + ]; + + // Add a join for each entity type to the workbench_revision_tracker table. + foreach ($this->moderationInformation->selectRevisionableEntities($this->entityTypeManager->getDefinitions()) as $entity_type_id => $entity_type) { + /** @var \Drupal\views\EntityViewsDataInterface $views_data */ + // We need the views_data handler in order to get the table name later. + if ($this->entityTypeManager->hasHandler($entity_type_id, 'views_data') && $views_data = $this->entityTypeManager->getHandler($entity_type_id, 'views_data')) { + // Add a join from the entity base table to the revision tracker table. + $base_table = $views_data->getViewsTableForEntityType($entity_type); + $data['workbench_revision_tracker']['table']['join'][$base_table] = [ + 'left_field' => $entity_type->getKey('id'), + 'field' => 'entity_id', + 'extra' => [ + [ + 'field' => 'entity_type', + 'value' => $entity_type_id, + ], + ], + ]; + + // Some entity types might not be translatable. + if ($entity_type->hasKey('langcode')) { + $data['workbench_revision_tracker']['table']['join'][$base_table]['extra'][] = [ + 'field' => 'langcode', + 'left_field' => $entity_type->getKey('langcode'), + 'operation' => '=', + ]; + } + + // Add a relationship between the revision tracker table to the latest + // revision on the entity revision table. + $data['workbench_revision_tracker']['latest_revision__' . $entity_type_id] = [ + 'title' => $this->t('@label latest revision', ['@label' => $entity_type->getLabel()]), + 'group' => $this->t('@label revision', ['@label' => $entity_type->getLabel()]), + 'relationship' => [ + 'id' => 'standard', + 'label' => $this->t('@label latest revision', ['@label' => $entity_type->getLabel()]), + 'base' => $this->getRevisionViewsTableForEntityType($entity_type), + 'base field' => $entity_type->getKey('revision'), + 'relationship field' => 'revision_id', + 'extra' => [ + [ + 'left_field' => 'entity_type', + 'value' => $entity_type_id, + ], + ], + ], + ]; + + // Some entity types might not be translatable. + if ($entity_type->hasKey('langcode')) { + $data['workbench_revision_tracker']['latest_revision__' . $entity_type_id]['relationship']['extra'][] = [ + 'left_field' => 'langcode', + 'field' => $entity_type->getKey('langcode'), + 'operation' => '=', + ]; + } + } + } + + return $data; + } + + /** + * Gets the table of an entity type to be used as revision table in views. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return string + * The revision base table. + */ + protected function getRevisionViewsTableForEntityType(EntityTypeInterface $entity_type) { + return $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable(); + } + +} diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml index e69de29..002b04d 100644 --- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml +++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml @@ -0,0 +1,431 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +_core: + default_config_hash: 5PoZglK-ZXEh1fnzVSvRWZ7QvLPqkT-gwz-8DJT1cKY +id: test_content_moderation_latest_revision +label: test_content_moderation_latest_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: 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_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: 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 + revision_id: + id: revision_id + table: workbench_revision_tracker + field: revision_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 + title: + id: title + table: node_field_revision + field: title + relationship: latest_revision__node + 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: string + settings: + link_to_entity: 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: title + plugin_id: field + moderation_state: + id: moderation_state + table: node_field_data + field: moderation_state + 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: target_id + type: entity_reference_label + settings: + link: true + 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: moderation_state + plugin_id: field + moderation_state_revision: + id: moderation_state_revision + table: node_field_revision + field: moderation_state + relationship: latest_revision__node + 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: target_id + type: entity_reference_label + settings: + link: true + 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: moderation_state + plugin_id: field + filters: { } + 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: + latest_revision__node: + id: latest_revision__node + table: workbench_revision_tracker + field: latest_revision__node + relationship: none + group_type: group + admin_label: 'Content latest revision' + required: false + plugin_id: standard + arguments: { } + display_extenders: { } + rendering_language: '***LANGUAGE_entity_default***' + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php index e69de29..e7bcf72 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php @@ -0,0 +1,133 @@ +createNodeType('Test', $node_type_id); + + $permissions = [ + 'access content', + 'view all revisions', + 'view moderation states', + ]; + $editor1 = $this->drupalCreateUser($permissions); + $this->drupalLogin($editor1); + + /** @var Node $node_1 */ + $node_1 = Node::create([ + 'type' => $node_type_id, + 'title' => 'Draft node', + 'uid' => $editor1->id(), + ]); + $node_1->moderation_state->target_id = 'draft'; + $node_1->save(); + + /** @var Node $node_2 */ + $node_2 = Node::create([ + 'type' => $node_type_id, + 'title' => 'Review node', + 'uid' => $editor1->id(), + ]); + $node_2->moderation_state->target_id = 'needs_review'; + $node_2->save(); + + /** @var Node $node_3 */ + $node_3 = Node::create([ + 'type' => $node_type_id, + 'title' => 'Published node', + 'uid' => $editor1->id(), + ]); + $node_3->moderation_state->target_id = 'published'; + $node_3->save(); + + // Resave the node with a new state. + $node_3->setTitle('Archived node'); + $node_3->moderation_state->target_id = 'archived'; + $node_3->save(); + + // Now show the View, and confirm that the state labels are showing. + $this->drupalGet('/latest'); + $page = $this->getSession()->getPage(); + $this->assertTrue($page->hasLink('Draft')); + $this->assertTrue($page->hasLink('Needs Review')); + $this->assertTrue($page->hasLink('Archived')); + $this->assertFalse($page->hasLink('Published')); + + // Now log in as an admin and test the same thing. + $permissions = [ + 'access content', + 'view all revisions', + 'administer moderation states', + ]; + $admin1 = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin1); + + $this->drupalGet('/latest'); + $page = $this->getSession()->getPage(); + $this->assertEquals(200, $this->getSession()->getStatusCode()); + $this->assertTrue($page->hasLink('Draft')); + $this->assertTrue($page->hasLink('Needs Review')); + $this->assertTrue($page->hasLink('Archived')); + $this->assertFalse($page->hasLink('Published')); + } + + /** + * Creates a new node type. + * + * @param string $label + * The human-readable label of the type to create. + * @param string $machine_name + * The machine name of the type to create. + * + * @return NodeType + * The node type just created. + */ + protected function createNodeType($label, $machine_name) { + /** @var NodeType $node_type */ + $node_type = NodeType::create([ + 'type' => $machine_name, + 'label' => $label, + ]); + $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE); + $node_type->save(); + + return $node_type; + } + +} diff --git a/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php b/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php index e69de29..26de71f 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php @@ -0,0 +1,78 @@ +installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installSchema('node', 'node_access'); + $this->installConfig('content_moderation_test_views'); + } + + public function testViewsData() { + $node_type = NodeType::create([ + 'type' => 'page', + ]); + $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE); + $node_type->save(); + + $node = Node::create([ + 'type' => 'page', + 'title' => 'Test title first revision', + 'moderation_state' => 'published', + ]); + $node->save(); + + $revision = clone $node; + $revision->setNewRevision(TRUE); + $revision->isDefaultRevision(FALSE); + $revision->title->value = 'Test title second revision'; + $revision->moderation_state->target_id = 'draft'; + $revision->save(); + + $view = Views::getView('test_content_moderation_latest_revision'); + $view->execute(); + + // Ensure that the workbench_revision_tracker contains the right latest + // revision ID. + // Also ensure that the relationship back to the revision table contains the + // right latest revision. + $expected_result = [ + [ + 'nid' => $node->id(), + 'revision_id' => $revision->getRevisionId(), + 'title' => $revision->label(), + 'moderation_state_revision' => 'published', + 'moderation_state' => 'published', + ], + ]; + $this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid', 'workbench_revision_tracker_revision_id' => 'revision_id', 'moderation_state_revision' => 'moderation_state_revision', 'moderation_state' => 'moderation_state']); + } + +}