diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php index 95ab0b2cf4..08642a06b9 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemInterface; @@ -116,8 +117,8 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; $url = NULL; if ($this->getSetting('link_to_entity')) { - // For the default revision this falls back to 'canonical' - $url = $items->getEntity()->urlInfo('revision'); + // For the default revision this falls back to 'canonical'. + $url = $this->getEntityUrl($items->getEntity()); } foreach ($items as $delta => $item) { @@ -155,4 +156,18 @@ protected function viewValue(FieldItemInterface $item) { ]; } + /** + * Gets the URI elements of the entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity object. + * + * @return \Drupal\Core\Url + * The URI elements of the entity. + */ + protected function getEntityUrl(EntityInterface $entity) { + // For the default revision this falls back to 'canonical'. + return $entity->toUrl('revision'); + } + } diff --git a/core/modules/comment/comment.post_update.php b/core/modules/comment/comment.post_update.php new file mode 100644 index 0000000000..04bf2db28d --- /dev/null +++ b/core/modules/comment/comment.post_update.php @@ -0,0 +1,38 @@ +getModule('comment')->getPath() . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY; + $storage = new FileStorage($config_install_path); + $entity_type_manager + ->getStorage('action') + ->create($storage->read('system.action.comment_delete_action')) + ->save(); + + // Only create if the views module is enabled. + if (!$module_handler->moduleExists('views')) { + return; + } + + // Save the comment admin view to config. + $optional_install_path = $module_handler->getModule('comment')->getPath() . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; + $storage = new FileStorage($optional_install_path); + $entity_type_manager + ->getStorage('view') + ->create($storage->read('views.view.comment')) + ->save(); +} diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml index 3d698b837a..80ee86c795 100644 --- a/core/modules/comment/comment.routing.yml +++ b/core/modules/comment/comment.routing.yml @@ -2,7 +2,7 @@ comment.admin: path: '/admin/content/comment' defaults: _title: 'Comments' - _controller: '\Drupal\comment\Controller\AdminController::adminPage' + _form: '\Drupal\comment\Form\CommentAdminOverview' type: 'new' requirements: _permission: 'administer comments' @@ -11,7 +11,7 @@ comment.admin_approval: path: '/admin/content/comment/approval' defaults: _title: 'Unapproved comments' - _controller: '\Drupal\comment\Controller\AdminController::adminPage' + _form: '\Drupal\comment\Form\CommentAdminOverview' type: 'approval' requirements: _permission: 'administer comments' @@ -54,6 +54,14 @@ entity.comment.delete_form: _entity_access: 'comment.delete' comment: \d+ +comment.multiple_delete_confirm: + path: '/admin/content/comment/delete' + defaults: + _title: 'Delete' + _form: '\Drupal\comment\Form\ConfirmDeleteMultiple' + requirements: + _permission: 'administer comments' + comment.reply: path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}' defaults: diff --git a/core/modules/comment/config/install/system.action.comment_delete_action.yml b/core/modules/comment/config/install/system.action.comment_delete_action.yml new file mode 100644 index 0000000000..035299ea12 --- /dev/null +++ b/core/modules/comment/config/install/system.action.comment_delete_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - comment +id: comment_delete_action +label: 'Delete comment' +type: comment +plugin: comment_delete_action +configuration: { } diff --git a/core/modules/comment/config/optional/views.view.comment.yml b/core/modules/comment/config/optional/views.view.comment.yml new file mode 100644 index 0000000000..427cb52e24 --- /dev/null +++ b/core/modules/comment/config/optional/views.view.comment.yml @@ -0,0 +1,1052 @@ +langcode: en +status: true +dependencies: + module: + - comment + - user +id: comment +label: Comments +module: comment +description: 'Find and manage comments.' +tag: default +base_table: comment_field_data +base_field: cid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'administer comments' + 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: 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: + comment_bulk_form: comment_bulk_form + subject: subject + uid: uid + entity_id: entity_id + changed: changed + operations: operations + info: + comment_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + subject: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + uid: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + entity_id: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + operations: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + comment_bulk_form: + id: comment_bulk_form + table: comment + field: comment_bulk_form + 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 + action_title: 'Update Options' + include_exclude: include + selected_actions: + - comment_delete_action + - comment_unpublish_action + plugin_id: comment_bulk_form + entity_type: comment + subject: + id: subject + table: comment_field_data + field: subject + relationship: none + group_type: group + admin_label: '' + label: Subject + 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: comment_permalink + 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: comment + entity_field: subject + plugin_id: field + name: + id: name + table: comment_field_data + field: name + relationship: none + 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: comment_username + 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: comment + entity_field: name + plugin_id: field + entity_id: + id: entity_id + table: comment_field_data + field: entity_id + relationship: none + group_type: group + admin_label: '' + label: 'Posted in' + 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: 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: comment + entity_field: entity_id + plugin_id: commented_entity + changed: + id: changed + table: comment_field_data + 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: 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: 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: comment + entity_field: changed + plugin_id: field + operations: + id: operations + table: comment + 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: comment + plugin_id: entity_operations + filters: + status: + id: status + table: comment_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + 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: comment + entity_field: status + plugin_id: boolean + sorts: + changed: + id: changed + table: comment_field_data + field: changed + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + entity_type: comment + entity_field: changed + plugin_id: date + title: Comments + 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 comments available.' + plugin_id: text_custom + arguments: { } + display_extenders: { } + use_more: false + use_more_always: true + use_more_text: more + use_ajax: false + hide_attachment_summary: false + show_admin_links: true + group_by: false + css_class: '' + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - user.permissions + cacheable: false + max-age: 0 + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: 'Published Comments' + position: 1 + display_options: + path: admin/content/comment + menu: + type: tab + title: Comments + description: 'Comments published' + parent: '' + weight: 0 + context: '0' + menu_name: admin + display_description: 'The approved comments listing.' + display_extenders: { } + exposed_block: false + display_comment: '' + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - user.permissions + cacheable: false + max-age: 0 + tags: { } + page_2: + display_plugin: page + id: page_2 + display_title: 'Unapproved Comments' + position: 2 + display_options: + path: admin/content/comment/approval + menu: + type: tab + title: 'Unapproved comments' + description: 'Comments unapproved' + parent: '' + weight: 1 + context: '0' + menu_name: admin + display_description: 'The unapproved comments listing.' + filters: + status: + id: status + table: comment_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '0' + 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: comment + entity_field: status + plugin_id: boolean + defaults: + filters: false + filter_groups: false + fields: false + display_extenders: { } + fields: + comment_bulk_form: + id: comment_bulk_form + table: comment + field: comment_bulk_form + 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 + action_title: 'Update Options' + include_exclude: include + selected_actions: + - comment_delete_action + - comment_publish_action + plugin_id: comment_bulk_form + entity_type: comment + subject: + id: subject + table: comment_field_data + field: subject + relationship: none + group_type: group + admin_label: '' + label: Subject + 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: comment_permalink + 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: comment + entity_field: subject + plugin_id: field + name: + id: name + table: comment_field_data + field: name + relationship: none + 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: comment_username + 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: comment + entity_field: name + plugin_id: field + entity_id: + id: entity_id + table: comment_field_data + field: entity_id + relationship: none + group_type: group + admin_label: '' + label: 'Posted in' + 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: 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: comment + entity_field: entity_id + plugin_id: commented_entity + changed: + id: changed + table: comment_field_data + 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: 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: 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: comment + entity_field: changed + plugin_id: field + operations: + id: operations + table: comment + 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: comment + plugin_id: entity_operations + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - user.permissions + cacheable: false + max-age: 0 + tags: { } diff --git a/core/modules/comment/config/schema/comment.schema.yml b/core/modules/comment/config/schema/comment.schema.yml index a55488a222..045e9ad544 100644 --- a/core/modules/comment/config/schema/comment.schema.yml +++ b/core/modules/comment/config/schema/comment.schema.yml @@ -38,6 +38,10 @@ action.configuration.comment_unpublish_action: type: action_configuration_default label: 'Unpublish comment configuration' +action.configuration.comment_delete_action: + type: action_configuration_default + label: 'Delete comment configuration' + comment.type.*: type: config_entity label: 'Comment type settings' @@ -105,3 +109,6 @@ field.field_settings.comment: preview: type: integer label: 'Preview comment' + +field.formatter.settings.comment_permalink: + type: field.formatter.settings.string diff --git a/core/modules/comment/config/schema/comment.views.schema.yml b/core/modules/comment/config/schema/comment.views.schema.yml index 606af826cd..a46a004d73 100644 --- a/core/modules/comment/config/schema/comment.views.schema.yml +++ b/core/modules/comment/config/schema/comment.views.schema.yml @@ -16,6 +16,14 @@ views.field.comment_entity_link: type: boolean label: 'Show teaser-style link' +views.field.comment_bulk_form: + type: views_field_bulk_form + label: 'Comment bulk form' + +views.field.commented_entity: + type: views.field.field + label: 'Commented entity' + views.field.comment_last_timestamp: type: views.field.date label: 'Last comment date' diff --git a/core/modules/comment/src/CommentViewsData.php b/core/modules/comment/src/CommentViewsData.php index 52af015d59..ead61c1a33 100644 --- a/core/modules/comment/src/CommentViewsData.php +++ b/core/modules/comment/src/CommentViewsData.php @@ -23,6 +23,7 @@ public function getViewsData() { $data['comment_field_data']['subject']['title'] = $this->t('Title'); $data['comment_field_data']['subject']['help'] = $this->t('The title of the comment.'); + $data['comment_field_data']['subject']['field']['default_formatter'] = 'comment_permalink'; $data['comment_field_data']['name']['title'] = $this->t('Author'); $data['comment_field_data']['name']['help'] = $this->t("The name of the comment's author. Can be rendered as a link to the author's homepage."); @@ -168,6 +169,17 @@ public function getViewsData() { ], ]; + $data['comment_field_data']['entity_id']['field']['id'] = 'commented_entity'; + unset($data['comment_field_data']['entity_id']['relationship']); + + $data['comment']['comment_bulk_form'] = [ + 'title' => $this->t('Comment operations bulk form'), + 'help' => $this->t('Add a form element that lets you run operations on multiple comments.'), + 'field' => [ + 'id' => 'comment_bulk_form', + ], + ]; + $data['comment_field_data']['thread']['field'] = [ 'title' => $this->t('Depth'), 'help' => $this->t('Display the depth of the comment if it is threaded.'), diff --git a/core/modules/comment/src/Controller/AdminController.php b/core/modules/comment/src/Controller/AdminController.php deleted file mode 100644 index 61cb8761dd..0000000000 --- a/core/modules/comment/src/Controller/AdminController.php +++ /dev/null @@ -1,62 +0,0 @@ -get('form_builder') - ); - } - - /** - * Constructs an AdminController object. - * - * @param \Drupal\Core\Form\FormBuilderInterface $form_builder - * The form builder. - */ - public function __construct(FormBuilderInterface $form_builder) { - $this->formBuilder = $form_builder; - } - - /** - * Presents an administrative comment listing. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request of the page. - * @param string $type - * The type of the overview form ('approval' or 'new') default to 'new'. - * - * @return array - * Then comment multiple delete confirmation form or the comments overview - * administration form. - */ - public function adminPage(Request $request, $type = 'new') { - if ($request->request->get('operation') == 'delete' && $request->request->get('comments')) { - return $this->formBuilder->getForm('\Drupal\comment\Form\ConfirmDeleteMultiple', $request); - } - else { - return $this->formBuilder->getForm('\Drupal\comment\Form\CommentAdminOverview', $type); - } - } - -} diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php index 24f8b0aa2e..b18bf3d0b0 100644 --- a/core/modules/comment/src/Form/CommentAdminOverview.php +++ b/core/modules/comment/src/Form/CommentAdminOverview.php @@ -3,13 +3,13 @@ namespace Drupal\comment\Form; use Drupal\comment\CommentInterface; -use Drupal\comment\CommentStorageInterface; use Drupal\Component\Utility\Unicode; use Drupal\Core\Datetime\DateFormatterInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\user\PrivateTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -18,11 +18,11 @@ class CommentAdminOverview extends FormBase { /** - * The entity storage. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * The comment storage. @@ -46,22 +46,30 @@ class CommentAdminOverview extends FormBase { protected $moduleHandler; /** + * The tempstore factory. + * + * @var \Drupal\user\PrivateTempStoreFactory + */ + protected $tempStoreFactory; + + /** * Creates a CommentAdminOverview form. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity manager service. - * @param \Drupal\comment\CommentStorageInterface $comment_storage - * The comment storage. * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter * The date formatter service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. */ - public function __construct(EntityManagerInterface $entity_manager, CommentStorageInterface $comment_storage, DateFormatterInterface $date_formatter, ModuleHandlerInterface $module_handler) { - $this->entityManager = $entity_manager; - $this->commentStorage = $comment_storage; + public function __construct(EntityTypeManagerInterface $entity_type_manager, DateFormatterInterface $date_formatter, ModuleHandlerInterface $module_handler, PrivateTempStoreFactory $temp_store_factory) { + $this->entityTypeManager = $entity_type_manager; + $this->commentStorage = $entity_type_manager->getStorage('comment'); $this->dateFormatter = $date_formatter; $this->moduleHandler = $module_handler; + $this->tempStoreFactory = $temp_store_factory; } /** @@ -69,10 +77,10 @@ public function __construct(EntityManagerInterface $entity_manager, CommentStora */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), - $container->get('entity.manager')->getStorage('comment'), + $container->get('entity_type.manager'), $container->get('date.formatter'), - $container->get('module_handler') + $container->get('module_handler'), + $container->get('user.private_tempstore') ); } @@ -171,7 +179,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = ' } foreach ($commented_entity_ids as $entity_type => $ids) { - $commented_entities[$entity_type] = $this->entityManager->getStorage($entity_type)->loadMultiple($ids); + $commented_entities[$entity_type] = $this->entityTypeManager + ->getStorage($entity_type) + ->loadMultiple($ids); } foreach ($comments as $comment) { @@ -255,23 +265,33 @@ public function validateForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { $operation = $form_state->getValue('operation'); $cids = $form_state->getValue('comments'); - - foreach ($cids as $cid) { - // Delete operation handled in \Drupal\comment\Form\ConfirmDeleteMultiple - // see \Drupal\comment\Controller\AdminController::adminPage(). - if ($operation == 'unpublish') { - $comment = $this->commentStorage->load($cid); - $comment->setPublished(FALSE); + /** @var \Drupal\comment\CommentInterface[] $comments */ + $comments = $this->commentStorage->loadMultiple($cids); + if ($operation != 'delete') { + foreach ($comments as $comment) { + if ($operation == 'unpublish') { + $comment->setUnpublished(); + } + elseif ($operation == 'publish') { + $comment->setPublished(); + } $comment->save(); } - elseif ($operation == 'publish') { - $comment = $this->commentStorage->load($cid); - $comment->setPublished(TRUE); - $comment->save(); + drupal_set_message($this->t('The update has been performed.')); + $form_state->setRedirect('comment.admin'); + } + else { + $info = []; + /** @var \Drupal\comment\CommentInterface $comment */ + foreach ($comments as $comment) { + $langcode = $comment->language()->getId(); + $info[$comment->id()][$langcode] = $langcode; } + $this->tempStoreFactory + ->get('comment_multiple_delete_confirm') + ->set($this->currentUser()->id(), $info); + $form_state->setRedirect('comment.multiple_delete_confirm'); } - drupal_set_message($this->t('The update has been performed.')); - $form_state->setRedirect('comment.admin'); } } diff --git a/core/modules/comment/src/Form/ConfirmDeleteMultiple.php b/core/modules/comment/src/Form/ConfirmDeleteMultiple.php index 5906248316..1be7a68c15 100644 --- a/core/modules/comment/src/Form/ConfirmDeleteMultiple.php +++ b/core/modules/comment/src/Form/ConfirmDeleteMultiple.php @@ -3,7 +3,7 @@ namespace Drupal\comment\Form; use Drupal\comment\CommentStorageInterface; -use Drupal\Component\Utility\Html; +use Drupal\user\PrivateTempStoreFactory; use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -15,6 +15,13 @@ class ConfirmDeleteMultiple extends ConfirmFormBase { /** + * The tempstore factory. + * + * @var \Drupal\user\PrivateTempStoreFactory + */ + protected $tempStoreFactory; + + /** * The comment storage. * * @var \Drupal\comment\CommentStorageInterface @@ -24,18 +31,21 @@ class ConfirmDeleteMultiple extends ConfirmFormBase { /** * An array of comments to be deleted. * - * @var \Drupal\comment\CommentInterface[] + * @var string[][] */ - protected $comments; + protected $commentInfo; /** * Creates an new ConfirmDeleteMultiple form. * * @param \Drupal\comment\CommentStorageInterface $comment_storage * The comment storage. + * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. */ - public function __construct(CommentStorageInterface $comment_storage) { + public function __construct(CommentStorageInterface $comment_storage, PrivateTempStoreFactory $temp_store_factory) { $this->commentStorage = $comment_storage; + $this->tempStoreFactory = $temp_store_factory; } /** @@ -43,7 +53,8 @@ public function __construct(CommentStorageInterface $comment_storage) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('comment') + $container->get('entity.manager')->getStorage('comment'), + $container->get('user.private_tempstore') ); } @@ -58,7 +69,7 @@ public function getFormId() { * {@inheritdoc} */ public function getQuestion() { - return $this->t('Are you sure you want to delete these comments and all their children?'); + return $this->formatPlural(count($this->commentInfo), 'Are you sure you want to delete this comment and all its children?', 'Are you sure you want to delete these comments and all their children?'); } /** @@ -72,39 +83,56 @@ public function getCancelUrl() { * {@inheritdoc} */ public function getConfirmText() { - return $this->t('Delete comments'); + return $this->t('Delete'); } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $edit = $form_state->getUserInput(); + $this->commentInfo = $this->tempStoreFactory->get('comment_multiple_delete_confirm')->get($this->currentUser()->id()); + if (empty($this->commentInfo)) { + return $this->redirect('comment.admin'); + } + /** @var \Drupal\comment\CommentInterface[] $comments */ + $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo)); + + $items = []; + foreach ($this->commentInfo as $id => $langcodes) { + foreach ($langcodes as $langcode) { + $comment = $comments[$id]->getTranslation($langcode); + $key = $id . ':' . $langcode; + $default_key = $id . ':' . $comment->getUntranslated()->language()->getId(); + + // If we have a translated entity we build a nested list of translations + // that will be deleted. + $languages = $comment->getTranslationLanguages(); + if (count($languages) > 1 && $comment->isDefaultTranslation()) { + $names = []; + foreach ($languages as $translation_langcode => $language) { + $names[] = $language->getName(); + unset($items[$id . ':' . $translation_langcode]); + } + $items[$default_key] = [ + 'label' => [ + '#markup' => $this->t('@label (Original translation) - The following comment translations will be deleted:', ['@label' => $comment->label()]), + ], + 'deleted_translations' => [ + '#theme' => 'item_list', + '#items' => $names, + ], + ]; + } + elseif (!isset($items[$default_key])) { + $items[$key] = $comment->label(); + } + } + } $form['comments'] = [ - '#prefix' => '', - '#tree' => TRUE, + '#theme' => 'item_list', + '#items' => $items, ]; - // array_filter() returns only elements with actual values. - $comment_counter = 0; - $this->comments = $this->commentStorage->loadMultiple(array_keys(array_filter($edit['comments']))); - foreach ($this->comments as $comment) { - $cid = $comment->id(); - $form['comments'][$cid] = [ - '#type' => 'hidden', - '#value' => $cid, - '#prefix' => '
  • ', - '#suffix' => Html::escape($comment->label()) . '
  • ' - ]; - $comment_counter++; - } - $form['operation'] = ['#type' => 'hidden', '#value' => 'delete']; - - if (!$comment_counter) { - drupal_set_message($this->t('There do not appear to be any comments to delete, or your selected comment was deleted by another administrator.')); - $form_state->setRedirect('comment.admin'); - } return parent::buildForm($form, $form_state); } @@ -113,12 +141,56 @@ public function buildForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - if ($form_state->getValue('confirm')) { - $this->commentStorage->delete($this->comments); - $count = count($form_state->getValue('comments')); - $this->logger('comment')->notice('Deleted @count comments.', ['@count' => $count]); - drupal_set_message($this->formatPlural($count, 'Deleted 1 comment.', 'Deleted @count comments.')); + if ($form_state->getValue('confirm') && !empty($this->commentInfo)) { + $total_count = 0; + $delete_comments = []; + /** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */ + $delete_translations = []; + /** @var \Drupal\comment\CommentInterface[] $comments */ + $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo)); + + foreach ($this->commentInfo as $id => $langcodes) { + foreach ($langcodes as $langcode) { + $comment = $comments[$id]->getTranslation($langcode); + if ($comment->isDefaultTranslation()) { + $delete_comments[$id] = $comment; + unset($delete_translations[$id]); + $total_count += count($comment->getTranslationLanguages()); + } + elseif (!isset($delete_comments[$id])) { + $delete_translations[$id][] = $comment; + } + } + } + + if ($delete_comments) { + $this->commentStorage->delete($delete_comments); + $this->logger('content')->notice('Deleted @count comments.', ['@count' => count($delete_comments)]); + } + + if ($delete_translations) { + $count = 0; + foreach ($delete_translations as $id => $translations) { + $comment = $comments[$id]->getUntranslated(); + foreach ($translations as $translation) { + $comment->removeTranslation($translation->language()->getId()); + } + $comment->save(); + $count += count($translations); + } + if ($count) { + $total_count += $count; + $this->logger('content')->notice('Deleted @count comment translations.', ['@count' => $count]); + } + } + + if ($total_count) { + drupal_set_message($this->formatPlural($total_count, 'Deleted 1 comment.', 'Deleted @count comments.')); + } + + $this->tempStoreFactory->get('comment_multiple_delete_confirm')->delete($this->currentUser()->id()); } + $form_state->setRedirectUrl($this->getCancelUrl()); } diff --git a/core/modules/comment/src/Plugin/Action/DeleteComment.php b/core/modules/comment/src/Plugin/Action/DeleteComment.php new file mode 100644 index 0000000000..675b021809 --- /dev/null +++ b/core/modules/comment/src/Plugin/Action/DeleteComment.php @@ -0,0 +1,98 @@ +currentUser = $current_user; + $this->tempStore = $temp_store_factory->get('comment_multiple_delete_confirm'); + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('user.private_tempstore'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $entities) { + $info = []; + /** @var \Drupal\comment\CommentInterface $comment */ + foreach ($entities as $comment) { + $langcode = $comment->language()->getId(); + $info[$comment->id()][$langcode] = $langcode; + } + $this->tempStore->set($this->currentUser->id(), $info); + } + + /** + * {@inheritdoc} + */ + public function execute($entity = NULL) { + $this->executeMultiple([$entity]); + } + + /** + * {@inheritdoc} + */ + public function access($comment, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\comment\CommentInterface $comment */ + return $comment->access('delete', $account, $return_as_object); + } + +} diff --git a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentPermalinkFormatter.php b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentPermalinkFormatter.php new file mode 100644 index 0000000000..0f344126d2 --- /dev/null +++ b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentPermalinkFormatter.php @@ -0,0 +1,51 @@ +permalink(); + if ($comment->hasField('comment_body') && ($body = $comment->get('comment_body')->value)) { + $attributes = $comment_permalink->getOption('attributes') ?: []; + $attributes += ['title' => Unicode::truncate($body, 128)]; + $comment_permalink->setOption('attributes', $attributes); + } + return $comment_permalink; + } + + /** + * {@inheritdoc} + */ + public static function isApplicable(FieldDefinitionInterface $field_definition) { + return parent::isApplicable($field_definition) && $field_definition->getTargetEntityTypeId() === 'comment' && $field_definition->getName() === 'subject'; + } + +} diff --git a/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php b/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php new file mode 100644 index 0000000000..d4283734f7 --- /dev/null +++ b/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php @@ -0,0 +1,21 @@ +t('Select one or more comments to perform the update on.'); + } + +} diff --git a/core/modules/comment/src/Plugin/views/field/CommentedEntity.php b/core/modules/comment/src/Plugin/views/field/CommentedEntity.php new file mode 100644 index 0000000000..9e0d64b23e --- /dev/null +++ b/core/modules/comment/src/Plugin/views/field/CommentedEntity.php @@ -0,0 +1,48 @@ +loadedCommentedEntities)) { + $result = $this->view->result; + + $entity_ids_per_type = []; + foreach ($result as $value) { + /** @var \Drupal\comment\CommentInterface $comment */ + if ($comment = $this->getEntity($value)) { + $entity_ids_per_type[$comment->getCommentedEntityTypeId()][] = $comment->getCommentedEntityId(); + } + } + + foreach ($entity_ids_per_type as $type => $ids) { + $this->loadedCommentedEntities[$type] = $this->entityManager->getStorage($type)->loadMultiple($ids); + } + } + + return parent::getItems($values); + } + +} diff --git a/core/modules/comment/src/Tests/CommentAdminTest.php b/core/modules/comment/src/Tests/CommentAdminTest.php index 980ad30a26..7c4aae0373 100644 --- a/core/modules/comment/src/Tests/CommentAdminTest.php +++ b/core/modules/comment/src/Tests/CommentAdminTest.php @@ -2,6 +2,9 @@ namespace Drupal\comment\Tests; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Utility\Html; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\user\RoleInterface; use Drupal\comment\Entity\Comment; @@ -90,7 +93,7 @@ public function testApprovalAdminInterface() { ]; $this->drupalPostForm(NULL, $edit, t('Update')); $this->assertText(t('Are you sure you want to delete these comments and all their children?'), 'Confirmation required.'); - $this->drupalPostForm(NULL, $edit, t('Delete comments')); + $this->drupalPostForm(NULL, [], t('Delete')); $this->assertText(t('No comments available.'), 'All comments were deleted.'); // Test message when no comments selected. $edit = [ @@ -98,6 +101,15 @@ public function testApprovalAdminInterface() { ]; $this->drupalPostForm(NULL, $edit, t('Update')); $this->assertText(t('Select one or more comments to perform the update on.')); + + // Make sure the label of unpublished node is not visible on listing page. + $this->drupalGet('admin/content/comment'); + $this->postComment($this->node, $this->randomMachineName()); + $this->drupalGet('admin/content/comment'); + $this->assertText(Html::escape($this->node->label())); + $this->node->setUnpublished()->save(); + $this->drupalGet('admin/content/comment'); + $this->assertNoText(Html::escape($this->node->label())); } /** @@ -215,4 +227,51 @@ public function testEditComment() { $this->assertFieldById('edit-mail', $anonymous_comment->getAuthorEmail()); } + /** + * Tests commented translation deletion admin view. + */ + public function testCommentedTranslationDeletion() { + \Drupal::service('module_installer')->install([ + 'language', + 'locale', + ]); + \Drupal::service('router.builder')->rebuildIfNeeded(); + + ConfigurableLanguage::createFromLangcode('ur')->save(); + // Rebuild the container to update the default language container variable. + $this->rebuildContainer(); + // Ensure that doesn't require contact info. + $this->setCommentAnonymous('0'); + $this->drupalLogin($this->webUser); + $count_query = \Drupal::entityTypeManager() + ->getStorage('comment') + ->getQuery() + ->count(); + $before_count = $count_query->execute(); + // Post 2 anonymous comments without contact info. + $comment1 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE); + $comment2 = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE); + + $comment1->addTranslation('ur', ['subject' => 'ur ' . $comment1->label()]) + ->save(); + $comment2->addTranslation('ur', ['subject' => 'ur ' . $comment1->label()]) + ->save(); + $this->drupalLogout(); + $this->drupalLogin($this->adminUser); + // Delete multiple comments in one operation. + $edit = [ + 'operation' => 'delete', + "comments[{$comment1->id()}]" => 1, + "comments[{$comment2->id()}]" => 1, + ]; + $this->drupalPostForm('admin/content/comment', $edit, t('Update')); + $this->assertRaw(new FormattableMarkup('@label (Original translation) - The following comment translations will be deleted:', ['@label' => $comment1->label()])); + $this->assertRaw(new FormattableMarkup('@label (Original translation) - The following comment translations will be deleted:', ['@label' => $comment2->label()])); + $this->assertText('English'); + $this->assertText('Urdu'); + $this->drupalPostForm(NULL, [], t('Delete')); + $after_count = $count_query->execute(); + $this->assertEqual($after_count, $before_count, 'No comment or translation found.'); + } + } diff --git a/core/modules/comment/src/Tests/CommentNonNodeTest.php b/core/modules/comment/src/Tests/CommentNonNodeTest.php index 3c285d8f1c..c75da84f62 100644 --- a/core/modules/comment/src/Tests/CommentNonNodeTest.php +++ b/core/modules/comment/src/Tests/CommentNonNodeTest.php @@ -222,7 +222,7 @@ public function performCommentOperation($comment, $operation, $approval = FALSE) $this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update')); if ($operation == 'delete') { - $this->drupalPostForm(NULL, [], t('Delete comments')); + $this->drupalPostForm(NULL, [], t('Delete')); $this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', ['@operation' => $operation])); } else { diff --git a/core/modules/comment/src/Tests/CommentTestBase.php b/core/modules/comment/src/Tests/CommentTestBase.php index c42d9883c6..e15915ff93 100644 --- a/core/modules/comment/src/Tests/CommentTestBase.php +++ b/core/modules/comment/src/Tests/CommentTestBase.php @@ -354,7 +354,7 @@ public function performCommentOperation(CommentInterface $comment, $operation, $ $this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update')); if ($operation == 'delete') { - $this->drupalPostForm(NULL, [], t('Delete comments')); + $this->drupalPostForm(NULL, [], t('Delete')); $this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', ['@operation' => $operation])); } else { diff --git a/core/modules/comment/src/Tests/Update/CommentAdminViewUpdateTest.php b/core/modules/comment/src/Tests/Update/CommentAdminViewUpdateTest.php new file mode 100644 index 0000000000..87f8f23a09 --- /dev/null +++ b/core/modules/comment/src/Tests/Update/CommentAdminViewUpdateTest.php @@ -0,0 +1,51 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Tests that comment admin view is enabled after update. + */ + public function testCommentAdminPostUpdateHook() { + $this->runUpdates(); + // Ensure we can load the view from the storage after the update and it's + // enabled. + $entity_type_manager = \Drupal::entityTypeManager(); + /** @var \Drupal\views\ViewEntityInterface $comment_admin_view */ + $comment_admin_view = $entity_type_manager->getStorage('view')->load('comment'); + $this->assertNotNull($comment_admin_view, 'Comment admin view exist in storage.'); + $this->assertTrue($comment_admin_view->enable(), 'Comment admin view is enabled.'); + $comment_delete_action = $entity_type_manager->getStorage('action')->load('comment_delete_action'); + $this->assertNotNull($comment_delete_action, 'Comment delete action imported'); + // Verify comment admin page is working after updates. + $account = $this->drupalCreateUser(['administer comments']); + $this->drupalLogin($account); + $this->drupalGet('admin/content/comment'); + $this->assertText(t('No comments available.')); + } + +} diff --git a/core/modules/comment/src/Tests/Views/CommentAdminTest.php b/core/modules/comment/src/Tests/Views/CommentAdminTest.php new file mode 100644 index 0000000000..50ac0c919e --- /dev/null +++ b/core/modules/comment/src/Tests/Views/CommentAdminTest.php @@ -0,0 +1,211 @@ +install(['views']); + $view = Views::getView('comment'); + $view->storage->enable()->save(); + \Drupal::service('router.builder')->rebuildIfNeeded(); + } + + /** + * Test comment approval functionality through admin/content/comment. + */ + public function testApprovalAdminInterface() { + // Set anonymous comments to require approval. + user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [ + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => FALSE, + ]); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->adminUser); + // Ensure that doesn't require contact info. + $this->setCommentAnonymous('0'); + + // Test that the comments page loads correctly when there are no comments. + $this->drupalGet('admin/content/comment'); + $this->assertText(t('No comments available.')); + + $this->drupalLogout(); + + // Post anonymous comment without contact info. + $body = $this->getRandomGenerator()->sentences(4); + $subject = Unicode::truncate(trim(Html::decodeEntities(strip_tags($body))), 29, TRUE, TRUE); + $author_name = $this->randomMachineName(); + $this->drupalPostForm('comment/reply/node/' . $this->node->id() . '/comment', [ + 'name' => $author_name, + 'comment_body[0][value]' => $body, + ], t('Save')); + $this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), 'Comment requires approval.'); + + // Get unapproved comment id. + $this->drupalLogin($this->adminUser); + $anonymous_comment4 = $this->getUnapprovedComment($subject); + $anonymous_comment4 = Comment::create([ + 'cid' => $anonymous_comment4, + 'subject' => $subject, + 'comment_body' => $body, + 'entity_id' => $this->node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', + ]); + $this->drupalLogout(); + + $this->assertFalse($this->commentExists($anonymous_comment4), 'Anonymous comment was not published.'); + + // Approve comment. + $this->drupalLogin($this->adminUser); + $edit = []; + $edit['action'] = 'comment_publish_action'; + $edit['comment_bulk_form[0]'] = $anonymous_comment4->id(); + $this->drupalPostForm('admin/content/comment/approval', $edit, t('Apply to selected items')); + + $this->assertText('Publish comment was applied to 1 item.', new FormattableMarkup('Operation "@operation" was performed on comment.', ['@operation' => 'publish'])); + $this->drupalLogout(); + + $this->drupalGet('node/' . $this->node->id()); + $this->assertTrue($this->commentExists($anonymous_comment4), 'Anonymous comment visible.'); + + // Post 2 anonymous comments without contact info. + $comments[] = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE); + $comments[] = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE); + + // Publish multiple comments in one operation. + $this->drupalLogin($this->adminUser); + $this->drupalGet('admin/content/comment/approval'); + $this->assertText(t('Unapproved comments (@count)', ['@count' => 2]), 'Two unapproved comments waiting for approval.'); + $edit = [ + "action" => 'comment_publish_action', + "comment_bulk_form[1]" => $comments[0]->id(), + "comment_bulk_form[0]" => $comments[1]->id(), + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + $this->assertText(t('Unapproved comments (@count)', ['@count' => 0]), 'All comments were approved.'); + + // Test message when no comments selected. + $this->drupalPostForm('admin/content/comment', [], t('Apply to selected items')); + $this->assertText(t('Select one or more comments to perform the update on.')); + + $subject_link = $this->xpath('//table/tbody/tr/td/a[contains(@href, :href) and contains(@title, :title) and text()=:text]', [ + ':href' => $comments[0]->permalink()->toString(), + ':title' => Unicode::truncate($comments[0]->get('comment_body')->value, 128), + ':text' => $comments[0]->getSubject(), + ]); + $this->assertTrue(!empty($subject_link), 'Comment listing shows the correct subject link.'); + $this->assertText($author_name . ' (not verified)', 'Anonymous author name is displayed correctly.'); + + $subject_link = $this->xpath('//table/tbody/tr/td/a[contains(@href, :href) and contains(@title, :title) and text()=:text]', [ + ':href' => $anonymous_comment4->permalink()->toString(), + ':title' => Unicode::truncate($body, 128), + ':text' => $subject, + ]); + $this->assertTrue(!empty($subject_link), 'Comment listing shows the correct subject link.'); + $this->assertText($author_name . ' (not verified)', 'Anonymous author name is displayed correctly.'); + + // Delete multiple comments in one operation. + $edit = [ + 'action' => 'comment_delete_action', + "comment_bulk_form[1]" => $comments[0]->id(), + "comment_bulk_form[0]" => $comments[1]->id(), + "comment_bulk_form[2]" => $anonymous_comment4->id(), + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + $this->assertText(t('Are you sure you want to delete these comments and all their children?'), 'Confirmation required.'); + $this->drupalPostForm(NULL, [], t('Delete')); + $this->assertText(t('No comments available.'), 'All comments were deleted.'); + + // Make sure the label of unpublished node is not visible on listing page. + $this->drupalGet('admin/content/comment'); + $this->postComment($this->node, $this->randomMachineName()); + $this->drupalLogout(); + $this->drupalLogin($this->adminUser); + $this->drupalGet('admin/content/comment'); + $this->assertText(Html::escape($this->node->label()), 'Comment admin can see the title of a published node'); + $this->node->setUnpublished()->save(); + $this->assertFalse($this->node->isPublished(), 'Node is unpublished now.'); + $this->drupalGet('admin/content/comment'); + $this->assertNoText(Html::escape($this->node->label()), 'Comment admin cannot see the title of an unpublished node'); + $this->drupalLogout(); + $node_access_user = $this->drupalCreateUser([ + 'administer comments', + 'bypass node access', + ]); + $this->drupalLogin($node_access_user); + $this->drupalGet('admin/content/comment'); + $this->assertText(Html::escape($this->node->label()), 'Comment admin with bypass node access permissions can still see the title of a published node'); + } + + /** + * Tests commented entity label of admin view. + */ + public function testCommentedEntityLabel() { + \Drupal::service('module_installer')->install(['block_content']); + \Drupal::service('router.builder')->rebuildIfNeeded(); + $bundle = BlockContentType::create([ + 'id' => 'basic', + 'label' => 'basic', + 'revision' => FALSE, + ]); + $bundle->save(); + $block_content = BlockContent::create([ + 'type' => 'basic', + 'label' => 'Some block title', + 'info' => 'Test block', + ]); + $block_content->save(); + + // Create comment field on block_content. + $this->addDefaultCommentField('block_content', 'basic', 'block_comment', CommentItemInterface::OPEN, 'block_comment'); + $this->drupalLogin($this->webUser); + // Post a comment to node. + $node_comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE); + // Post a comment to block content. + $block_content_comment = $this->postComment($block_content, $this->randomMachineName(), $this->randomMachineName(), TRUE, 'block_comment'); + $this->drupalLogout(); + // Login as admin to test the admin comment page. + $this->drupalLogin($this->adminUser); + $this->drupalGet('admin/content/comment'); + + $comment_author_link = $this->xpath('//table/tbody/tr[1]/td/a[contains(@href, :href) and text()=:text]', [ + ':href' => $this->webUser->toUrl()->toString(), + ':text' => $this->webUser->label(), + ]); + $this->assertTrue(!empty($comment_author_link), 'Comment listing links to comment author.'); + $comment_author_link = $this->xpath('//table/tbody/tr[2]/td/a[contains(@href, :href) and text()=:text]', [ + ':href' => $this->webUser->toUrl()->toString(), + ':text' => $this->webUser->label(), + ]); + $this->assertTrue(!empty($comment_author_link), 'Comment listing links to comment author.'); + // Admin page contains label of both entities. + $this->assertText(Html::escape($this->node->label()), 'Node title is visible.'); + $this->assertText(Html::escape($block_content->label()), 'Block content label is visible.'); + // Admin page contains subject of both entities. + $this->assertText(Html::escape($node_comment->label()), 'Node comment is visible.'); + $this->assertText(Html::escape($block_content_comment->label()), 'Block content comment is visible.'); + } + +} diff --git a/core/modules/comment/src/Tests/Views/CommentEditTest.php b/core/modules/comment/src/Tests/Views/CommentEditTest.php new file mode 100644 index 0000000000..2a11ef9628 --- /dev/null +++ b/core/modules/comment/src/Tests/Views/CommentEditTest.php @@ -0,0 +1,37 @@ +drupalLogin($this->adminUser); + // Post a comment to node. + $node_comment = $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName(), TRUE); + $this->drupalGet('admin/content/comment'); + $this->assertText($this->adminUser->label()); + $this->drupalGet($node_comment->toUrl('edit-form')->toString()); + $edit = [ + 'comment_body[0][value]' => $this->randomMachineName(), + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->drupalGet('admin/content/comment'); + $this->assertText($this->adminUser->label()); + } + +} diff --git a/core/modules/comment/tests/src/Unit/Plugin/views/field/CommentBulkFormTest.php b/core/modules/comment/tests/src/Unit/Plugin/views/field/CommentBulkFormTest.php new file mode 100644 index 0000000000..4e86bb7ef0 --- /dev/null +++ b/core/modules/comment/tests/src/Unit/Plugin/views/field/CommentBulkFormTest.php @@ -0,0 +1,93 @@ +getMock('\Drupal\system\ActionConfigEntityInterface'); + $action->expects($this->any()) + ->method('getType') + ->will($this->returnValue('comment')); + $actions[$i] = $action; + } + + $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface'); + $action->expects($this->any()) + ->method('getType') + ->will($this->returnValue('user')); + $actions[] = $action; + + $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $entity_storage->expects($this->any()) + ->method('loadMultiple') + ->will($this->returnValue($actions)); + + $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $entity_manager->expects($this->once()) + ->method('getStorage') + ->with('action') + ->will($this->returnValue($entity_storage)); + + $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + + $views_data = $this->getMockBuilder('Drupal\views\ViewsData') + ->disableOriginalConstructor() + ->getMock(); + $views_data->expects($this->any()) + ->method('get') + ->with('comment') + ->will($this->returnValue(['table' => ['entity type' => 'comment']])); + $container = new ContainerBuilder(); + $container->set('views.views_data', $views_data); + $container->set('string_translation', $this->getStringTranslationStub()); + \Drupal::setContainer($container); + + $storage = $this->getMock('Drupal\views\ViewEntityInterface'); + $storage->expects($this->any()) + ->method('get') + ->with('base_table') + ->will($this->returnValue('comment')); + + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $executable->storage = $storage; + + $display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase') + ->disableOriginalConstructor() + ->getMock(); + + $definition['title'] = ''; + $options = []; + + $comment_bulk_form = new CommentBulkForm([], 'comment_bulk_form', $definition, $entity_manager, $language_manager); + $comment_bulk_form->init($executable, $display, $options); + + $this->assertAttributeEquals(array_slice($actions, 0, -1, TRUE), 'actions', $comment_bulk_form); + } + +} diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php index 5299f38e24..1c4b883a35 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php @@ -57,7 +57,7 @@ protected function getEntityCounts() { 'search_page' => 2, 'shortcut' => 2, 'shortcut_set' => 1, - 'action' => 22, + 'action' => 23, 'menu' => 8, 'taxonomy_term' => 7, 'taxonomy_vocabulary' => 6, @@ -65,7 +65,7 @@ protected function getEntityCounts() { 'user' => 7, 'user_role' => 6, 'menu_link_content' => 4, - 'view' => 15, + 'view' => 16, 'date_format' => 11, 'entity_form_display' => 19, 'entity_form_mode' => 1, diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php index e1e70bd5d8..248213ad45 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php @@ -63,7 +63,7 @@ protected function getEntityCounts() { 'search_page' => 2, 'shortcut' => 6, 'shortcut_set' => 2, - 'action' => 16, + 'action' => 17, 'menu' => 6, 'taxonomy_term' => 18, 'taxonomy_vocabulary' => 4, @@ -71,7 +71,7 @@ protected function getEntityCounts() { 'user' => 4, 'user_role' => 3, 'menu_link_content' => 7, - 'view' => 15, + 'view' => 16, 'date_format' => 11, 'entity_form_display' => 18, 'entity_form_mode' => 1, diff --git a/core/modules/views/tests/src/Functional/DefaultViewsTest.php b/core/modules/views/tests/src/Functional/DefaultViewsTest.php index 0e5c893442..3e7d0b48b2 100644 --- a/core/modules/views/tests/src/Functional/DefaultViewsTest.php +++ b/core/modules/views/tests/src/Functional/DefaultViewsTest.php @@ -100,6 +100,15 @@ protected function setUp($import_test_views = TRUE) { 'field_name' => 'comment' ]; Comment::create($comment)->save(); + + $unpublished_comment = [ + 'uid' => $user->id(), + 'status' => CommentInterface::NOT_PUBLISHED, + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', + ]; + Comment::create($unpublished_comment)->save(); } // Some views, such as the "Who's Online" view, only return results if at