diff --git a/core/modules/content_moderation/config/optional/views.view.moderated_content.yml b/core/modules/content_moderation/config/optional/views.view.moderated_content.yml new file mode 100644 index 0000000..c4191b3 --- /dev/null +++ b/core/modules/content_moderation/config/optional/views.view.moderated_content.yml @@ -0,0 +1,817 @@ +langcode: en +status: true +dependencies: + module: + - content_moderation + - node + - user +id: moderated_content +label: 'Moderated content' +module: views +description: 'Find and manage 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 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: 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: + draft: draft + archived: 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: + published: 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 the 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/node/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: { } 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 5a10d85..1dab40b 100644 --- a/core/modules/content_moderation/config/schema/content_moderation.schema.yml +++ b/core/modules/content_moderation/config/schema/content_moderation.schema.yml @@ -6,6 +6,10 @@ views.filter.latest_revision: type: string label: 'Value' +views.filter.moderation_state_filter: + type: views.filter.in_operator + label: 'Moderation State Filter' + content_moderation.state: type: workflows.state mapping: diff --git a/core/modules/content_moderation/content_moderation.links.task.yml b/core/modules/content_moderation/content_moderation.links.task.yml index f92e92e..95105d3 100644 --- a/core/modules/content_moderation/content_moderation.links.task.yml +++ b/core/modules/content_moderation/content_moderation.links.task.yml @@ -1,3 +1,14 @@ 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 diff --git a/core/modules/content_moderation/content_moderation.routing.yml b/core/modules/content_moderation/content_moderation.routing.yml index 2ee2c47..f1819b6 100644 --- a/core/modules/content_moderation/content_moderation.routing.yml +++ b/core/modules/content_moderation/content_moderation.routing.yml @@ -1,3 +1,11 @@ +content_moderation.admin_moderated_content: + path: '/admin/content/node/moderated' + defaults: + _controller: '\Drupal\content_moderation\Controller\ModeratedContentController::listing' + _title: 'Moderated content' + requirements: + _permission: 'administer nodes' + content_moderation.workflow_type_edit_form: path: '/admin/config/workflow/workflows/manage/{workflow}/type/{entity_type_id}' defaults: diff --git a/core/modules/content_moderation/src/Controller/ModeratedContentController.php b/core/modules/content_moderation/src/Controller/ModeratedContentController.php new file mode 100644 index 0000000..030a72b --- /dev/null +++ b/core/modules/content_moderation/src/Controller/ModeratedContentController.php @@ -0,0 +1,25 @@ +entityTypeManager()->getDefinition('node'); + + return $this->entityTypeManager()->createHandlerInstance(ModeratedNodeListBuilder::class, $entity_type)->render(); + } + +} diff --git a/core/modules/content_moderation/src/ModeratedNodeListBuilder.php b/core/modules/content_moderation/src/ModeratedNodeListBuilder.php new file mode 100644 index 0000000..451fe5a --- /dev/null +++ b/core/modules/content_moderation/src/ModeratedNodeListBuilder.php @@ -0,0 +1,145 @@ +moderationInformation = $moderation_information; + $this->bundleInfo = $bundle_info; + } + + /** + * {@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('content_moderation.moderation_information'), + $container->get('entity_type.bundle.info') + ); + } + + /** + * {@inheritdoc} + */ + public function load() { + // @todo This is very slow because it does an entity query + an uncached + // entity revision load for each entity, but there is no other way to load + // the latest revisions. Fix it in https://www.drupal.org/node/2864995. + $entities = []; + foreach ($this->getEntityIds() as $entity_id) { + $entities[$entity_id] = $this->moderationInformation->getLatestRevision($this->entityTypeId, $entity_id); + } + return $entities; + } + + /** + * {@inheritdoc} + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery() + ->sort($this->entityType->getKey('id')); + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + + // Exclude published content from the list. + $query->condition($this->entityType->getKey('published'), NodeInterface::NOT_PUBLISHED); + + // Filter the list to only show nodes of bundles that can be moderated. + $bundles = $this->bundleInfo->getBundleInfo($this->entityTypeId); + $moderated_bundles = array_filter(array_keys($bundles), function ($bundle) { + return $this->moderationInformation->shouldModerateEntitiesOfBundle($this->entityType, $bundle); + }); + + if ($moderated_bundles) { + $query->condition($this->entityType->getKey('bundle'), $moderated_bundles, 'IN'); + + return $query->execute(); + } + + return []; + } + + /** + * {@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.', ['@label' => $this->entityType->getLabel()]); + + return $build; + } + +} diff --git a/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php b/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php new file mode 100644 index 0000000..0d44883 --- /dev/null +++ b/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php @@ -0,0 +1,140 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@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') + ); + } + + /** + * {@inheritdoc} + */ + public function getValueOptions() { + if (isset($this->valueOptions)) { + return $this->valueOptions; + } + + // Find all workflows which are moderating entity types or bundles of the + // same type the view is displaying. + $states = []; + foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) { + $workflow_type = $workflow->getTypePlugin(); + if (in_array($this->getEntityType(), $workflow_type->getEntityTypes(), TRUE)) { + $states += $workflow_type->getStates(); + } + } + $this->valueOptions = array_map(function (StateInterface $state) { + return $state->label(); + }, $states); + + return $this->valueOptions; + } + + /** + * {@inheritdoc} + */ + public function query() { + $this->ensureMyTable(); + // 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' => $this->tableAlias, + '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->query->addRelationship('content_moderation_state', $join, 'content_moderation_state_field_revision'); + parent::query(); + } + + /** + * {@inheritdoc} + */ + protected function opEmpty() { + if ($this->operator === 'empty') { + $operator = "IS NULL"; + } + else { + $operator = "IS NOT NULL"; + } + + $this->query->addWhere($this->options['group'], 'content_moderation_state.moderation_state', NULL, $operator); + } + + /** + * {@inheritdoc} + */ + protected function opSimple() { + if (empty($this->value)) { + return; + } + + $this->query->addWhere($this->options['group'], 'content_moderation_state.moderation_state', $this->value, $this->operator); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + // @todo calculate dependencies in https://www.drupal.org/node/2901690. + return parent::calculateDependencies(); + } + +} diff --git a/core/modules/content_moderation/src/ViewsData.php b/core/modules/content_moderation/src/ViewsData.php index 189562d..4f0573f 100644 --- a/core/modules/content_moderation/src/ViewsData.php +++ b/core/modules/content_moderation/src/ViewsData.php @@ -202,7 +202,8 @@ public function getViewsData() { ], ], ], - 'field' => ['default_formatter' => 'content_moderation_state'], + 'field' => ['id' => 'field', 'default_formatter' => 'content_moderation_state', 'field_name' => 'moderation_state'], + 'filter' => ['id' => 'moderation_state_filter'], ]; $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable(); @@ -221,7 +222,8 @@ public function getViewsData() { ], ], ], - 'field' => ['default_formatter' => 'content_moderation_state'], + 'field' => ['id' => 'field', 'default_formatter' => 'content_moderation_state', 'field_name' => 'moderation_state'], + 'filter' => ['id' => 'moderation_state_filter'], ]; } diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter.yml new file mode 100644 index 0000000..edffd3d --- /dev/null +++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter.yml @@ -0,0 +1,240 @@ +uuid: 3c177a50-5e2c-457c-ad87-3587641d38ec +langcode: en +status: true +dependencies: + module: + - content_moderation + - node + - user +id: test_content_moderation_state_filter +label: test_content_moderation_state_filter +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_revision + field: moderation_state + relationship: latest_revision__node + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: moderation_state_1_op + label: 'Latest Revision State' + description: '' + use_operator: false + operator: moderation_state_1_op + identifier: latest_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 + sorts: { } + header: { } + footer: { } + empty: { } + relationships: + latest_revision__node: + id: latest_revision__node + table: content_revision_tracker + field: latest_revision__node + relationship: none + group_type: group + admin_label: 'Content latest revision' + required: true + plugin_id: standard + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - 'user.node_grants:view' + - user.permissions + tags: { } + diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_entity_test.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_entity_test.yml new file mode 100644 index 0000000..c902ee2 --- /dev/null +++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_entity_test.yml @@ -0,0 +1,169 @@ +uuid: f4dd0f80-d28c-4212-ae44-8ab081d1b2d8 +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: { } + diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_revision_table.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_revision_table.yml new file mode 100644 index 0000000..9005e99 --- /dev/null +++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_revision_table.yml @@ -0,0 +1,205 @@ +uuid: 808fcf43-b98b-443b-84eb-62ea69f2c71d +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: { } + 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: { } + diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/content_moderation_test_views.info.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/content_moderation_test_views.info.yml index b96ef84..e3d37c3 100644 --- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/content_moderation_test_views.info.yml +++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/content_moderation_test_views.info.yml @@ -8,3 +8,4 @@ dependencies: - content_moderation - node - views + - entity_test diff --git a/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php b/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php new file mode 100644 index 0000000..a1a299c --- /dev/null +++ b/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php @@ -0,0 +1,254 @@ +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(); + + 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(); + + // 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(); + + // The latest revision for both nodes is a draft status. + $this->assertNodesWithFilters([$node, $second_node], [ + 'latest_revision_state' => 'draft', + ]); + + // Only node three has a published latest revision. + $this->assertNodesWithFilters([$third_node], [ + 'latest_revision_state' => 'published', + ]); + + // The default revision of node one and three is published. + $this->assertNodesWithFilters([$node, $third_node], [ + 'default_revision_state' => 'published', + ]); + + // The default revision of node two is draft. + $this->assertNodesWithFilters([$second_node], [ + 'default_revision_state' => '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' => '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' => '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' => 'translated_draft', + ], 'test_content_moderation_state_filter_revision_table'); + } + + /** + * Test the moderation filter with a non-translateable entity type. + */ + public function testNonTranslateableEntityType() { + $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' => '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([ + 'draft' => 'Draft', + 'published' => 'Published', + '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([ + 'draft' => 'Draft', + 'published' => 'Published', + 'archived' => 'Archived', + ]); + + // Adding a new content moderation workflow will add additional states to + // filter. + $workflow = Workflow::create(['id' => 'moderation_test', 'type' => 'content_moderation']); + $workflow->getTypePlugin()->addState('foo', 'Foo State'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example'); + $workflow->save(); + $this->assertPluginStates([ + 'draft' => 'Draft', + 'published' => 'Published', + 'archived' => 'Archived', + 'foo' => 'Foo State', + ]); + } + + /** + * 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'); + $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') { + $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']); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestComputedField.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestComputedField.php new file mode 100644 index 0000000..c873ccc --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestComputedField.php @@ -0,0 +1,45 @@ +setLabel('Computed Field Test') + ->setComputed(TRUE) + ->setClass(ComputedTestFieldItemList::class); + + return $fields; + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoBundle.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoBundle.php index 5907021..0369a64 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoBundle.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoBundle.php @@ -9,6 +9,9 @@ * 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", diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php b/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php index 3bb0d24..eca7335 100644 --- a/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php +++ b/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php @@ -16,6 +16,17 @@ class EntityTestViewsData extends EntityViewsData { public function getViewsData() { $views_data = parent::getViewsData(); + if ($this->entityType->id() === 'entity_test_computed_field') { + $views_data['entity_test_computed_field']['computed_string_field'] = [ + 'title' => t('Computed String Field'), + 'field' => [ + 'id' => 'field', + 'default_formatter' => 'string', + 'field_name' => 'computed_string_field', + ], + ]; + } + if ($this->entityType->id() != 'entity_test') { return $views_data; } diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php new file mode 100644 index 0000000..864d33f --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php @@ -0,0 +1,37 @@ +get('entity_test_computed_field_item_list_value', []) as $delta => $item) { + $this->list[$delta] = $this->createItem($delta, $item); + } + } + + /** + * {@inheritdoc} + */ + public function get($index) { + $this->computedListProperty(); + return isset($this->list[$index]) ? $this->list[$index] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + $this->computedListProperty(); + return parent::getIterator(); + } + +} diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php index 2ba2086..7393a24 100644 --- a/core/modules/views/src/EntityViewsData.php +++ b/core/modules/views/src/EntityViewsData.php @@ -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()) { diff --git a/core/modules/views/src/Plugin/views/field/EntityField.php b/core/modules/views/src/Plugin/views/field/EntityField.php index f580e50..ee2882c 100644 --- a/core/modules/views/src/Plugin/views/field/EntityField.php +++ b/core/modules/views/src/Plugin/views/field/EntityField.php @@ -316,24 +316,33 @@ public function clickSort($order) { } /** - * Gets the field storage of the used field. + * Gets the field storage definition. * * @return \Drupal\Core\Field\FieldStorageDefinitionInterface + * The field storage definition used by this handler. */ protected function getFieldStorageDefinition() { $entity_type_id = $this->definition['entity_type']; $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); - $field_storage = NULL; // @todo Unify 'entity field'/'field_name' instead of converting back and // forth. https://www.drupal.org/node/2410779 - if (isset($this->definition['field_name'])) { - $field_storage = $field_storage_definitions[$this->definition['field_name']]; + if (isset($this->definition['field_name']) && isset($field_storage_definitions[$this->definition['field_name']])) { + return $field_storage_definitions[$this->definition['field_name']]; } - elseif (isset($this->definition['entity field'])) { - $field_storage = $field_storage_definitions[$this->definition['entity field']]; + + if (isset($this->definition['entity field']) && isset($field_storage_definitions[$this->definition['entity field']])) { + return $field_storage_definitions[$this->definition['entity field']]; + } + + // The list of field storage definitions above does not include computed + // base fields, so we need to explicitly fetch a list of all base fields in + // order to support them. + // @see \Drupal\Core\Entity\EntityFieldManager::getFieldStorageDefinitions() + $base_fields = $this->entityManager->getBaseFieldDefinitions($entity_type_id); + if (isset($this->definition['field_name']) && isset($base_fields[$this->definition['field_name']])) { + return $base_fields[$this->definition['field_name']]->getFieldStorageDefinition(); } - return $field_storage; } /** diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.computed_field_view.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.computed_field_view.yml new file mode 100644 index 0000000..d8de3f1 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.computed_field_view.yml @@ -0,0 +1,171 @@ +langcode: en +status: true +dependencies: + module: + - entity_test +id: computed_field_view +label: 'Computed Field View' +module: views +description: '' +tag: '' +base_table: entity_test_computed_field +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: 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: + computed_string_field: + id: computed_string_field + table: entity_test_computed_field + field: computed_string_field + 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: 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: entity_test_computed_field + plugin_id: field + filters: { } + sorts: { } + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: foo + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } diff --git a/core/modules/views/tests/src/Kernel/Handler/ComputedFieldTest.php b/core/modules/views/tests/src/Kernel/Handler/ComputedFieldTest.php new file mode 100644 index 0000000..8f85f69 --- /dev/null +++ b/core/modules/views/tests/src/Kernel/Handler/ComputedFieldTest.php @@ -0,0 +1,57 @@ +installEntitySchema('entity_test_computed_field'); + } + + /** + * Test the computed field handler. + */ + public function testComputedFieldHandler() { + \Drupal::state()->set('entity_test_computed_field_item_list_value', ['computed string']); + + $entity = EntityTestComputedField::create([]); + $entity->save(); + + $view = Views::getView('computed_field_view'); + + $rendered_view = $view->preview(); + $output = $this->container->get('renderer')->renderRoot($rendered_view); + $this->assertContains('computed string', (string) $output); + } + +} diff --git a/core/modules/workflows/src/WorkflowTypeInterface.php b/core/modules/workflows/src/WorkflowTypeInterface.php index 0d91071..25660e1 100644 --- a/core/modules/workflows/src/WorkflowTypeInterface.php +++ b/core/modules/workflows/src/WorkflowTypeInterface.php @@ -129,7 +129,7 @@ public function hasState($state_id); * 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. diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php index e0bf655..12c4c75 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php @@ -16,6 +16,7 @@ use Drupal\Core\TypedData\ListInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\entity_test\Entity\EntityTestComputedField; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; @@ -738,6 +739,16 @@ public function testComputedProperties() { } /** + * Test computed fields. + */ + public function testComputedFields() { + \Drupal::state()->set('entity_test_computed_field_item_list_value', ['foo computed']); + + $entity = EntityTestComputedField::create([]); + $this->assertEquals($entity->computed_string_field->value, 'foo computed'); + } + + /** * Executes the computed properties tests for the given entity type. * * @param string $entity_type