diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc old mode 100644 new mode 100755 diff --git a/core/modules/comment/comment-entity-form.js b/core/modules/comment/comment-entity-form.js new file mode 100644 index 0000000..e068ef5 --- /dev/null +++ b/core/modules/comment/comment-entity-form.js @@ -0,0 +1,19 @@ +/** + * @file + * Attaches comment behaviors to the entity form. + */ + +(function ($) { + +"use strict"; + +Drupal.behaviors.commentFieldsetSummaries = { + attach: function (context) { + var $context = $(context); + $context.find('fieldset.comment-entity-settings-form').drupalSetSummary(function (context) { + return Drupal.checkPlain($(context).find('.form-item-comment input:checked').next('label').text()); + }); + } +}; + +})(jQuery); diff --git a/core/modules/comment/comment-node-form.js b/core/modules/comment/comment-node-form.js deleted file mode 100644 index 67800da..0000000 --- a/core/modules/comment/comment-node-form.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @file - * Attaches comment behaviors to the node form. - */ - -(function ($) { - -"use strict"; - -Drupal.behaviors.commentDetailsSummaries = { - attach: function (context) { - var $context = $(context); - $context.find('.comment-node-settings-form').drupalSetSummary(function (context) { - return Drupal.checkPlain($(context).find('.form-item-comment input:checked').next('label').text()); - }); - - // Provide the summary for the node type form. - $context.find('.comment-node-type-settings-form').drupalSetSummary(function(context) { - var $context = $(context); - var vals = []; - - // Default comment setting. - vals.push($context.find(".form-item-comment select option:selected").text()); - - // Threading. - var threading = $(context).find(".form-item-comment-default-mode input:checked").next('label').text(); - if (threading) { - vals.push(threading); - } - - // Comments per page. - var number = $context.find(".form-item-comment-default-per-page select option:selected").val(); - vals.push(Drupal.t('@number comments per page', {'@number': number})); - - return Drupal.checkPlain(vals.join(', ')); - }); - } -}; - -})(jQuery); diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc index 4bbff56..79c4913 100644 --- a/core/modules/comment/comment.admin.inc +++ b/core/modules/comment/comment.admin.inc @@ -82,56 +82,70 @@ function comment_admin_overview($form, &$form_state, $arg) { $query = db_select('comment', 'c') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->extend('Drupal\Core\Database\Query\TableSortExtender'); - $query->join('node_field_data', 'n', 'n.nid = c.nid'); - $query->addTag('node_access'); + if (Drupal::moduleHandler()->moduleExists('node')) { + // Special case to ensure node access works. + $query->leftJoin('node_field_data', 'n', "n.nid = c.entity_id AND c.entity_type = 'node'"); + $query->addTag('node_access'); + } $result = $query - ->fields('c', array('cid', 'nid', 'subject', 'name', 'changed')) + ->fields('c', array('cid', 'subject', 'name', 'changed', 'entity_id', 'entity_type', 'field_id')) ->condition('c.status', $status) ->limit(50) ->orderByHeader($header) ->execute(); - $nids = array(); $cids = array(); + $entity_ids = array(); + $entities = array(); - // We collect a sorted list of node_titles during the query to attach to the - // comments later. + // We collect entities grouped by entity_type so we can load them and use + // their labels. foreach ($result as $row) { - $nids[] = $row->nid; + $entity_ids[$row->entity_type][] = $row->entity_id; $cids[] = $row->cid; } + foreach ($entity_ids as $entity_type => $ids) { + $entities[$entity_type] = entity_load_multiple($entity_type, $ids); + } // Ensure all nodes are statically cached so that we do not have to load them // individually when getting their labels below. - node_load_multiple($nids); - $comments = comment_load_multiple($cids); + $comments = entity_load_multiple('comment', $cids); // Build a table listing the appropriate comments. $options = array(); $destination = drupal_get_destination(); foreach ($comments as $comment) { + // Use the first entity label. + $entity = $entities[$comment->entity_type->value][$comment->entity_id->value]; + $entity_uri = $entity->uri(); // Remove the first node title from the node_titles array and attach to // the comment. - $node_title = $comment->nid->entity->label(); $username = array( '#theme' => 'username', '#account' => comment_prepare_author($comment), ); + $body = ''; + if (!empty($comment->comment_body->value)) { + $body = $comment->comment_body->value; + } $options[$comment->id()] = array( 'subject' => array( 'data' => array( '#type' => 'link', '#title' => $comment->subject->value, '#href' => 'comment/' . $comment->id(), - '#options' => array('attributes' => array('title' => truncate_utf8($comment->comment_body->value, 128)), 'fragment' => 'comment-' . $comment->id()), + '#options' => array('attributes' => array('title' => truncate_utf8($body, 128)), 'fragment' => 'comment-' . $comment->id()), ), ), 'author' => drupal_render($username), 'posted_in' => array( 'data' => array( '#type' => 'link', - '#title' => $node_title, - '#href' => 'node/' . $comment->nid->target_id, + '#title' => $entity->label(), + '#href' => $entity_uri['path'], + '#options' => $entity_uri['options'], + '#access' => $entity->access('view'), ), ), 'changed' => format_date($comment->changed->value, 'short'), diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php index 078401e..41e8a0c 100644 --- a/core/modules/comment/comment.api.php +++ b/core/modules/comment/comment.api.php @@ -34,7 +34,9 @@ function hook_comment_presave(Drupal\comment\Comment $comment) { */ function hook_comment_insert(Drupal\comment\Comment $comment) { // Reindex the node when comments are added. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } /** @@ -45,7 +47,9 @@ function hook_comment_insert(Drupal\comment\Comment $comment) { */ function hook_comment_update(Drupal\comment\Comment $comment) { // Reindex the node when comments are updated. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } /** diff --git a/core/modules/comment/comment.info.yml b/core/modules/comment/comment.info.yml index 8fb8482..e2f0394 100644 --- a/core/modules/comment/comment.info.yml +++ b/core/modules/comment/comment.info.yml @@ -6,6 +6,5 @@ version: VERSION core: 8.x dependencies: - datetime - - node - text configure: admin/content/comment diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index 04e1291..911b770 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -5,31 +5,33 @@ * Install, update and uninstall functions for the Comment module. */ +use Drupal\Core\Language\Language; +use Drupal\comment\Plugin\field\field_type\CommentItem; +use Drupal\field\Entity\Field; + /** * Implements hook_uninstall(). */ function comment_uninstall() { - // Remove variables. - variable_del('comment_block_count'); - $node_types = array_keys(node_type_get_types()); + // @todo Remove when disabled modules go away. Drupal::entityManager()->addNamespaces(new ArrayIterator(array( 'Drupal\comment' => DRUPAL_ROOT . '/core/modules/comment/lib', ))); drupal_classloader_register('comment', 'core/modules/comment'); - foreach ($node_types as $node_type) { - entity_invoke_bundle_hook('delete', 'comment', 'comment_node_' . $node_type); - variable_del('comment_' . $node_type); - variable_del('comment_anonymous_' . $node_type); - variable_del('comment_controls_' . $node_type); - variable_del('comment_default_mode_' . $node_type); - variable_del('comment_default_order_' . $node_type); - variable_del('comment_default_per_page_' . $node_type); - variable_del('comment_form_location_' . $node_type); - variable_del('comment_preview_' . $node_type); - variable_del('comment_subject_field_' . $node_type); + entity_info_cache_clear(); + + // Remove the comment fields. + $fields = entity_load_multiple_by_properties('field_entity', array( + 'type' => 'comment', + 'include_inactive' => TRUE, + 'include_deleted' => FALSE, + )); + foreach ($fields as $field) { + entity_invoke_bundle_hook('delete', 'comment', $field->entity_type . '__' . $field->name); + $field->delete(); } - // Remove states. + // Remove state setting. Drupal::state()->delete('comment.node_comment_statistics_scale'); } @@ -37,47 +39,80 @@ function comment_uninstall() { * Implements hook_enable(). */ function comment_enable() { - // Insert records into the node_comment_statistics for nodes that are missing. - $query = db_select('node_field_data', 'n'); - $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid AND n.default_langcode = 1'); - $query->addField('n', 'created', 'last_comment_timestamp'); - $query->addField('n', 'uid', 'last_comment_uid'); - $query->addField('n', 'nid'); - $query->addExpression('0', 'comment_count'); - $query->addExpression('NULL', 'last_comment_name'); - $query->isNull('ncs.comment_count'); - - db_insert('node_comment_statistics') - ->from($query) - ->execute(); + $comment_fields = _update_comment_get_comment_fields(); + $entity_info = Drupal::service('entity.manager')->getDefinitions(); + foreach ($comment_fields as $field_name => $info) { + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $entity_detail = $entity_info[$entity_type]; + if (!empty($entity_detail['base_table']) && + !empty($entity_detail['entity_keys']['id'])) { + $table = $entity_detail['base_table']; + $schema = drupal_get_schema($table); + // Insert records into the comment_entity_statistics for entities that + // are missing. + $query = db_select($table, 'e'); + // Filter by bundle. + $query->condition($entity_detail['entity_keys']['bundle'], $bundle); + $query->leftJoin('comment_entity_statistics', 'ces', 'ces.entity_id = e.' . $entity_detail['entity_keys']['id'] . " AND ces.entity_type = '$entity_type'"); + if (!empty($schema[$table]['fields']['created'])) { + $query->addField('e', 'created', 'last_comment_timestamp'); + } + else { + // No created field for this entity type, default to now. + $query->addExpression(REQUEST_TIME, 'last_comment_timestamp'); + } + if (!empty($schema[$table]['fields']['uid'])) { + $query->addField('e', 'uid', 'last_comment_uid'); + } + else { + // No uid field for this entity type, default to anonymous. + $query->addExpression(0, 'last_comment_uid'); + } + $query->addField('e', $entity_detail['entity_keys']['id'], 'entity_id'); + $query->addExpression("'$entity_type'", 'entity_type'); + $query->addExpression("'$entity_type.$field_name'", 'field_id'); + $query->addExpression('0', 'comment_count'); + $query->addExpression('NULL', 'last_comment_name'); + $query->isNull('ces.comment_count'); + + db_insert('comment_entity_statistics') + ->from($query) + ->execute(); + } + } + } + } + // Set default value of comment.maintain_entity_statistics. + Drupal::state()->set('comment.maintain_entity_statistics', TRUE); } /** - * Implements hook_modules_enabled(). + * Returns comment fields. * - * Creates comment body fields for node types existing before the Comment module - * is enabled. We use hook_modules_enabled() rather than hook_enable() so we can - * react to node types of existing modules, and those of modules being enabled - * both before and after the Comment module in the loop of module_enable(). + * This function similar to CommentManager::getFields() that cannot be used in + * maintenance mode because comment.module is disabled and it's fields inactive. * - * There is a separate comment bundle for each node type to allow for - * per-node-type customization of comment fields. Each one of these bundles - * needs a comment body field instance. A comment bundle is needed even for - * node types whose comments are disabled by default, because individual nodes - * may override that default. - * - * @see comment_node_type_insert() + * @return + * An array of comment fields keyed by field ID. */ -function comment_modules_enabled($modules) { - // Only react if the Comment module is one of the modules being enabled. - // hook_node_type_insert() is used to create body fields while the comment - // module is enabled. - if (in_array('comment', $modules)) { - // Create comment body fields for each node type, if needed. - foreach (node_type_get_types() as $type => $info) { - _comment_body_field_create($info); +function _update_comment_get_comment_fields() { + $fields = entity_load_multiple_by_properties('field_entity', array( + 'type' => 'comment', + 'include_inactive' => TRUE, + 'include_deleted' => FALSE, + )); + foreach ($fields as &$field) { + $instances = entity_load_multiple_by_properties('field_instance', array( + 'field_uuid' => $field->uuid, + 'include_inactive' => TRUE, + 'include_deleted' => FALSE, + )); + foreach ($instances as $instance) { + $field->bundles[$instance->entity_type] = $instance->bundle; } } + return $fields; } /** @@ -104,12 +139,26 @@ function comment_schema() { 'default' => 0, 'description' => 'The {comment}.cid to which this comment is a reply. If set to 0, this comment is not a reply to an existing comment.', ), - 'nid' => array( + 'entity_id' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, - 'description' => 'The {node}.nid to which this comment is a reply.', + 'description' => 'The entity_id to which this comment is a reply.', + ), + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node', + 'length' => 255, + 'description' => 'The entity_type to which this comment is a reply.', + ), + 'field_id' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node.comment', + 'length' => 255, + 'description' => 'The field_id through which this comment was added.', ), 'uid' => array( 'type' => 'int', @@ -186,9 +235,22 @@ function comment_schema() { ), 'indexes' => array( 'comment_status_pid' => array('pid', 'status'), - 'comment_num_new' => array('nid', 'status', 'created', 'cid', 'thread'), + 'comment_num_new' => array( + 'entity_id', + array('entity_type', 32), + array('field_id', 32), + 'status', + 'created', + 'cid', + 'thread', + ), 'comment_uid' => array('uid'), - 'comment_nid_langcode' => array('nid', 'langcode'), + 'comment_entity_langcode' => array( + 'entity_id', + array('entity_type', 32), + array('field_id', 32), + 'langcode', + ), 'comment_created' => array('created'), ), 'primary key' => array('cid'), @@ -196,10 +258,6 @@ function comment_schema() { 'uuid' => array('uuid'), ), 'foreign keys' => array( - 'comment_node' => array( - 'table' => 'node', - 'columns' => array('nid' => 'nid'), - ), 'comment_author' => array( 'table' => 'users', 'columns' => array('uid' => 'uid'), @@ -207,15 +265,29 @@ function comment_schema() { ), ); - $schema['node_comment_statistics'] = array( - 'description' => 'Maintains statistics of node and comments posts to show "new" and "updated" flags.', + $schema['comment_entity_statistics'] = array( + 'description' => 'Maintains statistics of entity and comments posts to show "new" and "updated" flags.', 'fields' => array( - 'nid' => array( + 'entity_id' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, - 'description' => 'The {node}.nid for which the statistics are compiled.', + 'description' => 'The entity_id for which the statistics are compiled.', + ), + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node', + 'length' => 255, + 'description' => 'The entity_type to which this comment is a reply.', + ), + 'field_id' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node.comment', + 'length' => 255, + 'description' => 'The field_id through which this comment was added.', ), 'cid' => array( 'type' => 'int', @@ -247,20 +319,16 @@ function comment_schema() { 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, - 'description' => 'The total number of comments on this node.', + 'description' => 'The total number of comments on this entity.', ), ), - 'primary key' => array('nid'), + 'primary key' => array('entity_id', array('entity_type', 32), array('field_id', 32)), 'indexes' => array( - 'node_comment_timestamp' => array('last_comment_timestamp'), + 'last_comment_timestamp' => array('last_comment_timestamp'), 'comment_count' => array('comment_count'), 'last_comment_uid' => array('last_comment_uid'), ), 'foreign keys' => array( - 'statistics_node' => array( - 'table' => 'node', - 'columns' => array('nid' => 'nid'), - ), 'last_comment_author' => array( 'table' => 'users', 'columns' => array( @@ -279,6 +347,16 @@ function comment_schema() { */ /** + * Implements hook_update_dependencies(). + */ +function comment_update_dependencies() { + // Node comment status have been turned to fields after the fields and + // instances are converted to ConfigEntities. + $dependencies['comment'][8006]['field'] = 8003; + return $dependencies; +} + +/** * Renames {comment}.language to {comment}.langcode. */ function comment_update_8000() { @@ -388,7 +466,325 @@ function comment_update_8003(&$sandbox) { function comment_update_8004() { update_variables_to_state(array( 'node_cron_comments_scale' => 'comment.node_comment_statistics_scale', + 'comment_maintain_node_statistics' => 'comment.maintain_entity_statistics', + )); +} + +/** + * Updates the {comment_node_statistics} and {comment} tables to new structure. + */ +function comment_update_8005(&$sandbox) { + // Drop old indexes. + if (db_index_exists('comment', 'comment_node')) { + // Drop the comment_node foreign key. + db_drop_index('comment', 'comment_node'); + } + db_drop_index('comment', 'comment_num_new'); + db_drop_index('comment', 'comment_nid_langcode'); + + // Add the entity_type and field id columns to comment. + db_add_field('comment', 'entity_type', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node', + 'length' => 255, + 'description' => 'The entity_type to which this comment is a reply.', + )); + db_add_field('comment', 'field_id', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node__comment', + 'length' => 255, + 'description' => 'The field_id to which this comment is a reply.', + )); + // Rename the nid column to entity_id. + db_change_field('comment', 'nid', 'entity_id', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The entity_id to which this comment is a reply.', + )); + db_add_index('comment', 'comment_num_new', array( + 'entity_id', + array('entity_type', 32), + array('field_id', 32), + 'status', + 'created', + 'cid', + 'thread' + )); + // Add the comment_entity_langcode index. + db_add_index('comment', 'comment_entity_langcode', array( + 'entity_id', + array('entity_type', 32), + array('field_id', 32), + 'langcode', )); + + // We need to drop all indexes to make sure their constrains named properly. + db_drop_primary_key('node_comment_statistics'); + if (db_index_exists('node_comment_statistics', 'statistics_node')) { + // Drop the statistics_node foreign key. + db_drop_index('node_comment_statistics', 'statistics_node'); + } + db_drop_index('node_comment_statistics', 'node_comment_timestamp'); + db_drop_index('node_comment_statistics', 'comment_count'); + db_drop_index('node_comment_statistics', 'last_comment_uid'); + + // Rename {node_comment_statistics} to {comment_entity_statistics}. + db_rename_table('node_comment_statistics', 'comment_entity_statistics'); + + // Add the entity_type and field id columns to comment_entity_statistics. + db_add_field('comment_entity_statistics', 'entity_type', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node', + 'length' => 255, + 'description' => 'The entity_type to which this comment is a reply.', + )); + db_add_field('comment_entity_statistics', 'field_id', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node__comment', + 'length' => 255, + 'description' => 'The field_id to which this comment is a reply.', + )); + // Rename the nid column in entity_comment_statistics to entity_id. + db_change_field('comment_entity_statistics', 'nid', 'entity_id', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The entity_id to which this comment is a reply.', + )); + + // Add indexes. + db_add_primary_key('comment_entity_statistics', array('entity_id', array('entity_type', 32), array('field_id',32))); + db_add_index('comment_entity_statistics', 'last_comment_timestamp', array('last_comment_timestamp')); + db_add_index('comment_entity_statistics', 'comment_count', array('comment_count')); + db_add_index('comment_entity_statistics', 'last_comment_uid', array('last_comment_uid')); +} + +/** + * Adds comment fields for all node types. + * + * Field instance settings "default_mode", "per_page" and "form_location" are + * preserved to allow migrate contrib modules. + */ +function comment_update_8006(&$sandbox) { + // Entity module update functions are needed to update components of node + // entity display and form display for new comment fields. + module_load_install('entity'); + // Loop over defined node_types. + $node_types = array_keys(_update_7000_node_get_types()); + foreach ($node_types as $node_type) { + // COMMENT_OPEN + $default_value = update_variable_get('comment_' . $node_type, 2); + // Add a default comment field for existing node comments. + $field_name = 'comment_' . $node_type; + $field = array( + // We need one per content type to match the existing comment bundles. + 'id' => 'node.' . $field_name, + 'type' => 'comment', + 'module' => 'comment', + 'name' => $field_name, + 'entity_type' => 'node', + ); + // Make sure field doesn't already exist. + $index = 0; + + while (!Drupal::config('field.field.node.' . $field['name'])->isNew()) { + // Append a numeric index. + $field['id'] = 'node.' . $field_name . '_' . $index; + $field['name'] = $field_name . '_' . $index; + // Increment index. + $index++; + } + // Comment module may be disabled so we will need to inject the schema to + // avoid attempting to create a Field of an unknown type. + if (!Drupal::moduleHandler()->moduleExists('comment')) { + // We can't use the autoloader because the comment namespace isn't + // registered. + // @todo Remove once https://drupal.org/node/1199946 is in. + require_once __DIR__ . '/lib/Drupal/comment/Plugin/field/field_type/CommentItem.php'; + } + $field_object = new Field($field); + $field['schema'] = CommentItem::schema($field_object); + _update_8003_field_create_field($field); + + if (Drupal::config("field.instance.node.$node_type." . $field['name'])->isNew()) { + // Add the comment field, setting the instance settings to match those for + // the given node_type. + $instance_settings = array( + // COMMENT_MODE_THREADED + 'default_mode' => update_variable_get('comment_default_mode_' . $node_type, 1), + 'per_page' => update_variable_get('comment_default_per_page_' . $node_type, 50), + // COMMENT_FORM_BELOW + 'form_location' => update_variable_get('comment_form_location' . $node_type, 1), + // COMMENT_ANONYMOUS_MAYNOT_CONTACT + 'anonymous' => update_variable_get('comment_anonymous_' . $node_type, 0), + 'subject' => update_variable_get('comment_subject_field_' . $node_type, 1), + // DRUPAL_OPTIONAL + 'preview' => update_variable_get('comment_preview_' . $node_type, 1), + ); + $instance = array( + 'id' => "node.$node_type." . $field['name'], + 'entity_type' => 'node', + 'bundle' => $node_type, + 'default_value' => array(array('status' => $default_value)), + 'deleted' => '0', + 'description' => '', + 'label' => 'Comment settings', + 'required' => TRUE, + 'settings' => $instance_settings, + ); + _update_8003_field_create_instance($field, $instance); + } + + // Prepare defaults for the default and full view modes. + $display_options_default = array( + 'label' => 'hidden', + 'type' => 'comment_default', + 'settings' => array(), + 'weight' => 20, + ); + + // Assign display settings for the 'default' and 'full' view modes. + $display = _update_8000_entity_get_display('node', $node_type, 'default'); + $display->set('content.' . $field['name'], $display_options_default) + ->save(); + + $display = _update_8000_entity_get_display('node', $node_type, 'full'); + $display->set('content.' . $field['name'], $display_options_default) + ->save(); + + // Assign widget settings for the 'default' form mode. + $display_options_default = array( + 'type' => 'comment_default', + 'settings' => array(), + 'weight' => 20, + ); + $display = _update_8000_entity_get_form_display('node', $node_type, 'default'); + $display->set('content.' . $field['name'], $display_options_default) + ->save(); + + // Clean up old variables. + update_variable_del('comment_' . $node_type); + update_variable_del('comment_default_mode_' . $node_type); + update_variable_del('comment_default_per_page_' . $node_type); + update_variable_del('comment_anonymous_' . $node_type); + update_variable_del('comment_subject_field_' . $node_type); + update_variable_del('comment_form_location_' . $node_type); + update_variable_del('comment_preview_' . $node_type); + } +} + +/** + * Updates existing values. + */ +function comment_update_8007(&$sandbox) { + + $types = array_keys(_update_7000_node_get_types()); + // Load each node type in batch and initialize field values for comment field. + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + $sandbox['current_nid'] = 0; + // We track all node types here. + $sandbox['node_types'] = $types; + // We start with this node type. + $sandbox['node_type'] = array_shift($sandbox['node_types']); + $sandbox['#finished'] = 1; + $sandbox['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField(); + } + + // Set the initial values of comment fields for existing nodes. Note that + // contrib modules will need to handle the upgrade path on their own, as + // they are disabled during core upgrade. + + // Node table will always exist up until here because in 7.x comment + // depends on node. + $nodes = db_select('node', 'n') + ->fields('n', array('nid', 'comment', 'vid', 'langcode')) + ->condition('type', $sandbox['node_type']) + ->condition('nid', $sandbox['current_nid'], '>') + ->range(0, 50) + ->orderBy('nid', 'ASC') + ->execute() + ->fetchAllAssoc('nid'); + + if (count($nodes) > 0) { + $insert = db_insert('node__comment_' . $sandbox['node_type']) + ->fields(array( + 'bundle', + 'entity_id', + 'revision_id', + 'langcode', + 'delta', + 'comment_' . $sandbox['node_type'] . '_status', + )); + $revision = db_insert('node_revision__comment_' . $sandbox['node_type']) + ->fields(array( + 'bundle', + 'entity_id', + 'revision_id', + 'langcode', + 'delta', + 'comment_' . $sandbox['node_type'] . '_status', + )); + + // Update the field name to match the node type. + db_update('comment') + ->fields(array( + 'field_id' => 'node__comment_' . $sandbox['node_type'], + )) + ->condition('entity_id', array_keys($nodes)) + ->execute(); + foreach ($nodes as $nid => $node) { + $insert->values(array( + 'bundle' => $sandbox['node_type'], + 'entity_id' => $nid, + 'revision_id' => $node->vid, + 'langcode' => Language::LANGCODE_NOT_SPECIFIED, + 'delta' => 0, + 'comment_' . $sandbox['node_type'] . '_status' => $node->comment, + )); + $revision->values(array( + 'bundle' => $sandbox['node_type'], + 'entity_id' => $nid, + 'revision_id' => $node->vid, + 'langcode' => Language::LANGCODE_NOT_SPECIFIED, + 'delta' => 0, + 'comment_' . $sandbox['node_type'] . '_status' => $node->comment, + )); + $sandbox['progress']++; + $sandbox['current_nid'] = $nid; + } + $insert->execute(); + $revision->execute(); + // Populate the field name to match the node type. + db_update('comment_entity_statistics') + ->fields(array( + 'field_id' => 'node__comment_' . $sandbox['node_type'], + )) + ->condition('entity_id', array_keys($nodes)) + ->execute(); + } + else { + // Move to the next node type. + $sandbox['node_type'] = array_shift($sandbox['node_types']); + // Reset the current nid pointer. + $sandbox['current_nid'] = 0; + } + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); +} + +/** + * Removes the existing fields. + */ +function comment_update_8008(&$sandbox) { + // Remove the {node}.comment field. + db_drop_field('node', 'comment'); + // Remove the {node_revision}.comment field. + db_drop_field('node_revision', 'comment'); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index c3b0357..309853b 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -4,19 +4,20 @@ * @file * Enables users to comment on published content. * - * When enabled, the Comment module creates a discussion board for each Drupal - * node. Users can post comments to discuss a forum topic, story, collaborative - * book page, etc. + * When enabled, the Comment module creates a field that facilitates a + * discussion board for each Drupal entity to which a comment field is attached. + * Users can post comments to discuss a forum topic, story, collaborative + * book page, user etc. */ -use Drupal\node\NodeTypeInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityChangedInterface; +use Drupal\comment\CommentInterface; +use Drupal\comment\Entity\Comment; use Drupal\entity\Entity\EntityDisplay; +use Drupal\field\FieldInstanceInterface; +use Drupal\field\FieldInterface; use Drupal\file\Entity\File; -use Drupal\Core\Entity\EntityInterface; -use Drupal\node\NodeInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * Comment is awaiting approval. @@ -64,21 +65,39 @@ const COMMENT_FORM_BELOW = 1; /** - * Comments for this node are hidden. + * Comments for this entity are hidden. */ -const COMMENT_NODE_HIDDEN = 0; +const COMMENT_HIDDEN = 0; /** - * Comments for this node are closed. + * Comments for this entity are closed. */ -const COMMENT_NODE_CLOSED = 1; +const COMMENT_CLOSED = 1; /** - * Comments for this node are open. + * Comments for this entity are open. */ -const COMMENT_NODE_OPEN = 2; +const COMMENT_OPEN = 2; -use Drupal\comment\Entity\Comment; +/** + * Denotes that access is denied for an entity to which a comment field is + * attached and no {%entity_type}_access function exists. + * + * Modules should return this value from hook_comment_access() to deny + * access to commenting on an entity + */ +const COMMENT_ACCESS_DENY = 'deny'; + +/** + * The time cutoff for comments marked as read for entity types other node. + * + * Comments changed before this time are always marked as read. + * Comments changed after this time may be marked new, updated, or read, + * depending on their state for the current user. Defaults to 30 days ago. + * + * @todo Remove when http://drupal.org/node/1029708 lands. + */ +define('COMMENT_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60); /** * Implements hook_help(). @@ -91,7 +110,7 @@ function comment_help($path, $arg) { $output .= '

' . t('Uses') . '

'; $output .= '
'; $output .= '
' . t('Default and custom settings') . '
'; - $output .= '
' . t("Each content type can have its own default comment settings configured as: Open to allow new comments, Hidden to hide existing comments and prevent new comments, or Closed to view existing comments, but prevent new comments. These defaults will apply to all new content created (changes to the settings on existing content must be done manually). Other comment settings can also be customized per content type, and can be overridden for any given item of content. When a comment has no replies, it remains editable by its author, as long as the author has a user account and is logged in.", array('@content-type' => url('admin/structure/types'))) . '
'; + $output .= '
' . t("Comment functionality can be attached to any Drupal entity, eg a content type and the behavior can be customised to suit. Each entity can have its own default comment settings configured as: Open to allow new comments, Hidden to hide existing comments and prevent new comments, or Closed to view existing comments, but prevent new comments. These defaults will apply to all new content created (changes to the settings on existing content must be done manually). Other comment settings can also be customized per content type and entity, and can be overridden for any given item of content. When a comment has no replies, it remains editable by its author, as long as the author has a user account and is logged in.", array('@content-type' => url('admin/structure/types'))) . '
'; $output .= '
' . t('Comment approval') . '
'; $output .= '
' . t("Comments from users who have the Skip comment approval permission are published immediately. All other comments are placed in the Unapproved comments queue, until a user who has permission to Administer comments publishes or deletes them. Published comments can be bulk managed on the Published comments administration page.", array('@comment-approval' => url('admin/content/comment/approval'), '@admin-comment' => url('admin/content/comment'))) . '
'; $output .= '
'; @@ -104,18 +123,41 @@ function comment_help($path, $arg) { */ function comment_entity_bundle_info() { $bundles = array(); - foreach (node_type_get_names() as $type => $name) { - $bundles['comment']['comment_node_' . $type] = array( - 'label' => t('@node_type comment', array('@node_type' => $name)), - // Provide the node type/bundle name for other modules, so it does not - // have to be extracted manually from the bundle name. - 'node bundle' => $type, - ); + foreach (Drupal::service('comment.manager')->getAllFields() as $entity_type => $fields) { + foreach ($fields as $field_name => $field_info) { + $bundles['comment'][$entity_type . '__' . $field_name] = array( + 'label' => $entity_type . '_' . $field_name, + ); + } } return $bundles; } /** + * Menu callback for loading the actual name of a comment field. + * + * @param string $field_id + * The name of the field to load. + * + * @return string|false + * The field name of the comment field entity or FALSE if $field_name + * is not a comment field. + * + * @todo remove as unused. + */ +function comment_field_name_load($field_name) { + $fields = entity_load_multiple('field_entity', array( + 'name' => $field_name, + 'type' => 'comment', + )); + if (!empty($fields) && ($field = reset($fields))) { + return $field->getFieldName(); + } + + return FALSE; +} + +/** * Entity URI callback. */ function comment_uri(Comment $comment) { @@ -130,10 +172,9 @@ function comment_uri(Comment $comment) { */ function comment_field_extra_fields() { $return = array(); - - foreach (node_type_get_types() as $type) { - if (variable_get('comment_subject_field_' . $type->type, 1) == 1) { - $return['comment']['comment_node_' . $type->type] = array( + foreach (Drupal::service('comment.manager')->getAllFields() as $entity_type => $fields) { + foreach ($fields as $field_name => $field_info) { + $return['comment'][$entity_type . '__' . $field_name] = array( 'form' => array( 'author' => array( 'label' => t('Author'), @@ -169,7 +210,7 @@ function comment_theme() { 'render element' => 'elements', ), 'comment_post_forbidden' => array( - 'variables' => array('node' => NULL), + 'variables' => array('entity' => NULL, 'field_name' => 'comment'), ), 'comment_wrapper' => array( 'template' => 'comment-wrapper', @@ -182,6 +223,15 @@ function comment_theme() { * Implements hook_menu(). */ function comment_menu() { + $items['admin/structure/comments'] = array( + 'title' => 'Comment forms', + 'description' => 'Manage fields and displays settings for comment forms.', + 'route_name' => 'comment_bundle_list', + ); + $items['admin/structure/comments/manage/%'] = array( + 'title' => 'Comment form', + 'route_name' => 'comment_bundle', + ); $items['admin/content/comment'] = array( 'title' => 'Comments', 'description' => 'List and edit site comments and the comment approval queue.', @@ -238,15 +288,6 @@ function comment_menu() { function comment_menu_alter(&$items) { // Add comments to the description for admin/content. $items['admin/content']['description'] = 'Administer content and comments.'; - - // Adjust the Field UI tabs on admin/structure/types/manage/[node-type]. - // See comment_entity_bundle_info(). - $items['admin/structure/types/manage/{bundle}/comment/fields']['title'] = 'Comment fields'; - $items['admin/structure/types/manage/{bundle}/comment/fields']['weight'] = 3; - $items['admin/structure/types/manage/{bundle}/comment/form-display']['title'] = 'Comment form display'; - $items['admin/structure/types/manage/{bundle}/comment/form-display']['weight'] = 4; - $items['admin/structure/types/manage/{bundle}/comment/display']['title'] = 'Comment display'; - $items['admin/structure/types/manage/{bundle}/comment/display']['weight'] = 5; } /** @@ -260,97 +301,6 @@ function comment_count_unpublished() { } /** - * Implements hook_node_type_insert(). - * - * Creates a comment body field for a node type created while the Comment module - * is enabled. For node types created before the Comment module is enabled, - * hook_modules_enabled() serves to create the body fields. - * - * @see comment_modules_enabled() - */ -function comment_node_type_insert($info) { - _comment_body_field_create($info); -} - -/** - * Implements hook_node_type_update(). - */ -function comment_node_type_update(NodeTypeInterface $type) { - if ($type->original->id() != $type->id()) { - entity_invoke_bundle_hook('rename', 'comment', 'comment_node_' . $type->original->id(), 'comment_node_' . $type->id()); - } -} - -/** - * Implements hook_node_type_delete(). - */ -function comment_node_type_delete($info) { - entity_invoke_bundle_hook('delete', 'comment', 'comment_node_' . $info->type); - $settings = array( - 'comment', - 'comment_default_mode', - 'comment_default_per_page', - 'comment_anonymous', - 'comment_subject_field', - 'comment_preview', - 'comment_form_location', - ); - foreach ($settings as $setting) { - variable_del($setting . '_' . $info->type); - } -} - - /** - * Creates a comment_body field instance for a given node type. - * - * @param $info - * An object representing the content type. The only property that is - * currently used is $info->type, which is the machine name of the content - * type for which the body field (instance) is to be created. - */ -function _comment_body_field_create($info) { - // Create the field if needed. - if (!field_read_field('comment', 'comment_body', array('include_inactive' => TRUE))) { - $field = entity_create('field_entity', array( - 'name' => 'comment_body', - 'type' => 'text_long', - 'entity_type' => 'comment', - )); - $field->save(); - } - // Create the instance if needed. - if (!field_read_instance('comment', 'comment_body', 'comment_node_' . $info->type, array('include_inactive' => TRUE))) { - entity_invoke_bundle_hook('create', 'comment', 'comment_node_' . $info->type); - // Attaches the body field by default. - $instance = entity_create('field_instance', array( - 'field_name' => 'comment_body', - 'label' => 'Comment', - 'entity_type' => 'comment', - 'bundle' => 'comment_node_' . $info->type, - 'settings' => array('text_processing' => 1), - 'required' => TRUE, - )); - $instance->save(); - - // Assign widget settings for the 'default' form mode. - entity_get_form_display('comment', 'comment_node_' . $info->type, 'default') - ->setComponent('comment_body', array( - 'type' => 'text_textarea', - )) - ->save(); - - // Assign display settings for the 'default' view mode. - entity_get_display('comment', 'comment_node_' . $info->type, 'default') - ->setComponent('comment_body', array( - 'label' => 'hidden', - 'type' => 'text_default', - 'weight' => 0, - )) - ->save(); - } -} - -/** * Implements hook_permission(). */ function comment_permission() { @@ -382,19 +332,25 @@ function comment_permission() { * @return * An array of comment objects or an empty array if there are no recent * comments visible to the current user. + * + * @todo entity access for other entity types? */ function comment_get_recent($number = 10) { $query = db_select('comment', 'c'); - $query->innerJoin('node_field_data', 'n', 'n.nid = c.nid'); - $query->addTag('node_access'); $query->addMetaData('base_table', 'comment'); - $comments = $query - ->fields('c') - ->condition('c.status', COMMENT_PUBLISHED) - ->condition('n.status', NODE_PUBLISHED) + $query->fields('c') + ->condition('c.status', COMMENT_PUBLISHED); + if (Drupal::moduleHandler()->moduleExists('node')) { + // Special case to filter by published content. + $query->innerJoin('node_field_data', 'n', "n.nid = c.entity_id AND c.entity_type = 'node'"); + $query->addTag('node_access'); // @todo This should be actually filtering on the desired node status field // language and just fall back to the default language. - ->condition('n.default_langcode', 1) + $query + ->condition('n.status', NODE_PUBLISHED) + ->condition('n.default_langcode', 1); + } + $comments = $query ->orderBy('c.created', 'DESC') // Additionally order by cid to ensure that comments with the same timestamp // are returned in the exact order posted. @@ -409,19 +365,22 @@ function comment_get_recent($number = 10) { /** * Calculates the page number for the first new comment. * - * @param $num_comments + * @param int $num_comments * Number of comments. - * @param $new_replies + * @param int $new_replies * Number of new replies. - * @param \Drupal\Core\Entity\EntityInterface $node - * The first new comment node. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The first new comment entity. + * @param string $field_name + * The field name * * @return * "page=X" if the page number is greater than zero; empty string otherwise. */ -function comment_new_page_count($num_comments, $new_replies, EntityInterface $node) { - $mode = variable_get('comment_default_mode_' . $node->getType(), COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->getType(), 50); +function comment_new_page_count($num_comments, $new_replies, EntityInterface $entity, $field_name = 'comment') { + $instance = Drupal::service('field.info')->getInstance($entity->entityType(), $entity->bundle(), $field_name); + $mode = $instance->getFieldSetting('default_mode'); + $comments_per_page = $instance->getFieldSetting('per_page'); $pagenum = NULL; $flat = $mode == COMMENT_MODE_FLAT ? TRUE : FALSE; if ($num_comments <= $comments_per_page) { @@ -440,7 +399,9 @@ function comment_new_page_count($num_comments, $new_replies, EntityInterface $no // 1. Find all the threads with a new comment. $unread_threads_query = db_select('comment') ->fields('comment', array('thread')) - ->condition('nid', $node->id()) + ->condition('entity_id', $entity->id()) + ->condition('entity_type', $entity->entityType()) + ->condition('field_id', $entity->entityType() . '__' . $field_name) ->condition('status', COMMENT_PUBLISHED) ->orderBy('created', 'DESC') ->orderBy('cid', 'DESC') @@ -458,9 +419,14 @@ function comment_new_page_count($num_comments, $new_replies, EntityInterface $no $first_thread = substr($first_thread, 0, -1); // Find the number of the first comment of the first unread thread. - $count = db_query('SELECT COUNT(*) FROM {comment} WHERE nid = :nid AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array( + $count = db_query('SELECT COUNT(*) FROM {comment} WHERE entity_id = :entity_id + AND entity_type = :entity_type + AND field_id = :field_id + AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array( ':status' => COMMENT_PUBLISHED, - ':nid' => $node->id(), + ':entity_id' => $entity->id(), + ':field_id' => $entity->entityType() . '__' . $field_name, + ':entity_type' => $entity->entityType(), ':thread' => $first_thread, ))->fetchField(); @@ -499,171 +465,147 @@ function theme_comment_block($variables) { } /** - * Implements hook_node_view(). + * Implements hook_entity_view(). + * + * @todo Make node links as comment_links formatter + * http://drupal.org/node/1901110 */ -function comment_node_view(EntityInterface $node, EntityDisplay $display, $view_mode) { - $links = array(); - - if ($node->comment->value != COMMENT_NODE_HIDDEN) { - if ($view_mode == 'rss') { - // Add a comments RSS element which is a URL to the comments of this node. - $node->rss_elements[] = array( - 'key' => 'comments', - 'value' => url('node/' . $node->id(), array('fragment' => 'comments', 'absolute' => TRUE)) - ); +function comment_entity_view(EntityInterface $entity, EntityDisplay $display, $view_mode, $langcode) { + if ($entity->entityType() != 'node') { + // Comment links are only added to node entity type for backwards + // compatibility. Should you require comment links for other entity types + // you can do-so by implementing a new field formatter. + // @todo Make this configurable from the formatter see + // http://drupal.org/node/1901110 + return; + } + $fields = Drupal::service('comment.manager')->getFields('node'); + foreach ($fields as $field_name => $detail) { + // Skip fields that entity does not have. + if (!$entity->getPropertyDefinition($field_name)) { + continue; } - elseif ($view_mode == 'teaser') { - // Teaser view: display the number of comments that have been posted, - // or a link to add new comments if the user has permission, the node - // is open to new comments, and there currently are none. - if (user_access('access comments')) { - if (!empty($node->comment_count)) { - $links['comment-comments'] = array( - 'title' => format_plural($node->comment_count, '1 comment', '@count comments'), - 'href' => 'node/' . $node->id(), - 'attributes' => array('title' => t('Jump to the first comment of this posting.')), - 'fragment' => 'comments', - 'html' => TRUE, - ); - // Show a link to the first new comment. - if ($new = comment_num_new($node->id())) { - $links['comment-new-comments'] = array( - 'title' => format_plural($new, '1 new comment', '@count new comments'), - 'href' => 'node/' . $node->id(), - 'query' => comment_new_page_count($node->comment_count, $new, $node), - 'attributes' => array('title' => t('Jump to the first new comment of this posting.')), - 'fragment' => 'new', - 'html' => TRUE, - ); - } + $links = array(); + $commenting_status = $entity->get($field_name)->status; + if ($commenting_status) { + $instance = Drupal::service('field.info')->getInstance('node', $entity->bundle(), $field_name); + // Entity have commenting open or close. + $uri = $entity->uri(); + if ($view_mode == 'rss') { + // Add a comments RSS element which is a URL to the comments of this node. + if (!empty($uri['options'])) { + $uri['options']['fragment'] = 'comments'; + $uri['options']['absolute'] = TRUE; } + $entity->rss_elements[] = array( + 'key' => 'comments', + 'value' => url($uri['path'], $uri['options']) + ); } - if ($node->comment->value == COMMENT_NODE_OPEN) { - $comment_form_location = variable_get('comment_form_location_' . $node->getType(), COMMENT_FORM_BELOW); - if (user_access('post comments')) { - $links['comment-add'] = array( - 'title' => t('Add new comment'), - 'href' => 'node/' . $node->id(), - 'attributes' => array('title' => t('Add a new comment to this page.')), - 'fragment' => 'comment-form', - ); - if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment-add']['href'] = 'comment/reply/' . $node->id(); + elseif ($view_mode == 'teaser') { + // Teaser view: display the number of comments that have been posted, + // or a link to add new comments if the user has permission, the node + // is open to new comments, and there currently are none. + if (user_access('access comments')) { + if (!empty($entity->get($field_name)->comment_count)) { + $links['comment-comments'] = array( + 'title' => format_plural($entity->get($field_name)->comment_count, '1 comment', '@count comments'), + 'href' => $uri['path'], + 'attributes' => array('title' => t('Jump to the first comment of this posting.')), + 'fragment' => 'comments', + 'html' => TRUE, + ); + // Show a link to the first new comment. + if ($new = comment_num_new($entity->id(), $entity->entityType(), $field_name)) { + $links['comment-new-comments'] = array( + 'title' => format_plural($new, '1 new comment', '@count new comments'), + 'href' => $uri['path'], + 'query' => comment_new_page_count($entity->get($field_name)->comment_count, $new, $entity, $field_name), + 'attributes' => array('title' => t('Jump to the first new comment of this posting.')), + 'fragment' => 'new', + 'html' => TRUE, + ); + } } } - else { - $comment_post_forbidden = array( - '#theme' => 'comment_post_forbidden', - '#node' => $node, - ); - $links['comment-forbidden'] = array( - 'title' => drupal_render($comment_post_forbidden), - 'html' => TRUE, - ); - } - } - } - elseif ($view_mode != 'search_index' && $view_mode != 'search_result') { - // Node in other view modes: add a "post comment" link if the user is - // allowed to post comments and if this node is allowing new comments. - // But we don't want this link if we're building the node for search - // indexing or constructing a search result excerpt. - if ($node->comment->value == COMMENT_NODE_OPEN) { - $comment_form_location = variable_get('comment_form_location_' . $node->getType(), COMMENT_FORM_BELOW); - if (user_access('post comments')) { - // Show the "post comment" link if the form is on another page, or - // if there are existing comments that the link will skip past. - if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE || (!empty($node->comment_count) && user_access('access comments'))) { + // Provide a link to new comment form. + if ($commenting_status == COMMENT_OPEN) { + $comment_form_location = $instance->getFieldSetting('form_location'); + if (user_access('post comments')) { $links['comment-add'] = array( 'title' => t('Add new comment'), - 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')), - 'href' => 'node/' . $node->id(), + 'href' => $uri['path'], + 'attributes' => array('title' => t('Add a new comment to this page.')), 'fragment' => 'comment-form', ); if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment-add']['href'] = 'comment/reply/' . $node->id(); + $links['comment-add']['href'] = 'comment/reply/'. $entity->entityType() . '/' . $entity->id() .'/' . $field_name; } } + else { + $comment_post_forbidden = array( + '#theme' => 'comment_post_forbidden', + '#entity' => $entity, + '#field_name' => $field_name, + ); + $links['comment-forbidden'] = array( + 'title' => drupal_render($comment_post_forbidden), + 'html' => TRUE, + ); + } } - else { - $comment_post_forbidden = array( - '#theme' => 'comment_post_forbidden', - '#node' => $node, - ); - $links['comment-forbidden'] = array( - 'title' => drupal_render($comment_post_forbidden), - 'html' => TRUE, - ); + } + elseif ($view_mode != 'search_index' && $view_mode != 'search_result') { + // Entity in other view modes: add a "post comment" link if the user is + // allowed to post comments and if this entity is allowing new comments. + // But we don't want this link if we're building the entity for search + // indexing or constructing a search result excerpt. + if ($commenting_status == COMMENT_OPEN) { + $comment_form_location = $instance->getFieldSetting('form_location'); + if (user_access('post comments')) { + // Show the "post comment" link if the form is on another page, or + // if there are existing comments that the link will skip past. + if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE || (!empty($entity->get($field_name)->comment_count) && user_access('access comments'))) { + $links['comment-add'] = array( + 'title' => t('Add new comment'), + 'attributes' => array('title' => t('Share your thoughts and opinions related to this item.')), + 'href' => $uri['path'], + 'fragment' => 'comment-form', + ); + if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { + $links['comment-add']['href'] = 'comment/reply/'. $entity->entityType() . '/' . $entity->id() .'/' . $field_name; + } + } + } + else { + $comment_post_forbidden = array( + '#theme' => 'comment_post_forbidden', + '#entity' => $entity, + '#field_name' => $field_name, + ); + $links['comment-forbidden'] = array( + 'title' => drupal_render($comment_post_forbidden), + 'html' => TRUE, + ); + } } } } - $node->content['links']['comment'] = array( - '#theme' => 'links__node__comment', + $entity->content['links']['comment__' . $field_name] = array( + '#theme' => 'links__entity__comment__' . $field_name, '#links' => $links, '#attributes' => array('class' => array('links', 'inline')), ); - - // Only append comments when we are building a node on its own node detail - // page. We compare $node and $page_node to ensure that comments are not - // appended to other nodes shown on the page, for example a node_reference - // displayed in 'full' view mode within another node. - if ($node->comment && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) { - $node->content['comments'] = comment_node_page_additions($node); - } } } /** - * Builds the comment-related elements for node detail pages. * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node entity for which to build the comment-related elements. - * - * @return - * A renderable array representing the comment-related page elements for the - * node. - */ -function comment_node_page_additions(EntityInterface $node) { - $additions = array(); - - // Only attempt to render comments if the node has visible comments. - // Unpublished comments are not included in $node->comment_count, so show - // comments unconditionally if the user is an administrator. - if (($node->comment_count && user_access('access comments')) || user_access('administer comments')) { - $mode = variable_get('comment_default_mode_' . $node->getType(), COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->getType(), 50); - if ($cids = comment_get_thread($node, $mode, $comments_per_page)) { - $comments = comment_load_multiple($cids); - comment_prepare_thread($comments); - $build = comment_view_multiple($comments); - $build['pager']['#theme'] = 'pager'; - $additions['comments'] = $build; - } - } - - // Append comment form if needed. - if (user_access('post comments') && $node->comment->value == COMMENT_NODE_OPEN && (variable_get('comment_form_location_' . $node->getType(), COMMENT_FORM_BELOW) == COMMENT_FORM_BELOW)) { - $additions['comment_form'] = comment_add($node); - } - - if ($additions) { - $additions += array( - '#theme' => 'comment_wrapper__node_' . $node->getType(), - '#node' => $node, - 'comments' => array(), - 'comment_form' => array(), - ); - } - - return $additions; -} - -/** - * Returns a rendered form to comment the given node. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node entity to be commented. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to which the comments are in reply to. + * @param string $field_name + * The field name where the comments were entered. * @param int $pid * (optional) Some comments are replies to other comments. In those cases, * $pid is the parent comment's comment ID. Defaults to NULL. @@ -671,8 +613,13 @@ function comment_node_page_additions(EntityInterface $node) { * @return array * The renderable array for the comment addition form. */ -function comment_add(EntityInterface $node, $pid = NULL) { - $values = array('nid' => $node->id(), 'pid' => $pid, 'node_type' => 'comment_node_' . $node->getType()); +function comment_add(EntityInterface $entity, $field_name = 'comment', $pid = NULL) { + $values = array( + 'entity_type' => $entity->entityType(), + 'entity_id' => $entity->id(), + 'field_id' => $entity->entityType() . '__' . $field_name, + 'pid' => $pid, + ); $comment = entity_create('comment', $values); return Drupal::entityManager()->getForm($comment); } @@ -680,11 +627,13 @@ function comment_add(EntityInterface $node, $pid = NULL) { /** * Retrieves comments for a thread. * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node whose comment(s) needs rendering. - * @param $mode + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity whose comment(s) needs rendering. + * @param string $field_name + * The field_name whose comment(s) needs rendering. + * @param int $mode * The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED. - * @param $comments_per_page + * @param int $comments_per_page * The amount of comments to display per page. * * @return @@ -744,32 +693,38 @@ function comment_add(EntityInterface $node, $pid = NULL) { * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need * to consider the trailing "/" so we use a substring only. */ -function comment_get_thread(EntityInterface $node, $mode, $comments_per_page) { +function comment_get_thread(EntityInterface $entity, $field_name, $mode, $comments_per_page) { $query = db_select('comment', 'c') ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); $query->addField('c', 'cid'); $query - ->condition('c.nid', $node->id()) - ->addTag('node_access') + ->condition('c.entity_id', $entity->id()) + ->condition('c.entity_type', $entity->entityType()) + ->condition('c.field_id', $entity->entityType() . '__' . $field_name) + ->addTag('entity_access') ->addTag('comment_filter') ->addMetaData('base_table', 'comment') - ->addMetaData('node', $node) + ->addMetaData('entity', $entity) + ->addMetaData('field_name', $field_name) ->limit($comments_per_page); $count_query = db_select('comment', 'c'); $count_query->addExpression('COUNT(*)'); $count_query - ->condition('c.nid', $node->id()) - ->addTag('node_access') + ->condition('c.entity_id', $entity->id()) + ->condition('c.entity_type', $entity->entityType()) + ->condition('c.field_id', $entity->entityType() . '__' . $field_name) + ->addTag('entity_access') ->addTag('comment_filter') ->addMetaData('base_table', 'comment') - ->addMetaData('node', $node); + ->addMetaData('entity', $entity) + ->addMetaData('field_name', $field_name); if (!user_access('administer comments')) { $query->condition('c.status', COMMENT_PUBLISHED); $count_query->condition('c.status', COMMENT_PUBLISHED); } - if ($mode === COMMENT_MODE_FLAT) { + if ($mode == COMMENT_MODE_FLAT) { $query->orderBy('c.cid', 'ASC'); } else { @@ -781,9 +736,7 @@ function comment_get_thread(EntityInterface $node, $mode, $comments_per_page) { } $query->setCountQuery($count_query); - $cids = $query->execute()->fetchCol(); - - return $cids; + return $query->execute()->fetchCol(); } /** @@ -803,7 +756,7 @@ function comment_prepare_thread(&$comments) { // A counter that helps track how indented we are. $divs = 0; - foreach ($comments as $key => $comment) { + foreach ($comments as $key => &$comment) { if ($first_new && $comment->new->value != MARK_READ) { // Assign the anchor only for the first new comment. This avoids duplicate // id attributes on a page. @@ -824,7 +777,6 @@ function comment_prepare_thread(&$comments) { $divs--; } } - $comments[$key] = $comment; } // The final comment must close up some hanging divs @@ -834,10 +786,10 @@ function comment_prepare_thread(&$comments) { /** * Generates an array for rendering a comment. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\CommentInterface $comment * The comment object. * @param $view_mode - * View mode, e.g. 'full', 'teaser'... + * (optional) View mode, e.g. 'full', 'teaser'... Defaults to 'full'. * @param $langcode * (optional) A language code to use for rendering. Defaults to the global * content language of the current request. @@ -845,24 +797,27 @@ function comment_prepare_thread(&$comments) { * @return * An array as expected by drupal_render(). */ -function comment_view(Comment $comment, $view_mode = 'full', $langcode = NULL) { +function comment_view(CommentInterface $comment, $view_mode = 'full', $langcode = NULL) { return entity_view($comment, $view_mode, $langcode); } /** * Adds reply, edit, delete, etc. links, depending on user permissions. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\CommentInterface $comment * The comment object. - * @param \Drupal\Core\Entity\EntityInterface $node - * The node the comment is attached to. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the comment is attached to. + * @param string $field_name + * The field the comment is attached to. * * @return * A structured array of links. */ -function comment_links(Comment $comment, EntityInterface $node) { +function comment_links(CommentInterface $comment, EntityInterface $entity, $field_name) { $links = array(); - if ($node->comment->value == COMMENT_NODE_OPEN) { + $status = $entity->get($field_name)->status; + if ($status == COMMENT_OPEN) { if ($comment->access('delete')) { $links['comment-delete'] = array( 'title' => t('delete'), @@ -871,16 +826,16 @@ function comment_links(Comment $comment, EntityInterface $node) { ); } if ($comment->access('update')) { - $links['comment-edit'] = array( - 'title' => t('edit'), - 'href' => "comment/{$comment->id()}/edit", - 'html' => TRUE, - ); + $links['comment-edit'] = array( + 'title' => t('edit'), + 'href' => "comment/{$comment->id()}/edit", + 'html' => TRUE, + ); } if ($comment->access('create')) { $links['comment-reply'] = array( 'title' => t('reply'), - 'href' => "comment/reply/{$comment->nid->target_id}/{$comment->id()}", + 'href' => "comment/reply/{$comment->entity_type->value}/{$comment->entity_id->value}/$field_name/{$comment->id()}", 'html' => TRUE, ); } @@ -895,7 +850,8 @@ function comment_links(Comment $comment, EntityInterface $node) { if (empty($links)) { $comment_post_forbidden = array( '#theme' => 'comment_post_forbidden', - '#node' => $node, + '#entity' => $entity, + '#field_name' => $field_name, ); $links['comment-forbidden']['title'] = drupal_render($comment_post_forbidden); $links['comment-forbidden']['html'] = TRUE; @@ -922,8 +878,9 @@ function comment_links(Comment $comment, EntityInterface $node) { * @param $view_mode * View mode, e.g. 'full', 'teaser'... * @param $langcode - * A string indicating the language field values are to be shown in. If no - * language is provided the current content language is used. + * (optional) A string indicating the language field values are to be shown + * in. If no language is provided the current content language is used. + * Defaults to NULL. * * @return * An array in the format expected by drupal_render(). @@ -935,234 +892,165 @@ function comment_view_multiple($comments, $view_mode = 'full', $langcode = NULL) } /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_form_FORM_ID_alter() for field_ui_field_instance_edit_form(). */ -function comment_form_node_type_form_alter(&$form, $form_state) { - if (isset($form['type'])) { - $node_type = $form_state['controller']->getEntity(); - $form['comment'] = array( - '#type' => 'details', - '#title' => t('Comment settings'), - '#collapsed' => TRUE, - '#group' => 'additional_settings', - '#attributes' => array( - 'class' => array('comment-node-type-settings-form'), - ), - '#attached' => array( - 'library' => array(array('comment', 'drupal.comment')), - ), - ); - // Unlike coment_form_node_form_alter(), all of these settings are applied - // as defaults to all new nodes. Therefore, it would be wrong to use #states - // to hide the other settings based on the primary comment setting. - $form['comment']['comment'] = array( - '#type' => 'select', - '#title' => t('Default comment setting for new content'), - '#default_value' => variable_get('comment_' . $node_type->id(), COMMENT_NODE_OPEN), - '#options' => array( - COMMENT_NODE_OPEN => t('Open'), - COMMENT_NODE_CLOSED => t('Closed'), - COMMENT_NODE_HIDDEN => t('Hidden'), - ), - ); - $form['comment']['comment_default_mode'] = array( - '#type' => 'checkbox', - '#title' => t('Threading'), - '#default_value' => variable_get('comment_default_mode_' . $node_type->id(), COMMENT_MODE_THREADED), - '#description' => t('Show comment replies in a threaded list.'), - ); - $form['comment']['comment_default_per_page'] = array( - '#type' => 'select', - '#title' => t('Comments per page'), - '#default_value' => variable_get('comment_default_per_page_' . $node_type->id(), 50), - '#options' => _comment_per_page(), - ); - $form['comment']['comment_anonymous'] = array( - '#type' => 'select', - '#title' => t('Anonymous commenting'), - '#default_value' => variable_get('comment_anonymous_' . $node_type->id(), COMMENT_ANONYMOUS_MAYNOT_CONTACT), - '#options' => array( - COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'), - COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'), - COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information'), - ), - '#access' => user_access('post comments', drupal_anonymous_user()), - ); - $form['comment']['comment_subject_field'] = array( - '#type' => 'checkbox', - '#title' => t('Allow comment title'), - '#default_value' => variable_get('comment_subject_field_' . $node_type->id(), 1), - ); - $form['comment']['comment_form_location'] = array( - '#type' => 'checkbox', - '#title' => t('Show reply form on the same page as comments'), - '#default_value' => variable_get('comment_form_location_' . $node_type->id(), COMMENT_FORM_BELOW), - ); - $form['comment']['comment_preview'] = array( - '#type' => 'radios', - '#title' => t('Preview comment'), - '#default_value' => variable_get('comment_preview_' . $node_type->id(), DRUPAL_OPTIONAL), - '#options' => array( - DRUPAL_DISABLED => t('Disabled'), - DRUPAL_OPTIONAL => t('Optional'), - DRUPAL_REQUIRED => t('Required'), - ), - ); - // @todo Remove this check once language settings are generalized. - if (module_exists('content_translation')) { - $comment_form = $form; - $comment_form_state['content_translation']['key'] = 'language_configuration'; - $form['comment'] += content_translation_enable_widget('comment', 'comment_node_' . $node_type->id(), $comment_form, $comment_form_state); +function comment_form_field_ui_field_instance_edit_form_alter(&$form, $form_state) { + if ($form['#field']['type'] == 'comment') { + // Collect translation settings. + if (Drupal::moduleHandler()->moduleExists('content_translation')) { array_unshift($form['#submit'], 'comment_translation_configuration_element_submit'); } + + // Hide required checkbox. + $form['instance']['required']['#access'] = FALSE; + } +} + +/** + * Implements hook_form_FORM_ID_alter() for field_ui_field_overview_form(). + */ +function comment_form_field_ui_field_overview_form_alter(&$form, $form_state) { + // @todo Remove when https://drupal.org/node/2076085 is in. + drupal_set_title(t('Manage Fields: %field_name', array('%field_name' => $form['#bundle'])), TRUE); +} + +/** + * Implements hook_form_FORM_ID_alter() for field_ui_display_overview_form(). + */ +function comment_form_field_ui_display_overview_form_alter(&$form, $form_state) { + // @todo Remove when https://drupal.org/node/2076085 is in. + drupal_set_title(t('Manage Display: %field_name', array('%field_name' => $form['#bundle'])), TRUE); +} + +/** + * Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form(). + */ +function comment_form_field_ui_field_edit_form_alter(&$form, $form_state) { + if ($form['#field']['type'] == 'comment') { + // We only support posting one comment at the time so it doesn't make sense + // to let the site builder choose anything else. + $form['field']['cardinality_container']['cardinality']['#options'] = drupal_map_assoc(array(1)); + $form['field']['cardinality_container']['#access'] = FALSE; } } /** - * Form submission handler for node_type_form(). + * Form submission handler for field_ui_field_edit_form(). * * This handles the comment translation settings added by - * comment_form_node_type_form_alter(). + * _comment_field_instance_settings_form_process(). * - * @see comment_form_node_type_form_alter() + * @see _comment_field_instance_settings_form_process() */ function comment_translation_configuration_element_submit($form, &$form_state) { - // The comment translation settings form element is embedded into the node - // type form. Hence we need to provide to the regular submit handler a - // manipulated form state to make it process comment settings instead of node - // settings. + // The comment translation settings form element is embedded into the instance + // settings form. Hence we need to provide to the regular submit handler a + // manipulated form state to make it process comment settings instead of the + // host entity. $key = 'language_configuration'; $comment_form_state = array( 'content_translation' => array('key' => $key), - 'language' => array($key => array('entity_type' => 'comment', 'bundle' => 'comment_node_' . $form_state['controller']->getEntity()->id())), + 'language' => array($key => array('entity_type' => 'comment', 'bundle' => $form['#field']['name'])), 'values' => array($key => array('content_translation' => $form_state['values']['content_translation'])), ); content_translation_language_configuration_element_submit($form, $comment_form_state); } /** - * Implements hook_form_BASE_FORM_ID_alter(). - */ -function comment_form_node_form_alter(&$form, $form_state) { - $node = $form_state['controller']->getEntity(); - $form['comment_settings'] = array( - '#type' => 'details', - '#access' => user_access('administer comments'), - '#title' => t('Comment settings'), - '#collapsed' => TRUE, - '#group' => 'advanced', - '#attributes' => array( - 'class' => array('comment-node-settings-form'), - ), - '#attached' => array( - 'library' => array(array('comment', 'drupal.comment')), - ), - '#weight' => 30, - ); - $comment_count = $node->id() ? db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array(':nid' => $node->id()))->fetchField() : 0; - $comment_settings = ($node->comment->value == COMMENT_NODE_HIDDEN && empty($comment_count)) ? COMMENT_NODE_CLOSED : $node->comment->value; - $form['comment_settings']['comment'] = array( - '#type' => 'radios', - '#title' => t('Comments'), - '#title_display' => 'invisible', - '#parents' => array('comment'), - '#default_value' => $comment_settings, - '#options' => array( - COMMENT_NODE_OPEN => t('Open'), - COMMENT_NODE_CLOSED => t('Closed'), - COMMENT_NODE_HIDDEN => t('Hidden'), - ), - COMMENT_NODE_OPEN => array( - '#description' => t('Users with the "Post comments" permission can post comments.'), - ), - COMMENT_NODE_CLOSED => array( - '#description' => t('Users cannot post comments, but existing comments will be displayed.'), - ), - COMMENT_NODE_HIDDEN => array( - '#description' => t('Comments are hidden from view.'), - ), - ); - // If the node doesn't have any comments, the "hidden" option makes no - // sense, so don't even bother presenting it to the user. - if (empty($comment_count)) { - $form['comment_settings']['comment'][COMMENT_NODE_HIDDEN]['#access'] = FALSE; - // Also adjust the description of the "closed" option. - $form['comment_settings']['comment'][COMMENT_NODE_CLOSED]['#description'] = t('Users cannot post comments.'); - } -} - -/** - * Implements hook_node_load(). + * Implements hook_entity_load(). + * + * @see \Drupal\comment\Plugin\field\field_type\CommentItem::getPropertyDefinitions() */ -function comment_node_load($nodes, $types) { - $comments_enabled = array(); - - // Check if comments are enabled for each node. If comments are disabled, - // assign values without hitting the database. - foreach ($nodes as $node) { - // Store whether comments are enabled for this node. - if ($node->comment->value != COMMENT_NODE_HIDDEN) { - $comments_enabled[] = $node->id(); - } - else { - $node->cid = 0; - $node->last_comment_timestamp = $node->getCreatedTime(); - $node->last_comment_name = ''; - $node->last_comment_uid = $node->getAuthorId(); - $node->comment_count = 0; - } +function comment_entity_load($entities, $entity_type) { + if (!Drupal::service('comment.manager')->getFields($entity_type)) { + // Do not query database when entity has no comment fields. + return; } - - // For nodes with comments enabled, fetch information from the database. - if (!empty($comments_enabled)) { - $result = db_query('SELECT nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count FROM {node_comment_statistics} WHERE nid IN (:comments_enabled)', array(':comments_enabled' => $comments_enabled)); - foreach ($result as $record) { - $nodes[$record->nid]->cid = $record->cid; - $nodes[$record->nid]->last_comment_timestamp = $record->last_comment_timestamp; - $nodes[$record->nid]->last_comment_name = $record->last_comment_name; - $nodes[$record->nid]->last_comment_uid = $record->last_comment_uid; - $nodes[$record->nid]->comment_count = $record->comment_count; - } - } -} - -/** - * Implements hook_node_prepare_form(). - */ -function comment_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) { - if (!isset($node->comment->value)) { - $node->comment = variable_get('comment_' . $node->getType(), COMMENT_NODE_OPEN); + // Load comment information from the database and update the entity's comment + // statistics properties, which are defined on each CommentItem field. + $result = db_select('comment_entity_statistics', 'ces') + ->fields('ces') + ->condition('ces.entity_id', array_keys($entities)) + ->condition('ces.entity_type', $entity_type) + ->execute(); + foreach ($result as $record) { + $parts = explode('__', $record->field_id, 2); + list(, $field_name) = $parts; + $comment_statistics = $entities[$record->entity_id]->get($field_name); + $comment_statistics->cid = $record->cid; + $comment_statistics->last_comment_timestamp = $record->last_comment_timestamp; + $comment_statistics->last_comment_name = $record->last_comment_name; + $comment_statistics->last_comment_uid = $record->last_comment_uid; + $comment_statistics->comment_count = $record->comment_count; } } /** - * Implements hook_node_insert(). + * Implements hook_entity_insert(). */ -function comment_node_insert(EntityInterface $node) { +function comment_entity_insert(EntityInterface $entity) { + global $user; // Allow bulk updates and inserts to temporarily disable the - // maintenance of the {node_comment_statistics} table. - if (variable_get('comment_maintain_node_statistics', TRUE)) { - db_insert('node_comment_statistics') - ->fields(array( - 'nid' => $node->id(), + // maintenance of the {comment_entity_statistics} table. + if (Drupal::state()->get('comment.maintain_entity_statistics') && + $fields = Drupal::service('comment.manager')->getFields($entity->entityType())) { + $query = db_insert('comment_entity_statistics') + ->fields(array( + 'entity_id', + 'entity_type', + 'field_id', + 'cid', + 'last_comment_timestamp', + 'last_comment_name', + 'last_comment_uid', + 'comment_count' + )); + foreach ($fields as $field_name => $detail) { + // Skip fields that entity does not have. + if (!$entity->getPropertyDefinition($field_name)) { + continue; + } + // There is at least one comment field, the query needs to be executed. + // Default to current user when entity does not have a uid property. + $last_comment_uid = $user->id(); + // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387 + if ($entity->getPropertyDefinition('uid')) { + $last_comment_uid = $entity->get('uid')->value; + } + // Default to REQUEST_TIME when entity does not have a changed property. + $last_comment_timestamp = REQUEST_TIME; + if ($entity instanceof EntityChangedInterface) { + $last_comment_timestamp = $entity->getChangedTime(); + } + $query->values(array( + 'entity_id' => $entity->id(), + 'entity_type' => $entity->entityType(), + 'field_id' => $entity->entityType() . '__' . $field_name, 'cid' => 0, - 'last_comment_timestamp' => $node->getChangedTime(), + 'last_comment_timestamp' => $last_comment_timestamp, 'last_comment_name' => NULL, - 'last_comment_uid' => $node->getAuthorId(), + 'last_comment_uid' => $last_comment_uid, 'comment_count' => 0, - )) - ->execute(); + )); + } + $query->execute(); } } /** - * Implements hook_node_predelete(). + * Implements hook_entity_predelete(). */ -function comment_node_predelete(EntityInterface $node) { - $cids = db_query('SELECT cid FROM {comment} WHERE nid = :nid', array(':nid' => $node->id()))->fetchCol(); +function comment_entity_predelete(EntityInterface $entity) { + $cids = db_select('comment', 'c') + ->fields('c', array('cid')) + ->condition('entity_id', $entity->id()) + ->condition('entity_type', $entity->entityType()) + ->execute() + ->fetchCol(); entity_delete_multiple('comment', $cids); - db_delete('node_comment_statistics') - ->condition('nid', $node->id()) + db_delete('comment_entity_statistics') + ->condition('entity_id', $entity->id()) + ->condition('entity_type', $entity->entityType()) ->execute(); } @@ -1195,17 +1083,26 @@ function comment_node_update_index(EntityInterface $node, $langcode) { } } + $return = ''; + if ($index_comments) { - $mode = variable_get('comment_default_mode_' . $node->getType(), COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->bundle(), 50); - if ($node->comment->value && $cids = comment_get_thread($node, $mode, $comments_per_page)) { - $comments = comment_load_multiple($cids); - comment_prepare_thread($comments); - $build = comment_view_multiple($comments, $langcode); - return drupal_render($build); + foreach (Drupal::service('comment.manager')->getFields('node') as $field_name => $info) { + // Skip fields that entity does not have. + if (!$node->getPropertyDefinition($field_name)) { + continue; + } + $instance = Drupal::service('field.info')->getInstance('node', $node->getType(), $field_name); + $mode = $instance->getFieldSetting('default_mode'); + $comments_per_page = $instance->getFieldSetting('per_page'); + if ($node->get($field_name)->status && $cids = comment_get_thread($node, $field_name, $mode, $comments_per_page)) { + $comments = comment_load_multiple($cids); + comment_prepare_thread($comments); + $build = comment_view_multiple($comments); + $return .= drupal_render($build); + } } } - return ''; + return $return; } /** @@ -1213,7 +1110,7 @@ function comment_node_update_index(EntityInterface $node, $langcode) { */ function comment_update_index() { // Store the maximum possible comments per thread (used for ranking by reply count) - Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}')->fetchField())); + Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField())); } /** @@ -1223,15 +1120,32 @@ function comment_update_index() { * results. */ function comment_node_search_result(EntityInterface $node) { - // Do not make a string if comments are hidden. - if (user_access('access comments') && $node->comment->value != COMMENT_NODE_HIDDEN) { - $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->id()))->fetchField(); - // Do not make a string if comments are closed and there are currently - // zero comments. - if ($node->comment->value != COMMENT_NODE_CLOSED || $comments > 0) { - return array('comment' => format_plural($comments, '1 comment', '@count comments')); + $comment_fields = Drupal::service('comment.manager')->getFields('node'); + $comments = 0; + $open = FALSE; + foreach ($comment_fields as $field_name => $info) { + // Skip fields that entity does not have. + if (!$node->getPropertyDefinition($field_name)) { + continue; + } + // Do not make a string if comments are hidden. + if (user_access('access comments') && ($items = field_get_items($node, $field_name)) && + isset($items[0]['status']) && $items[0]['status'] != COMMENT_HIDDEN) { + if ($items[0]['status'] == COMMENT_OPEN) { + // At least one comment field is open. + $open = TRUE; + } + $comments += db_query("SELECT comment_count FROM {comment_entity_statistics} WHERE entity_id = :nid AND entity_type = 'node' AND field_id = :field_id", array( + 'nid' => $node->id(), + 'field_id' => 'node__' . $field_name) + )->fetchField(); } } + // Do not make a string if there are no comment fields, or no comments exist + // or all comment fields are hidden. + if ($comments > 0 || $open) { + return array('comment' => format_plural($comments, '1 comment', '@count comments')); + } } /** @@ -1301,8 +1215,12 @@ function comment_load($cid, $reset = FALSE) { /** * Gets the number of new comments for the current user and the specified node. * - * @param $nid - * Node ID to count comments for. + * @param int $entity_id + * Entity ID of the entity to which the comments are attached. + * @param string $entity_type + * Entity type of the entity to which the comments are attached. + * @param string $field_name + * (optional) The field_name to count comments for. Defaults to NULL. * @param $timestamp * Time to count from (defaults to time of last user access * to node). @@ -1310,22 +1228,43 @@ function comment_load($cid, $reset = FALSE) { * @return * The number of new comments or FALSE if the user is not logged in. */ -function comment_num_new($nid, $timestamp = 0) { +function comment_num_new($entity_id, $entity_type, $field_name = NULL, $timestamp = 0) { global $user; if ($user->isAuthenticated() && module_exists('history')) { // Retrieve the timestamp at which the current user last viewed this node. if (!$timestamp) { - $timestamp = history_read($nid); + if ($entity_type == 'node') { + $timestamp = history_read($entity_id); + } + else { + $function = $entity_type . '_last_viewed'; + if (function_exists($function)) { + $timestamp = $function($entity_id); + } + else { + // Default to 30 days ago. + // @todo Remove once http://drupal.org/node/1029708 lands. + $timestamp = COMMENT_NEW_LIMIT; + } + } } $timestamp = ($timestamp > HISTORY_READ_LIMIT ? $timestamp : HISTORY_READ_LIMIT); // Use the timestamp to retrieve the number of new comments. - return db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND created > :timestamp AND status = :status', array( - ':nid' => $nid, - ':timestamp' => $timestamp, - ':status' => COMMENT_PUBLISHED, - ))->fetchField(); + $query = db_select('comment', 'c'); + $query->addExpression('COUNT(cid)'); + $query->condition('c.entity_type', $entity_type) + ->condition('c.entity_id', $entity_id) + ->condition('c.status', COMMENT_PUBLISHED) + ->condition('c.created', $timestamp, '>'); + if ($field_name) { + // Limit to a particular field. + $query->condition('c.field_id', $entity_type . '__' . $field_name); + } + + return $query->execute() + ->fetchField(); } else { return FALSE; @@ -1339,31 +1278,31 @@ function comment_num_new($nid, $timestamp = 0) { * Count the number of comments which appear before the comment we want to * display, taking into account display settings and threading. * - * @param $cid + * @param int $cid * The comment ID. - * @param $node_type - * The node type of the comment's parent. + * @param array $instance + * Field instance as returned from field_info_instance(). * * @return * The display ordinal for the comment. * * @see comment_get_display_page() + * @see field_info_instance(). */ -function comment_get_display_ordinal($cid, $node_type) { +function comment_get_display_ordinal($cid, $instance) { // Count how many comments (c1) are before $cid (c2) in display order. This is // the 0-based display ordinal. $query = db_select('comment', 'c1'); - $query->innerJoin('comment', 'c2', 'c2.nid = c1.nid'); + $query->innerJoin('comment', 'c2', 'c2.entity_id = c1.entity_id AND c2.entity_type = c1.entity_type AND c2.field_id = c1.field_id'); $query->addExpression('COUNT(*)', 'count'); $query->condition('c2.cid', $cid); if (!user_access('administer comments')) { $query->condition('c1.status', COMMENT_PUBLISHED); } - $mode = variable_get('comment_default_mode_' . $node_type, COMMENT_MODE_THREADED); - if ($mode == COMMENT_MODE_FLAT) { + if ($instance->getFieldSetting('default_mode') == COMMENT_MODE_FLAT) { // For flat comments, cid is used for ordering comments due to - // unpredicatable behavior with timestamp, so we make the same assumption + // unpredictable behavior with timestamp, so we make the same assumption // here. $query->condition('c1.cid', $cid, '<'); } @@ -1385,26 +1324,27 @@ function comment_get_display_ordinal($cid, $node_type) { * * @param $cid * The comment ID. - * @param $node_type - * The node type the comment is attached to. + * @param array $instance + * Field instance as returned from field_info_instance(). * * @return * The page number. */ -function comment_get_display_page($cid, $node_type) { - $ordinal = comment_get_display_ordinal($cid, $node_type); - $comments_per_page = variable_get('comment_default_per_page_' . $node_type, 50); +function comment_get_display_page($cid, $instance) { + $ordinal = comment_get_display_ordinal($cid, $instance); + $comments_per_page = $instance->getFieldSetting('per_page'); return floor($ordinal / $comments_per_page); } /** * Generates a comment preview. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\CommentInterface $comment */ -function comment_preview(Comment $comment) { +function comment_preview(CommentInterface $comment) { global $user; $preview_build = array(); + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); if (!form_get_errors()) { // Attach the user and time information. @@ -1434,13 +1374,28 @@ function comment_preview(Comment $comment) { if ($comment->pid->target_id) { $build = array(); - $comment = $comment->pid->entity; - if ($comment && $comment->status->value == COMMENT_PUBLISHED) { - $build = comment_view($comment); + $parent = $comment->pid->entity; + if ($parent && $parent->status->value == COMMENT_PUBLISHED) { + $build = comment_view($parent); } } else { - $build = node_view($comment->nid->entity); + // The comment field output includes rendering the parent entity of the + // thread to which the comment is a reply. The rendered entity output + // includes the comment reply form, which contains the comment preview and + // therefore the rendered parent entity. This results in an infinite loop of + // parent entity output rendering the comment form and the comment form + // rendering the parent entity. To prevent this infinite loop we temporarily + // set the value of the comment field on the rendered entity to hidden + // before calling entity_view(). That way when the output of the commented + // entity is rendered, it excludes the comment field output. As objects are + // always addressed by reference we ensure changes are not lost by setting + // the value back to its original state after the call to entity_view(). + $field_name = $comment->field_name->value; + $original_value = $entity->get($field_name); + $entity->set($field_name, COMMENT_HIDDEN); + $build = entity_view($entity, 'full'); + $entity->set($field_name, $original_value); } $preview_build['comment_output_below'] = $build; @@ -1464,10 +1419,13 @@ function comment_preprocess_block(&$variables) { * This helper handles anonymous authors in addition to registered comment * authors. * + * @param \Drupal\comment\CommentInterface $comment + * The comment to which the author replied. + * * @return \Drupal\user\Entity\User * A user account, for use with theme_username() or the user_picture template. */ -function comment_prepare_author(Comment $comment) { +function comment_prepare_author(CommentInterface $comment) { // The account has been pre-loaded by CommentRenderController::buildContent(). $account = $comment->uid->entity; if (empty($account->uid->value)) { @@ -1488,9 +1446,9 @@ function comment_prepare_author(Comment $comment) { */ function template_preprocess_comment(&$variables) { $comment = $variables['elements']['#comment']; - $node = $variables['elements']['#node']; + $comment_entity = entity_load($comment->entity_type->value, $comment->entity_id->value); $variables['comment'] = $comment; - $variables['node'] = $node; + $variables['comment_entity'] = $comment_entity; $account = comment_prepare_author($comment); // @todo Do not call theme() here. We do this for purposes of t(). @@ -1598,8 +1556,9 @@ function template_preprocess_comment(&$variables) { $variables['attributes']['class'][] = 'by-anonymous'; } else { - if ($comment->uid->target_id == $variables['node']->getAuthorId()) { - $variables['attributes']['class'][] = 'by-node-author'; + // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387 + if ($comment_entity->getPropertyDefinition('uid') && $comment->uid->target_id == $comment_entity->get('uid')->value) { + $variables['attributes']['class'][] = 'by-' . $comment_entity->entityType() . '-author'; } if ($comment->uid->target_id == $variables['user']->id()) { $variables['attributes']['class'][] = 'by-viewer'; @@ -1616,12 +1575,14 @@ function template_preprocess_comment(&$variables) { * * @param $variables * An associative array containing: - * - node: The comment node. + * - entity: The comment entity. + * - field_name: The comment field. * * @ingroup themeable */ function theme_comment_post_forbidden($variables) { - $node = $variables['node']; + $entity = $variables['entity']; + $field_name = $variables['field_name']; global $user; // Since this is expensive to compute, we cache it so that a page with many @@ -1637,13 +1598,15 @@ function theme_comment_post_forbidden($variables) { } if ($authenticated_post_comments) { + $instance = Drupal::service('field.info')->getInstance($entity->entityType(), $entity->bundle(), $field_name); // We cannot use drupal_get_destination() because these links // sometimes appear on /node and taxonomy listing pages. - if (variable_get('comment_form_location_' . $node->getType(), COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) { - $destination = array('destination' => 'comment/reply/' . $node->id() . '#comment-form'); + if ($instance->getFieldSetting('form_location') == COMMENT_FORM_SEPARATE_PAGE) { + $destination = array('destination' => 'comment/reply/' . $entity->entityType() . '/' . $entity->id() . '/' . $field_name . '#comment-form'); } else { - $destination = array('destination' => 'node/' . $node->id() . '#comment-form'); + $uri = $entity->uri(); + $destination = array('destination' => $uri['path'] . '#comment-form'); } if (Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) { @@ -1670,8 +1633,8 @@ function theme_comment_post_forbidden($variables) { */ function template_preprocess_comment_wrapper(&$variables) { // Provide contextual information. - $variables['node'] = $variables['content']['#node']; - $variables['display_mode'] = variable_get('comment_default_mode_' . $variables['node']->getType(), COMMENT_MODE_THREADED); + $variables['entity'] = $variables['content']['#entity']; + $variables['display_mode'] = $variables['content']['#display_mode']; // The comment form is optional and may not exist. $variables['content'] += array('comment_form' => array()); @@ -1744,12 +1707,14 @@ function comment_ranking() { 'title' => t('Number of comments'), 'join' => array( 'type' => 'LEFT', - 'table' => 'node_comment_statistics', - 'alias' => 'node_comment_statistics', - 'on' => 'node_comment_statistics.nid = i.sid', + 'table' => 'comment_entity_statistics', + 'alias' => 'ces', + // Default to comment field as this is the most common use case for + // nodes. + 'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_id = 'node__comment'", ), // Inverse law that maps the highest reply count on the site to 1 and 0 to 0. - 'score' => '2.0 - 2.0 / (1.0 + node_comment_statistics.comment_count * CAST(:scale AS DECIMAL))', + 'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * CAST(:scale AS DECIMAL))', 'arguments' => array(':scale' => Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0), ), ); @@ -1761,8 +1726,9 @@ function comment_ranking() { function comment_file_download_access($field, EntityInterface $entity, File $file) { if ($entity->entityType() == 'comment') { if (user_access('access comments') && $entity->status->value == COMMENT_PUBLISHED || user_access('administer comments')) { - $node = $entity->nid->entity; - return node_access('view', $node); + $commented_entity = entity_load($entity->entity_type->value, $entity->entity_id->value); + // Check access to parent entity. + return $commented_entity->access('view'); } return FALSE; } @@ -1776,7 +1742,7 @@ function comment_library_info() { 'title' => 'Comment', 'version' => Drupal::VERSION, 'js' => array( - drupal_get_path('module', 'comment') . '/comment-node-form.js' => array(), + drupal_get_path('module', 'comment') . '/comment-entity-form.js' => array(), ), 'dependencies' => array( array('system', 'jquery'), @@ -1787,3 +1753,75 @@ function comment_library_info() { return $libraries; } + +/** + * Implements hook_ENTITY_TYPE_create() for 'field_instance'. + */ +function comment_field_instance_create(FieldInstanceInterface $instance) { + if ($instance->getFieldType() == 'comment') { + Drupal::service('comment.manager')->addBodyField($instance->entity_type, $instance->getFieldName()); + Drupal::cache()->delete('comment_entity_info'); + } +} + +/** + * Implements hook_ENTITY_TYPE_delete() for 'field_entity'. + */ +function comment_field_entity_delete(FieldInterface $field) { + if ($field->getFieldType() == 'comment') { + // Delete all fields and displays attached to the comment bundle. + entity_invoke_bundle_hook('delete', 'comment', $field->getFieldName()); + Drupal::cache()->delete('comment_entity_info'); + } +} + +/** + * Implements hook_ENTITY_TYPE_delete() for 'field_instance'. + */ +function comment_field_instance_delete(FieldInstanceInterface $instance) { + if ($instance->getFieldType() == 'comment') { + // Delete all comments that used by the entity bundle. + $comments = db_query("SELECT cid FROM {comment} WHERE entity_type = :entity_type AND field_id = :field_id", array( + ':entity_type' => $instance->entityType(), + ':field_id' => $instance->entityType() . '__' . $instance->getFieldName(), + ))->fetchCol(); + entity_delete_multiple('comment', $comments); + Drupal::cache()->delete('comment_entity_info'); + } +} + +/** + * Decide on the type of marker to be shown for a comment. + * + * @param \Drupal\comment\CommentInterface $comment + * The comment for which the marker is to be shown. + * + * @return int + * The type of marker to be shown one of MARK_READ, MARK_UPDATED or MARK_NEW. + */ +function comment_mark(CommentInterface $comment) { + global $user; + $cache = &drupal_static(__FUNCTION__, array()); + $cid = $comment->entity_id->value . '__' . $comment->entity_type->value; + + if (!$user->isAuthenticated()) { + return MARK_READ; + } + if (!isset($cache[$cid])) { + if ($comment->entity_type->value == 'node' && Drupal::moduleHandler()->moduleExists('history')) { + $cache[$cid] = history_read($comment->entity_id->value); + } + else { + // @todo - decide how to handle last viewed for other entities. For now + // assume REQUEST_TIME. + $cache[$cid] = REQUEST_TIME; + } + } + if ($cache[$cid] == 0 && $comment->changed->value > COMMENT_NEW_LIMIT) { + return MARK_NEW; + } + elseif ($comment->changed->value > $cache[$cid] && $comment->changed->value > COMMENT_NEW_LIMIT) { + return MARK_UPDATED; + } + return MARK_READ; +} diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml index 948af22..b40c1d2 100644 --- a/core/modules/comment/comment.routing.yml +++ b/core/modules/comment/comment.routing.yml @@ -28,9 +28,24 @@ comment_confirm_delete: _entity_access: 'comment.delete' comment_reply: - pattern: 'comment/reply/{node}/{pid}' + pattern: 'comment/reply/{entity_type}/{entity_id}/{field_name}/{pid}' defaults: _content: '\Drupal\comment\Controller\CommentController::getReplyForm' pid: ~ + _title: 'Add new comment' requirements: - _entity_access: 'node.view' + _access: 'TRUE' + +comment_bundle_list: + pattern: '/admin/structure/comments' + defaults: + _content: 'Drupal\comment\Controller\AdminController::overviewBundles' + requirements: + _permission: 'administer comments' + +comment_bundle: + pattern: '/admin/structure/comments/manage/{field_name}' + defaults: + _content: 'Drupal\comment\Controller\AdminController::bundleInfo' + requirements: + _permission: 'administer comments' diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml index 074d812..6159789 100644 --- a/core/modules/comment/comment.services.yml +++ b/core/modules/comment/comment.services.yml @@ -3,4 +3,14 @@ services: class: Drupal\comment\CommentBreadcrumbBuilder tags: - { name: breadcrumb_builder, priority: 100 } - arguments: ['@string_translation'] + arguments: ['@string_translation', '@plugin.manager.entity'] + + comment.subscriber: + class: Drupal\comment\Routing\RouteSubscriber + arguments: ['@module_handler'] + tags: + - { name: event_subscriber } + + comment.manager: + class: Drupal\comment\CommentManager + arguments: ['@field.info', '@plugin.manager.entity'] diff --git a/core/modules/comment/comment.tokens.inc b/core/modules/comment/comment.tokens.inc index 8002162..8f82d82 100644 --- a/core/modules/comment/comment.tokens.inc +++ b/core/modules/comment/comment.tokens.inc @@ -15,14 +15,14 @@ function comment_token_info() { 'needs-data' => 'comment', ); - // Comment-related tokens for nodes - $node['comment-count'] = array( + // @todo Make this work per field. See http://drupal.org/node/2031903 + $entity['comment-count'] = array( 'name' => t("Comment count"), - 'description' => t("The number of comments posted on a node."), + 'description' => t("The number of comments posted on an entity."), ); - $node['comment-count-new'] = array( + $entity['comment-count-new'] = array( 'name' => t("New comment count"), - 'description' => t("The number of comments posted on a node since the reader last viewed it."), + 'description' => t("The number of comments posted on an entity since the reader last viewed it."), ); // Core comment tokens @@ -79,9 +79,16 @@ function comment_token_info() { 'description' => t("The comment's parent, if comment threading is active."), 'type' => 'comment', ); + $comment['entity'] = array( + 'name' => t("Entity"), + 'description' => t("The entity the comment was posted to."), + 'type' => 'entity', + ); + // Support legacy comment node tokens, since tokes are embedded in user data + // and can't be upgraded directly. $comment['node'] = array( 'name' => t("Node"), - 'description' => t("The node the comment was posted to."), + 'description' => t("The node the comment was posted to (deprecated)."), 'type' => 'node', ); $comment['author'] = array( @@ -93,7 +100,7 @@ function comment_token_info() { return array( 'types' => array('comment' => $type), 'tokens' => array( - 'node' => $node, + 'entity' => $entity, 'comment' => $comment, ), ); @@ -177,8 +184,8 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = case 'parent': if (!empty($comment->pid->target_id)) { - $parent = comment_load($comment->pid->target_id); - $replacements[$original] = $sanitize ? filter_xss($parent->subject) : $parent->subject; + $parent = entity_load('comment', $comment->pid->target_id); + $replacements[$original] = $sanitize ? filter_xss($parent->subject->value) : $parent->subject->value; } break; @@ -190,17 +197,36 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = $replacements[$original] = format_date($comment->changed->value, 'medium', '', NULL, $langcode); break; - case 'node': - $node = $comment->nid->entity; - $title = $node->label(); + case 'entity': + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $title = $entity->label(); $replacements[$original] = $sanitize ? filter_xss($title) : $title; break; + + case 'node': + // Support legacy comment node tokens, since tokes are embedded in + // user data and can't be upgraded directly. + // @todo Remove in Drupal 9, see https://drupal.org/node/2031901. + if ($comment->entity_type->value == 'node') { + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $title = $entity->label(); + $replacements[$original] = $sanitize ? filter_xss($title) : $title; + } + else { + $replacements[$original] = NULL; + } + break; } } // Chained token relationships. - if ($node_tokens = $token_service->findwithPrefix($tokens, 'node')) { - $node = $comment->nid->entity; + if ($entity_tokens = $token_service->findwithPrefix($tokens, 'entity')) { + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $replacements += $token_service->generate($comment->entity_type->value, $entity_tokens, array($comment->entity_type->value => $entity), $options); + } + + if (($node_tokens = $token_service->findwithPrefix($tokens, 'node')) && $comment->entity_type->value == 'node') { + $node = entity_load($comment->entity_type->value, $comment->entity_id->value); $replacements += $token_service->generate('node', $node_tokens, array('node' => $node), $options); } @@ -220,17 +246,24 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = $replacements += $token_service->generate('user', $author_tokens, array('user' => $account), $options); } } - elseif ($type == 'node' & !empty($data['node'])) { - $node = $data['node']; + elseif ($type == 'entity' & !empty($data['entity'])) { + $entity = $data['entity']; foreach ($tokens as $name => $original) { switch($name) { case 'comment-count': - $replacements[$original] = $node->comment_count; + $count = 0; + $fields = array_keys(Drupal::service('comment.manager')->getFields($entity->entityType())); + $definitions = array_keys($entity->getPropertyDefinitions()); + $valid_fields = array_intersect($fields, $definitions); + foreach ($valid_fields as $field_name) { + $count += $entity->get($field_name)->comment_count; + } + $replacements[$original] = $count; break; case 'comment-count-new': - $replacements[$original] = comment_num_new($node->id()); + $replacements[$original] = comment_num_new($entity->id(), $entity->entityType()); break; } } diff --git a/core/modules/comment/comment.views.inc b/core/modules/comment/comment.views.inc index 66e2083..97356cc 100644 --- a/core/modules/comment/comment.views.inc +++ b/core/modules/comment/comment.views.inc @@ -98,7 +98,7 @@ function comment_views_data() { 'help' => t('Hostname of user that posted the comment.'), 'field' => array( 'id' => 'standard', - ), + ), 'filter' => array( 'id' => 'string', ), @@ -115,7 +115,7 @@ function comment_views_data() { 'help' => t('E-mail of user that posted the comment. Will be empty if the author is a registered user.'), 'field' => array( 'id' => 'standard', - ), + ), 'filter' => array( 'id' => 'string', ), @@ -300,16 +300,11 @@ function comment_views_data() { ), ); - $data['comment']['nid'] = array( - 'title' => t('Nid'), - 'help' => t('The node ID to which the comment is a reply to.'), - 'relationship' => array( - 'title' => t('Content'), - 'help' => t('The content to which the comment is a reply to.'), - 'base' => 'node', - 'base field' => 'nid', + $data['comment']['entity_id'] = array( + 'title' => t('Entity ID'), + 'help' => t('The Entity ID to which the comment is a reply to.'), + 'field' => array( 'id' => 'standard', - 'label' => t('Content'), ), 'filter' => array( 'id' => 'numeric', @@ -317,11 +312,74 @@ function comment_views_data() { 'argument' => array( 'id' => 'numeric', ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['comment']['entity_type'] = array( + 'title' => t('Entity type'), + 'help' => t('The Entity type to which the comment is a reply to.'), 'field' => array( - 'id' => 'numeric', + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['comment']['field_id'] = array( + 'title' => t('Comment field id'), + 'help' => t('The Field id from which the comment originated.'), + 'field' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', ), ); + $entities_info = Drupal::entityManager()->getDefinitions(); + + // Provide a relationship for each entity type except comment. + foreach ($entities_info as $type => $entity_info) { + if ($type == 'comment' || empty($entity_info['fieldable']) || !isset($entity_info['base_table'])) { + continue; + } + if ($fields = Drupal::service('comment.manager')->getFields($type)) { + $data['comment'][$type] = array( + 'relationship' => array( + 'title' => $entity_info['label'], + 'help' => t('The @entity_type to which the comment is a reply to.', array('@entity_type' => $entity_info['label'])), + 'base' => $entity_info['base_table'], + 'base field' => $entity_info['entity_keys']['id'], + 'relationship field' => 'entity_id', + 'id' => 'standard', + 'label' => $entity_info['label'], + 'extra' => array( + array( + 'field' => 'entity_type', + 'value' => $type, + 'table' => 'comment' + ), + ), + ), + ); + } + } + $data['comment']['uid'] = array( 'title' => t('Author uid'), 'help' => t('If you need more fields than the uid add the comment: author relationship'), @@ -372,18 +430,31 @@ function comment_views_data() { // Define the base group of this table. Fields that don't have a group defined // will go into this field by default. - $data['node_comment_statistics']['table']['group'] = t('Content'); - - // Explain how this table joins to others. - $data['node_comment_statistics']['table']['join'] = array( - 'node' => array( - 'type' => 'INNER', - 'left_field' => 'nid', - 'field' => 'nid', - ), - ); + $data['comment_entity_statistics']['table']['group'] = t('Comment Statistics'); + + // Provide a relationship for each entity type except comment. + foreach ($entities_info as $type => $entity_info) { + if ($type == 'comment' || empty($entity_info['fieldable']) || !isset($entity_info['base_table'])) { + continue; + } + // @todo As you can't have multiple joins this join doesn't use the field_id yet. + if (Drupal::service('comment.manager')->getFields($type)) { + // Explain how this table joins to others. + $data['comment_entity_statistics']['table']['join'][$entity_info['base_table']] = array( + 'type' => 'INNER', + 'left_field' => $entity_info['entity_keys']['id'], + 'field' => 'entity_id', + 'extra' => array( + array( + 'field' => 'entity_type', + 'value' => $type, + ), + ), + ); + } + } - $data['node_comment_statistics']['last_comment_timestamp'] = array( + $data['comment_entity_statistics']['last_comment_timestamp'] = array( 'title' => t('Last comment time'), 'help' => t('Date and time of when the last comment was posted.'), 'field' => array( @@ -397,22 +468,22 @@ function comment_views_data() { ), ); - $data['node_comment_statistics']['last_comment_name'] = array( + $data['comment_entity_statistics']['last_comment_name'] = array( 'title' => t("Last comment author"), 'help' => t('The name of the author of the last posted comment.'), 'field' => array( - 'id' => 'comment_ncs_last_comment_name', + 'id' => 'comment_ces_last_comment_name', 'no group by' => TRUE, ), 'sort' => array( - 'id' => 'comment_ncs_last_comment_name', + 'id' => 'comment_ces_last_comment_name', 'no group by' => TRUE, ), ); - $data['node_comment_statistics']['comment_count'] = array( + $data['comment_entity_statistics']['comment_count'] = array( 'title' => t('Comment count'), - 'help' => t('The number of comments a node has.'), + 'help' => t('The number of comments an entity has.'), 'field' => array( 'id' => 'numeric', ), @@ -427,28 +498,28 @@ function comment_views_data() { ), ); - $data['node_comment_statistics']['last_updated'] = array( + $data['comment_entity_statistics']['last_updated'] = array( 'title' => t('Updated/commented date'), - 'help' => t('The most recent of last comment posted or node updated time.'), + 'help' => t('The most recent of last comment posted or entity updated time.'), 'field' => array( - 'id' => 'comment_ncs_last_updated', + 'id' => 'comment_ces_last_updated', 'no group by' => TRUE, ), 'sort' => array( - 'id' => 'comment_ncs_last_updated', + 'id' => 'comment_ces_last_updated', 'no group by' => TRUE, ), 'filter' => array( - 'id' => 'comment_ncs_last_updated', + 'id' => 'comment_ces_last_updated', ), ); - $data['node_comment_statistics']['cid'] = array( + $data['comment_entity_statistics']['cid'] = array( 'title' => t('Last comment CID'), - 'help' => t('Display the last comment of a node'), + 'help' => t('Display the last comment of an entity'), 'relationship' => array( 'title' => t('Last comment'), - 'help' => t('The last comment of a node.'), + 'help' => t('The last comment of an entity.'), 'group' => t('Comment'), 'base' => 'comment', 'base field' => 'cid', @@ -457,9 +528,9 @@ function comment_views_data() { ), ); - $data['node_comment_statistics']['last_comment_uid'] = array( + $data['comment_entity_statistics']['last_comment_uid'] = array( 'title' => t('Last comment uid'), - 'help' => t('The User ID of the author of the last comment of a node.'), + 'help' => t('The User ID of the author of the last comment of an entity.'), 'relationship' => array( 'title' => t('Last comment author'), 'base' => 'users', @@ -478,76 +549,130 @@ function comment_views_data() { ), ); - return $data; -} - -/** - * Implements hook_views_data_alter(). - */ -function comment_views_data_alter(&$data) { - // new comments - $data['node']['new_comments'] = array( - 'title' => t('New comments'), - 'help' => t('The number of new comments on the node.'), + $data['comment_entity_statistics']['entity_type'] = array( + 'title' => t('Entity type'), + 'help' => t('The Entity type to which the comment is a reply to.'), 'field' => array( - 'id' => 'node_new_comments', - 'no group by' => TRUE, + 'id' => 'standard', ), - ); - - $data['node']['comments_link'] = array( - 'field' => array( - 'title' => t('Add comment link'), - 'help' => t('Display the standard add comment link used on regular nodes, which will only display if the viewing user has access to add a comment.'), - 'id' => 'comment_node_link', + 'filter' => array( + 'id' => 'string', ), - ); - - $data['node_field_data']['comment'] = array( - 'title' => t('Comment status'), - 'help' => t('Whether comments are enabled or disabled on the node.'), - 'field' => array( - 'id' => 'node_comment', + 'argument' => array( + 'id' => 'string', ), 'sort' => array( 'id' => 'standard', ), + ); + $data['comment_entity_statistics']['field_id'] = array( + 'title' => t('Comment field id'), + 'help' => t('The Field id from which the comment originated.'), + 'field' => array( + 'id' => 'standard', + ), 'filter' => array( - 'id' => 'node_comment', + 'id' => 'string', ), - ); - - $data['node_field_data']['uid_touch'] = array( - 'title' => t('User posted or commented'), - 'help' => t('Display nodes only if a user posted the node or commented on the node.'), 'argument' => array( - 'field' => 'uid', - 'name table' => 'users', - 'name field' => 'name', - 'id' => 'argument_comment_user_uid', - 'no group by' => TRUE, + 'id' => 'string', ), - 'filter' => array( - 'field' => 'uid', - 'name table' => 'users', - 'name field' => 'name', - 'id' => 'comment_user_uid', + 'sort' => array( + 'id' => 'standard', ), ); - $data['node']['cid'] = array( - 'title' => t('Comments of the node'), - 'help' => t('Relate all comments on the node. This will create 1 duplicate record for every comment. Usually if you need this it is better to create a comment view.'), - 'relationship' => array( - 'group' => t('Comment'), - 'label' => t('Comments'), - 'base' => 'comment', - 'base field' => 'nid', - 'relationship field' => 'nid', - 'id' => 'standard', + return $data; +} + +/** + * Implements hook_views_data_alter(). + */ +function comment_views_data_alter(&$data) { + // New comments are only supported for node table because it requires the + // history table. + $data['node']['new_comments'] = array( + 'title' => t('New comments'), + 'help' => t('The number of new comments on the node.'), + 'field' => array( + 'id' => 'node_new_comments', + 'no group by' => TRUE, ), ); + // Provide a integration for each entity type except comment. + foreach (Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) { + if ($entity_type == 'comment' || empty($entity_info['fieldable']) || !isset($entity_info['base_table'])) { + continue; + } + $fields = Drupal::service('comment.manager')->getFields($entity_type); + $base_table = $entity_info['base_table']; + $args = array('@entity_type' => $entity_type); + + if ($fields) { + $data[$base_table]['comments_link'] = array( + 'field' => array( + 'title' => t('Add comment link'), + 'help' => t('Display the standard add comment link used on regular @entity_type, which will only display if the viewing user has access to add a comment.', $args), + 'id' => 'comment_entity_link', + ), + ); + + if ($entity_info['id'] == 'node') { + // Node properties lives in data_table. + $table = $entity_info['data_table']; + } + else { + $table = $base_table; + } + $data[$table]['uid_touch'] = array( + 'title' => t('User posted or commented'), + 'help' => t('Display nodes only if a user posted the @entity_type or commented on the @entity_type.', $args), + 'argument' => array( + 'field' => 'uid', + 'name table' => 'users', + 'name field' => 'name', + 'id' => 'argument_comment_user_uid', + 'no group by' => TRUE, + 'entity_type' => $entity_type, + 'entity_id' => $entity_info['entity_keys']['id'], + ), + 'filter' => array( + 'field' => 'uid', + 'name table' => 'users', + 'name field' => 'name', + 'id' => 'comment_user_uid', + 'entity_type' => $entity_type, + 'entity_id' => $entity_info['entity_keys']['id'], + ), + ); + + foreach ($fields as $field_name => $field) { + $data[$base_table][$field_name . '_cid'] = array( + 'title' => t('Comments of the @entity_type using field: @field_name', $args + array('@field_name' => $field_name)), + 'help' => t('Relate all comments on the @entity_type. This will create 1 duplicate record for every comment. Usually if you need this it is better to create a comment view.', $args), + 'relationship' => array( + 'group' => t('Comment'), + 'label' => t('Comments'), + 'base' => 'comment', + 'base field' => 'entity_id', + 'relationship field' => $entity_info['entity_keys']['id'], + 'id' => 'standard', + 'extra' => array( + array( + 'field' => 'entity_type', + 'value' => $entity_type, + ), + array( + 'field' => 'field_id', + 'value' => $entity_type . '.' . $field_name, + ), + ), + ), + ); + } + } + } } /** diff --git a/core/modules/comment/lib/Drupal/comment/CommentBreadcrumbBuilder.php b/core/modules/comment/lib/Drupal/comment/CommentBreadcrumbBuilder.php index 38dd91f..1e53724 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentBreadcrumbBuilder.php +++ b/core/modules/comment/lib/Drupal/comment/CommentBreadcrumbBuilder.php @@ -8,6 +8,7 @@ namespace Drupal\comment; use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Entity\EntityManager; use Drupal\Core\StringTranslation\TranslationManager; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -24,24 +25,42 @@ class CommentBreadcrumbBuilder implements BreadcrumbBuilderInterface { protected $translation; /** + * Stores the Entity manager service. + * + * @var \Drupal\Core\Entity\EntityManager + */ + protected $entityManager; + + /** * Constructs a CommentBreadcrumbBuilder object. * * @param \Drupal\Core\StringTranslation\TranslationManager $translation * The translation manager. + * @param \Drupal\Core\Entity\EntityManager + * The entity manager. */ - public function __construct(TranslationManager $translation) { + public function __construct(TranslationManager $translation, EntityManager $entity_manager) { $this->translation = $translation; + $this->entityManager = $entity_manager; } /** * {@inheritdoc} */ public function build(array $attributes) { - if (isset($attributes[RouteObjectInterface::ROUTE_NAME]) && $attributes[RouteObjectInterface::ROUTE_NAME] == 'comment_reply' && isset($attributes['node'])) { - $node = $attributes['node']; - $uri = $node->uri(); + if (!empty($attributes[RouteObjectInterface::ROUTE_NAME]) && $attributes[RouteObjectInterface::ROUTE_NAME] == 'comment_reply' + && isset($attributes['entity_type']) + && isset($attributes['entity_id']) + && isset($attributes['field_name']) + ) { $breadcrumb[] = l($this->t('Home'), NULL); - $breadcrumb[] = l($node->label(), $uri['path']); + // @todo clean-up. + $entity = entity_load($attributes['entity_type'], $attributes['entity_id']); + $uri = $entity->uri(); + $breadcrumb[] = l($entity->label(), $uri['path'], $uri['options']); + } + + if (!empty($breadcrumb)) { return $breadcrumb; } } diff --git a/core/modules/comment/lib/Drupal/comment/CommentFieldName.php b/core/modules/comment/lib/Drupal/comment/CommentFieldName.php new file mode 100644 index 0000000..6162d78 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentFieldName.php @@ -0,0 +1,41 @@ + 'string', + 'label' => t('String value'), + 'class' => '\Drupal\comment\CommentFieldNameValue', + 'computed' => TRUE, + ); + } + return static::$propertyDefinitions; + } +} diff --git a/core/modules/comment/lib/Drupal/comment/CommentFieldNameValue.php b/core/modules/comment/lib/Drupal/comment/CommentFieldNameValue.php new file mode 100644 index 0000000..c27669a --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentFieldNameValue.php @@ -0,0 +1,53 @@ +value)) { + if (!isset($this->parent)) { + throw new InvalidArgumentException('Computed properties require context for computation.'); + } + $field = $this->parent->getParent(); + $entity = $field->getParent(); + // Field id is of the form {entity_type}__{field_name}. We set the + // optional limit param to explode() in case the user adds a field with __ + // in the name. + $parts = explode('__', $entity->field_id->value, 2); + if ($parts && count($parts) == 2) { + $this->value = end($parts); + } + } + return $this->value; + } + + /** + * Implements \Drupal\Core\TypedData\TypedDataInterface::setValue(). + */ + public function setValue($value, $notify = TRUE) { + if (isset($value)) { + $this->field_name = $value; + // Also set the field id. + $field = $this->parent->getParent(); + $entity = $field->getParent(); + $entity->field_id = $entity->entity_type->value . '__' . $value; + } + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index 750f748..58eb837 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -22,13 +22,15 @@ class CommentFormController extends EntityFormControllerNG { public function form(array $form, array &$form_state) { global $user; $comment = $this->entity; - $node = $comment->nid->entity; - // Use #comment-form as unique jump target, regardless of node type. + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $instance = field_info_instance($comment->entity_type->value, $comment->field_name->value, $entity->bundle()); + + // Use #comment-form as unique jump target, regardless of entity type. $form['#id'] = drupal_html_id('comment_form'); - $form['#theme'] = array('comment_form__node_' . $node->getType(), 'comment_form'); + $form['#theme'] = array('comment_form__' . $comment->entity_type->value . '__' . $entity->bundle() . '__' . $comment->field_name->value, 'comment_form'); - $anonymous_contact = variable_get('comment_anonymous_' . $node->getType(), COMMENT_ANONYMOUS_MAYNOT_CONTACT); + $anonymous_contact = $instance->getFieldSetting('anonymous'); $is_admin = $comment->id() && user_access('administer comments'); if (!$user->isAuthenticated() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) { @@ -37,9 +39,9 @@ public function form(array $form, array &$form_state) { } // If not replying to a comment, use our dedicated page callback for new - // comments on nodes. - if (!$comment->id() && !$comment->pid->target_id) { - $form['#action'] = url('comment/reply/' . $comment->nid->target_id); + // comments on entities. + if (!$comment->id() && empty($comment->pid->target_id)) { + $form['#action'] = url('comment/reply/' . $comment->entity_type->value . '/' . $comment->entity_id->value . '/' . $comment->field_name->value); } if (isset($form_state['comment_preview'])) { @@ -145,7 +147,7 @@ public function form(array $form, array &$form_state) { '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => $comment->subject->value, - '#access' => variable_get('comment_subject_field_' . $node->getType(), 1) == 1, + '#access' => $instance['settings']['subject'], ); // Used for conditional validation of author fields. @@ -163,7 +165,7 @@ public function form(array $form, array &$form_state) { // Add internal comment properties. $original = $comment->getUntranslated(); - foreach (array('cid', 'pid', 'nid', 'uid', 'node_type', 'langcode') as $key) { + foreach (array('cid', 'pid', 'entity_id', 'entity_type', 'field_id', 'uid', 'langcode') as $key) { $key_name = key($comment->$key->offsetGet(0)->getPropertyDefinitions()); $form[$key] = array('#type' => 'value', '#value' => $original->$key->{$key_name}); } @@ -177,8 +179,9 @@ public function form(array $form, array &$form_state) { protected function actions(array $form, array &$form_state) { $element = parent::actions($form, $form_state); $comment = $this->entity; - $node = $comment->nid->entity; - $preview_mode = variable_get('comment_preview_' . $node->getType(), DRUPAL_OPTIONAL); + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $instance = field_info_instance($comment->entity_type->value, $comment->field_name->value, $entity->bundle()); + $preview_mode = $instance['settings']['preview']; // No delete action on the comment form. unset($element['delete']); @@ -315,10 +318,15 @@ public function preview(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { - $node = node_load($form_state['values']['nid']); + $entity = entity_load($form_state['values']['entity_type'], $form_state['values']['entity_id']); $comment = $this->entity; + $field_name = $this->entity->field_name->value; + $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()); + $items = field_get_items($entity, $field_name); + $status = reset($items); + $uri = $entity->uri(); - if (user_access('post comments') && (user_access('administer comments') || $node->comment->value == COMMENT_NODE_OPEN)) { + if (user_access('post comments') && (user_access('administer comments') || $status['status'] == COMMENT_OPEN)) { // Save the anonymous user information to a cookie for reuse. if (user_is_anonymous()) { user_cookie_save(array_intersect_key($form_state['values'], array_flip(array('name', 'mail', 'homepage')))); @@ -341,18 +349,18 @@ public function save(array $form, array &$form_state) { } $query = array(); // Find the current display page for this comment. - $page = comment_get_display_page($comment->id(), $node->getType()); + $page = comment_get_display_page($comment->id(), $instance); if ($page > 0) { $query['page'] = $page; } - // Redirect to the newly posted comment. - $redirect = array('node/' . $node->id(), array('query' => $query, 'fragment' => 'comment-' . $comment->id())); + // Redirect to the newly posted comment. @todo up to here. + $redirect = array($uri['path'], array('query' => $query, 'fragment' => 'comment-' . $comment->id()) + $uri['options']); } else { watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject->value), WATCHDOG_WARNING); drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject->value)), 'error'); - // Redirect the user to the node they are commenting on. - $redirect = 'node/' . $node->id(); + // Redirect the user to the entity they are commenting on. + $redirect = $uri['path']; } $form_state['redirect'] = $redirect; // Clear the block and page caches so that anonymous users see the comment diff --git a/core/modules/comment/lib/Drupal/comment/CommentInterface.php b/core/modules/comment/lib/Drupal/comment/CommentInterface.php index 456b957..fb9c043 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentInterface.php +++ b/core/modules/comment/lib/Drupal/comment/CommentInterface.php @@ -24,4 +24,5 @@ * UrlGenerator::generateFromPath(). */ public function permalink(); + } diff --git a/core/modules/comment/lib/Drupal/comment/CommentManager.php b/core/modules/comment/lib/Drupal/comment/CommentManager.php new file mode 100644 index 0000000..c1e0c1c --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentManager.php @@ -0,0 +1,217 @@ +fieldInfo = $field_info; + $this->entityManager = $entity_manager; + } + + /** + * Utility function to return URI of the comment's parent entity. + * + * @param \Drupal\comment\CommentInterface $comment + * The comment entity. + * + * @return array + * An array returned by \Drupal\Core\Entity\EntityInterface::uri(). + */ + public function getParentEntityUri(CommentInterface $comment) { + return $this->entityManager + ->getStorageController($comment->entity_type->value) + ->load($comment->entity_id->value) + ->uri(); + } + + /** + * Utility function to return an array of comment fields. + * + * @param string $entity_type + * The entity type to return fields which are attached on. + * + * @return array + * An array of comment field map definitions, keyed by field name. Each + * value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with entity + * types as keys and the array of bundle names as values. + * + * @see field_info_field_map() + */ + public function getFields($entity_type = NULL) { + $map = $this->getAllFields(); + if (!isset($map[$entity_type])) { + return array(); + } + return $map[$entity_type]; + } + + /** + * Utility function to return all comment fields. + */ + public function getAllFields() { + $map = $this->fieldInfo->getFieldMap(); + // Build a list of comment fields only. + $comment_fields = array(); + foreach ($map as $entity_type => $data) { + foreach ($data as $field_name => $field_info) { + if ($field_info['type'] == 'comment') { + $comment_fields[$entity_type][$field_name] = $field_info; + } + } + } + return $comment_fields; + } + + /** + * Utility method to add the default comment field to an entity. + * + * Attaches a comment field named 'comment' to the given entity type and bundle. + * Largely replicates the default behaviour in Drupal 7 and earlier. + * + * @param string $entity_type + * The entity type to attach the default comment field to. + * @param string $bundle + * The bundle to attach the default comment field instance to. + * @param string $field_name + * (optional) Field name to use for the comment field. Defaults to 'comment'. + * @param int $default_value + * (optional) Default value, one of COMMENT_HIDDEN, COMMENT_OPEN, + * COMMENT_CLOSED. Defaults to COMMENT_OPEN. + */ + public function addDefaultField($entity_type, $bundle, $field_name = 'comment', $default_value = COMMENT_OPEN) { + // Make sure field doesn't already exist. + if (!$this->fieldInfo->getField($entity_type, $field_name)) { + // Add a default comment field for existing node comments. + $field = $this->entityManager->getStorageController('field_entity')->create(array( + 'entity_type' => $entity_type, + 'name' => $field_name, + 'type' => 'comment', + 'translatable' => '0', + )); + // Create the field. + $field->save(); + } + // Make sure the instance doesn't already exist. + if (!$this->fieldInfo->getInstance($entity_type, $bundle, $field_name)) { + $instance = $this->entityManager->getStorageController('field_instance')->create(array( + 'label' => 'Comment settings', + 'description' => '', + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'required' => 1, + 'default_value' => array(array('status' => $default_value)), + )); + $instance->save(); + + // Assign widget settings for the 'default' form mode. + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent($field_name, array( + 'type' => 'comment_default', + 'weight' => 20, + )) + ->save(); + + // Set default to display comment list. + entity_get_display($entity_type, $bundle, 'default') + ->setComponent($field_name, array( + 'label' => 'hidden', + 'type' => 'comment_default', + 'weight' => 20, + )) + ->save(); + } + $this->addBodyField($entity_type, $field_name); + } + + /** + * Creates a comment_body field instance. + * + * @param string $entity_type + * The type of the entity to which the comment field attached. + * @param string $field_name + * Name of the comment field to add comment_body field. + */ + public function addBodyField($entity_type, $field_name) { + // Create the field if needed. + $field = $this->entityManager->getStorageController('field_entity')->load('comment.comment_body'); + if (!$field) { + $field = $this->entityManager->getStorageController('field_entity')->create(array( + 'name' => 'comment_body', + 'type' => 'text_long', + 'entity_type' => 'comment', + )); + $field->save(); + } + // Create the instance if needed, field name defaults to 'comment'. + $comment_bundle = $entity_type . '__' . $field_name; + $field_instance = $this->entityManager + ->getStorageController('field_instance') + ->load("comment.$comment_bundle.comment_body"); + if (!$field_instance) { + // Attaches the body field by default. + $field_instance = $this->entityManager->getStorageController('field_instance')->create(array( + 'field_name' => 'comment_body', + 'label' => 'Comment', + 'entity_type' => 'comment', + 'bundle' => $comment_bundle, + 'settings' => array('text_processing' => 1), + 'required' => TRUE, + )); + $field_instance->save(); + + // Assign widget settings for the 'default' form mode. + entity_get_form_display('comment', $comment_bundle, 'default') + ->setComponent('comment_body', array( + 'type' => 'text_textarea', + )) + ->save(); + + // Assign display settings for the 'default' view mode. + entity_get_display('comment', $comment_bundle, 'default') + ->setComponent('comment_body', array( + 'label' => 'hidden', + 'type' => 'text_default', + 'weight' => 0, + )) + ->save(); + } + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/CommentNewValue.php b/core/modules/comment/lib/Drupal/comment/CommentNewValue.php index 1295776..47aed52 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentNewValue.php +++ b/core/modules/comment/lib/Drupal/comment/CommentNewValue.php @@ -28,7 +28,7 @@ public function getValue() { } $field = $this->parent->getParent(); $entity = $field->getParent(); - $this->value = node_mark($entity->nid->target_id, $entity->changed->value); + $this->value = comment_mark($entity); } return $this->value; } diff --git a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php index 641c566..0d22692 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php @@ -20,7 +20,7 @@ class CommentRenderController extends EntityRenderController { * Overrides Drupal\Core\Entity\EntityRenderController::buildContent(). * * In addition to modifying the content key on entities, this implementation - * will also set the node key which all comments carry. + * will also set the comment entity key which all comments carry. */ public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) { $return = array(); @@ -37,22 +37,27 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang parent::buildContent($entities, $displays, $view_mode, $langcode); - // Load all nodes of all comments at once. - $nids = array(); + // Load all the entities that have comments attached. + $comment_entity_ids = array(); + $comment_entities = array(); foreach ($entities as $entity) { - $nids[$entity->nid->target_id] = $entity->nid->target_id; + $comment_entity_ids[$entity->entity_type->value][] = $entity->entity_id->value; + } + // Load entities in bulk. This is more performant than using + // $comment->entity_id->value as we can load them in bulk per type. + foreach ($comment_entity_ids as $entity_type => $entity_ids) { + $comment_entities[$entity_type] = entity_load_multiple($entity_type, $entity_ids); } - $nodes = node_load_multiple($nids); foreach ($entities as $entity) { - if (isset($nodes[$entity->nid->target_id])) { - $node = $nodes[$entity->nid->target_id]; + if (isset($comment_entities[$entity->entity_type->value][$entity->entity_id->value])) { + $comment_entity = $comment_entities[$entity->entity_type->value][$entity->entity_id->value]; } else { - throw new \InvalidArgumentException(t('Invalid node for comment.')); + throw new \InvalidArgumentException(t('Invalid entity for comment.')); } - $entity->content['#node'] = $node; - $entity->content['#theme'] = 'comment__node_' . $node->bundle(); + $entity->content['#entity'] = $entity; + $entity->content['#theme'] = 'comment__' . $entity->field_id->value . '__' . $comment_entity->bundle(); $entity->content['links'] = array( '#theme' => 'links__comment', '#pre_render' => array('drupal_pre_render_links'), @@ -61,8 +66,9 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang if (empty($entity->in_preview)) { $entity->content['links'][$this->entityType] = array( '#theme' => 'links__comment__comment', - // The "node" property is specified to be present, so no need to check. - '#links' => comment_links($entity, $node), + // The "entity" property is specified to be present, so no need to + // check. + '#links' => comment_links($entity, $comment_entity, $entity->field_name->value), '#attributes' => array('class' => array('links', 'inline')), ); } @@ -76,8 +82,10 @@ protected function alterBuild(array &$build, EntityInterface $comment, EntityDis parent::alterBuild($build, $comment, $display, $view_mode, $langcode); if (empty($comment->in_preview)) { $prefix = ''; + $comment_entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $instance = field_info_instance($comment_entity->entityType(), $comment->field_name->value, $comment_entity->bundle()); $is_threaded = isset($comment->divs) - && variable_get('comment_default_mode_' . $comment->bundle(), COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED; + && $instance['settings']['default_mode'] == COMMENT_MODE_THREADED; // Add 'new' anchor if needed. if (!empty($comment->first_new)) { diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php index 3dc7f2c..bb2c958 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -9,8 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\DatabaseStorageControllerNG; -use Drupal\Component\Uuid\Uuid; -use LogicException; +use Drupal\Core\Entity\EntityChangedInterface; /** * Defines the controller class for comments. @@ -30,9 +29,7 @@ class CommentStorageController extends DatabaseStorageControllerNG implements Co */ protected function buildQuery($ids, $revision_id = FALSE) { $query = parent::buildQuery($ids, $revision_id); - // Specify additional fields from the user and node tables. - $query->innerJoin('node', 'n', 'base.nid = n.nid'); - $query->addField('n', 'type', 'node_type'); + // Specify additional fields from the user table. $query->innerJoin('users', 'u', 'base.uid = u.uid'); // @todo: Move to a computed 'name' field instead. $query->addField('u', 'name', 'registered_name'); @@ -44,10 +41,8 @@ protected function buildQuery($ids, $revision_id = FALSE) { */ protected function attachLoad(&$records, $load_revision = FALSE) { // Prepare standard comment fields. - foreach ($records as $key => $record) { + foreach ($records as $key => &$record) { $record->name = $record->uid ? $record->registered_name : $record->name; - $record->node_type = 'comment_node_' . $record->node_type; - $records[$key] = $record; } parent::attachLoad($records, $load_revision); } @@ -55,25 +50,36 @@ protected function attachLoad(&$records, $load_revision = FALSE) { /** * {@inheritdoc} */ - public function updateNodeStatistics($nid) { + public function updateEntityStatistics(CommentInterface $comment) { + global $user; // Allow bulk updates and inserts to temporarily disable the - // maintenance of the {node_comment_statistics} table. - if (!variable_get('comment_maintain_node_statistics', TRUE)) { + // maintenance of the {comment_entity_statistics} table. + if (!\Drupal::state()->get('comment.maintain_entity_statistics')) { return; } - $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND status = :status', array( - ':nid' => $nid, - ':status' => COMMENT_PUBLISHED, - ))->fetchField(); + $query = $this->database->select('comment', 'c'); + $query->addExpression('COUNT(cid)'); + $count = $query->condition('c.entity_id', $comment->entity_id->value) + ->condition('c.entity_type', $comment->entity_type->value) + ->condition('c.field_id', $comment->field_id->value) + ->condition('c.status', COMMENT_PUBLISHED) + ->execute() + ->fetchField(); if ($count > 0) { // Comments exist. - $last_reply = db_query_range('SELECT cid, name, changed, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array( - ':nid' => $nid, - ':status' => COMMENT_PUBLISHED, - ))->fetchObject(); - db_update('node_comment_statistics') + $last_reply = $this->database->select('comment', 'c') + ->fields('c', array('cid', 'name', 'changed', 'uid')) + ->condition('c.entity_id', $comment->entity_id->value) + ->condition('c.entity_type', $comment->entity_type->value) + ->condition('c.field_id', $comment->field_id->value) + ->condition('c.status', COMMENT_PUBLISHED) + ->orderBy('c.created', 'DESC') + ->range(0, 1) + ->execute() + ->fetchObject(); + $this->database->update('comment_entity_statistics') ->fields(array( 'cid' => $last_reply->cid, 'comment_count' => $count, @@ -81,21 +87,30 @@ public function updateNodeStatistics($nid) { 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name, 'last_comment_uid' => $last_reply->uid, )) - ->condition('nid', $nid) + ->condition('entity_id', $comment->entity_id->value) + ->condition('entity_type', $comment->entity_type->value) + ->condition('field_id', $comment->field_id->value) ->execute(); } else { // Comments do not exist. - $node = db_query('SELECT uid, created FROM {node_field_data} WHERE nid = :nid LIMIT 1', array(':nid' => $nid))->fetchObject(); - db_update('node_comment_statistics') + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $this->database->update('comment_entity_statistics') ->fields(array( 'cid' => 0, 'comment_count' => 0, - 'last_comment_timestamp' => $node->created, + // Use the created date of the entity if it's set, + // or default to REQUEST_TIME. + 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME, 'last_comment_name' => '', - 'last_comment_uid' => $node->uid, + // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387 + // Get the user ID from the entity if it's set, or default to the + // currently logged in user. + 'last_comment_uid' => $entity->getPropertyDefinition('uid') ? $entity->get('uid')->value : $user->id(), )) - ->condition('nid', $nid) + ->condition('entity_id', $comment->entity_id->value) + ->condition('entity_type', $comment->entity_type->value) + ->condition('field_id', $comment->field_id->value) ->execute(); } } @@ -104,17 +119,27 @@ public function updateNodeStatistics($nid) { * {@inheritdoc} */ public function getMaxThread(EntityInterface $comment) { - return db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField(); + $query = $this->database->select('comment', 'c') + ->condition('entity_id', $comment->entity_id->value) + ->condition('field_id', $comment->field_id->value) + ->condition('entity_type', $comment->entity_type->value); + $query->addExpression('MAX(thread)', 'thread'); + return $query->execute() + ->fetchField(); } /** * {@inheritdoc} */ public function getMaxThreadPerThread(EntityInterface $comment) { - return $this->database->query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array( - ':thread' => rtrim($comment->pid->entity->thread->value, '/') . '.%', - ':nid' => $comment->nid->target_id, - ))->fetchField(); + $query = $this->database->select('comment', 'c') + ->condition('entity_id', $comment->entity_id->value) + ->condition('field_id', $comment->field_id->value) + ->condition('entity_type', $comment->entity_type->value) + ->condition('thread', $comment->pid->entity->thread->value . '.%', 'LIKE'); + $query->addExpression('MAX(thread)', 'thread'); + return $query->execute() + ->fetchField(); } /** diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php index 00a6edb..69ba909 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageControllerInterface; +use Drupal\comment\CommentInterface; /** * Defines a common interface for comment entity controller classes. @@ -51,19 +52,19 @@ public function getChildCids(array $comments); /** * Updates the comment statistics for a given node. * - * The {node_comment_statistics} table has the following fields: - * - last_comment_timestamp: The timestamp of the last comment for this node, - * or the node created timestamp if no comments exist for the node. + * The {comment_entity_statistics} table has the following fields: + * - last_comment_timestamp: The timestamp of the last comment for the entity, + * or the entity created timestamp if no comments exist for the entity. * - last_comment_name: The name of the anonymous poster for the last comment. * - last_comment_uid: The user ID of the poster for the last comment for - * this node, or the node author's user ID if no comments exist for the - * node. + * this entity, or the entity author's user ID if no comments exist for the + * entity. * - comment_count: The total number of approved/published comments on this - * node. + * entity. * - * @param $nid - * The node ID. + * @param \Drupal\comment\CommentInterface $comment + * The comment being saved. */ - public function updateNodeStatistics($nid); + public function updateEntityStatistics(CommentInterface $comment); } diff --git a/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php b/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php new file mode 100644 index 0000000..06bfbb1 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php @@ -0,0 +1,215 @@ +get('plugin.manager.entity'), + $container->get('module_handler'), + $container->get('field.info'), + $container->get('comment.manager') + ); + } + + /** + * Constructs an AdminController object. + * + * @param \Drupal\Core\Entity\EntityManager $entity_manager + * The entity manager service. + * @param \Drupal\Core\Extension\ModuleHandler $module_handler + * The module handler service. + * @param \Drupal\field\FieldInfo $field_info + * The field info service. + */ + public function __construct(EntityManager $entity_manager, ModuleHandler $module_handler, FieldInfo $field_info, CommentManager $comment_manager) { + $this->entityManager = $entity_manager; + $this->moduleHandler = $module_handler; + $this->fieldInfo = $field_info; + $this->commentManager = $comment_manager; + } + + /** + * Returns an overview of comment fields in use on the site. + * + * @return array + * A renderable array containing a list of comment fields, the entity + * type and bundle combinations on which they are in use and various + * operation links for configuring each field. + */ + public function overviewBundles() { + $header = array( + 'field_name' => t('Field name'), + 'usage' => array( + 'data' => t('Used in'), + 'class' => array(RESPONSIVE_PRIORITY_MEDIUM), + ), + ); + + // Add a column for field UI operations if the Field UI module is enabled. + $field_ui_enabled = $this->moduleHandler->moduleExists('field_ui'); + if ($field_ui_enabled) { + $header['operations'] = t('Operations'); + } + + $entity_bundles = $this->entityManager->getAllBundleInfo(); + $entity_types = $this->entityManager->getDefinitions(); + $rows = array(); + + // Fetch a list of all comment fields. + $fields = $this->commentManager->getAllFields(); + + foreach ($fields as $entity_type => $data) { + foreach ($data as $field_name => $field_info_map) { + $field_info = $this->fieldInfo->getField($entity_type, $field_name); + // Initialize the row. + $row = array( + 'class' => $field_info->get('locked') ? array('field-disabled') : array(''), + ); + $row['data']['field_name']['data'] = $field_info->get('locked') ? t('@field_name (Locked)', array('@field_name' => $field_name)) : String::checkPlain($field_name); + + $row['data']['usage']['data'] = array( + '#theme' => 'item_list', + '#title' => String::checkPlain($entity_types[$entity_type]['label']), + '#items' => array(), + ); + foreach ($field_info_map['bundles'] as $bundle) { + if (isset($entity_bundles[$entity_type][$bundle])) { + // Add the current instance. + if ($field_ui_enabled && ($path = $this->entityManager->getAdminPath($entity_type, $bundle))) { + $row['data']['usage']['data']['#items'][] = l($entity_bundles[$entity_type][$bundle]['label'], $path . '/fields'); + } + else { + $row['data']['usage']['data']['#items'][] = $entity_bundles[$entity_type][$bundle]['label']; + } + } + } + + if ($field_ui_enabled) { + // @todo Check proper permissions for operations. + $links['fields'] = array( + 'title' => t('Manage fields'), + 'href' => 'admin/structure/comments/manage/' . $entity_type . '__' . $field_name . '/fields', + 'weight' => 5, + ); + $links['display'] = array( + 'title' => t('Manage display'), + 'href' => 'admin/structure/comments/manage/' . $entity_type . '__' . $field_name . '/display', + 'weight' => 10, + ); + + $row['data']['operations']['data'] = array( + '#type' => 'operations', + '#links' => $links, + ); + } + $rows[$entity_type . '__' . $field_name] = $row; + } + } + + $build['overview'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('No comment forms available.'), + ); + $build['#title'] = t('Comment forms'); + + return $build; + } + + /** + * Returns an overview of the entity types a comment field is attached to. + * + * @param string $field_name + * The comment field for which the overview is to be displayed. + * + * @return array + * A renderable array containing the list of entity types and bundle + * combinations on which the comment field is in use. + */ + public function bundleInfo($field_name) { + $entity_bundles = $this->entityManager->getAllBundleInfo(); + $entity_types = $this->entityManager->getDefinitions(); + // Add a link to manage entity fields if the Field UI module is enabled. + $field_ui_enabled = $this->moduleHandler->moduleExists('field_ui'); + + // @todo Provide dynamic routing to get entity type and field name. + list($entity_type, $field) = explode('__', $field_name, 2); + $field_info = $this->fieldInfo->getField($entity_type, $field); + // @todo Decide on better UX http://drupal.org/node/1901110 + $build['usage'] = array( + '#theme' => 'item_list', + '#title' => String::checkPlain($entity_types[$entity_type]['label']), + '#items' => array(), + ); + // Loop over all of the entity types to which this comment field is + // attached. + foreach ($field_info->getBundles() as $bundle) { + if (isset($entity_bundles[$entity_type][$bundle])) { + // Add the current instance to the list of bundles. + if ($field_ui_enabled && ($path = $this->entityManager->getAdminPath($entity_type, $bundle))) { + // Add a link to configure the fields on the given bundle and entity + // type combination. + $build['usage']['#items'][] = l($entity_bundles[$entity_type][$bundle]['label'], $path . '/fields'); + } + else { + // Field UI is disabled so fallback to a list of bundle labels + // instead of links to configure fields. + $build['usage']['#items'][] = String::checkPlain($entity_bundles[$entity_type][$bundle]['label']); + } + } + } + + return $build; + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php index d3f4150..0aadbb5 100644 --- a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php +++ b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php @@ -8,11 +8,13 @@ namespace Drupal\comment\Controller; use Drupal\comment\CommentInterface; -use Drupal\comment\Entity\Comment; +use Drupal\comment\CommentManager; +use Drupal\field\FieldInfo; use Drupal\Core\Access\CsrfTokenGenerator; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Node\NodeInterface; +use Drupal\Core\Routing\UrlGeneratorInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -42,24 +44,40 @@ class CommentController extends ControllerBase implements ContainerInjectionInte protected $csrfToken; /** + * Field info service. + * + * @var \Drupal\field\FieldInfo + */ + protected $fieldInfo; + + /** * Constructs a CommentController object. * * @param \Symfony\Component\HttpKernel\HttpKernelInterface $httpKernel * HTTP kernel to handle requests. * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * The CSRF token manager service. + * @param \Drupal\field\FieldInfo $field_info + * Field Info service. */ - public function __construct(HttpKernelInterface $httpKernel, CsrfTokenGenerator $csrf_token) { + public function __construct(HttpKernelInterface $httpKernel, CsrfTokenGenerator $csrf_token, UrlGeneratorInterface $url_generator, FieldInfo $field_info, CommentManager $comment_manager) { $this->httpKernel = $httpKernel; $this->csrfToken = $csrf_token; + $this->urlGenerator = $url_generator; + $this->fieldInfo = $field_info; + $this->commentManager = $comment_manager; } + /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('http_kernel'), - $container->get('csrf_token') + $container->get('csrf_token'), + $container->get('url_generator'), + $container->get('field.info'), + $container->get('comment.manager') ); } @@ -117,15 +135,18 @@ public function commentApprove(Request $request, CommentInterface $comment) { * The comment listing set to the page on which the comment appears. */ public function commentPermalink(Request $request, CommentInterface $comment) { - if ($node = $comment->nid->entity) { - // Check access permissions for the node entity. - if (!$node->access('view')) { + if ($entity = entity_load($comment->entity_type->value, $comment->entity_id->value)) { + // Check access permissions for the entity. + if (!$entity->access('view')) { throw new AccessDeniedHttpException(); } + $instance = $this->fieldInfo->getInstance($entity->entityType(), $entity->bundle(), $comment->field_name->value); + // Find the current display page for this comment. - $page = comment_get_display_page($comment->id(), $node->getType()); + $page = comment_get_display_page($comment->id(), $instance); // @todo: Cleaner sub request handling. - $redirect_request = Request::create('/node/' . $node->id(), 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all()); + $uri = $entity->uri(); + $redirect_request = Request::create($uri['path'], 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all()); $redirect_request->query->set('page', $page); // @todo: Convert the pager to use the request object. $request->query->set('page', $page); @@ -135,53 +156,89 @@ public function commentPermalink(Request $request, CommentInterface $comment) { } /** + * Redirects legacy node links to the new path. + * + * @param \Drupal\Core\Entity\EntityInterface $node + * The node object identified by the legacy URL. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * Redirects user to new url. + */ + public function redirectNode(EntityInterface $node) { + $fields = $this->commentManager->getFields('node'); + // Legacy nodes only had a single comment field, so use the first comment + // field on the entity. + if (!empty($fields) && ($field_names = array_keys($fields)) && ($field_name = reset($field_names))) { + return new RedirectResponse($this->urlGenerator->generateFromPath('comment/reply/node/' . $node->id() . '/' . $field_name, array('absolute' => TRUE))); + } + throw new NotFoundHttpException(); + } + + /** * Form constructor for the comment reply form. * - * Both replies on the node itself and replies on other comments are - * supported. To provide context, the node or comment that is being replied on - * will be displayed along the comment reply form. - * The constructor takes care of access permissions and checks whether the - * node still accepts comments. + * There are several cases that have to be handled, including: + * - replies to comments + * - replies to entities + * - attempts to reply to entities that can no longer accept comments + * - respecting access permissions ('access comments', 'post comments', etc.) * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. - * @param \Drupal\node\NodeInterface $node - * Every comment belongs to a node. This is that node. + * @param string $entity_type + * Every comment belongs to an entity. This is the type of the entity. + * @param string $entity_id + * Every comment belongs to an entity. This is the ID of the entity. + * @param string $field_name + * The field_name to which the comment belongs. * @param int $pid * (optional) Some comments are replies to other comments. In those cases, - * $pid is the parent comment's ID. Defaults to NULL. + * $pid is the parent comment's comment ID. Defaults to NULL. * * @return array|\Symfony\Component\HttpFoundation\RedirectResponse * One of the following: + * An associative array containing: + * - An array for rendering the entity or parent comment. + * - comment_entity: If the comment is a reply to the entity. + * - comment_parent: If the comment is a reply to another comment. + * - comment_form: The comment form as a renderable array. * - An associative array containing: - * - An array for rendering the node or parent comment. - * - comment_node: If the comment is a reply to the node. + * - An array for rendering the entity or parent comment. + * - comment_entity: If the comment is a reply to the entity. * - comment_parent: If the comment is a reply to another comment. * - comment_form: The comment form as a renderable array. * - A redirect response to current node: * - If user is not authorized to post comments. - * - If parent comment doesn't belong to current node. + * - If parent comment doesn't belong to current entity. * - If user is not authorized to view comments. - * - If current node comments are disable. + * - If current entity comments are disable. */ - public function getReplyForm(Request $request, NodeInterface $node, $pid = NULL) { - $uri = $node->uri(); - $build = array(); + public function getReplyForm(Request $request, $entity_type, $entity_id, $field_name, $pid = NULL) { $account = $this->currentUser(); - $build['#title'] = $this->t('Add new comment'); + // Check if entity and field exists. + $fields = $this->commentManager->getFields($entity_type); + if (empty($fields[$field_name]) || !($entity = $this->entityManager()->getStorageController($entity_type)->load($entity_id))) { + throw new NotFoundHttpException(); + } + + // Entity loaded. + $uri = $entity->uri(); + $build = array(); // Check if the user has the proper permissions. if (!$account->hasPermission('post comments')) { drupal_set_message($this->t('You are not authorized to post comments.'), 'error'); - return new RedirectResponse($this->urlGenerator()->generateFromPath($uri['path'], array('absolute' => TRUE))); + return new RedirectResponse($this->urlGenerator()->generateFromRoute($uri['path'], array('absolute' => TRUE))); } // The user is not just previewing a comment. if ($request->request->get('op') != $this->t('Preview')) { - if ($node->comment->value != COMMENT_NODE_OPEN) { + if ($entity->$field_name->status != COMMENT_OPEN) { drupal_set_message($this->t("This discussion is closed: you can't post new comments."), 'error'); - return new RedirectResponse($this->urlGenerator()->generateFromPath($uri['path'], array('absolute' => TRUE))); + return new RedirectResponse($this->urlGenerator()->generateFromRoute($uri['path'], array('absolute' => TRUE))); } // $pid indicates that this is a reply to a comment. @@ -194,7 +251,7 @@ public function getReplyForm(Request $request, NodeInterface $node, $pid = NULL) // Load the parent comment. $comment = $this->entityManager()->getStorageController('comment')->load($pid); // Check if the parent comment is published and belongs to the current nid. - if (($comment->status->value == COMMENT_NOT_PUBLISHED) || ($comment->nid->target_id != $node->id())) { + if (($comment->status->value == COMMENT_NOT_PUBLISHED) || ($comment->entity_id->value != $entity->id())) { drupal_set_message($this->t('The comment you are replying to does not exist.'), 'error'); return new RedirectResponse($this->urlGenerator()->generateFromPath($uri['path'], array('absolute' => TRUE))); } @@ -202,10 +259,15 @@ public function getReplyForm(Request $request, NodeInterface $node, $pid = NULL) $build['comment_parent'] = $this->entityManager()->getRenderController('comment')->view($comment); } - // The comment is in response to a node. - elseif ($account->hasPermission('access content')) { - // Display the node. - $build['comment_node'] = $this->entityManager()->getRenderController('node')->view($node); + // The comment is in response to a entity. + elseif ($entity->access('view', $account)) { + // We make sure the field value isn't set so we don't end up with a + // redirect loop. + $original_value = $entity->{$field_name}->status; + $entity->{$field_name}->status = COMMENT_HIDDEN; + // Render array of the entity full view mode. + $build['comment_entity'] = entity_view($entity, 'full'); + $entity->{$field_name}->status = $original_value; } } else { @@ -214,9 +276,10 @@ public function getReplyForm(Request $request, NodeInterface $node, $pid = NULL) // Show the actual reply box. $comment = $this->entityManager()->getStorageController('comment')->create(array( - 'nid' => $node->id(), + 'entity_id' => $entity->id(), 'pid' => $pid, - 'node_type' => 'comment_node_' . $node->getType(), + 'entity_type' => $entity->entityType(), + 'field_id' => $entity->entityType() . '__' . $field_name, )); $build['comment_form'] = $this->entityManager()->getForm($comment); diff --git a/core/modules/comment/lib/Drupal/comment/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Entity/Comment.php index 59c122b..ed33bce 100644 --- a/core/modules/comment/lib/Drupal/comment/Entity/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Entity/Comment.php @@ -36,14 +36,16 @@ * uri_callback = "comment_uri", * fieldable = TRUE, * translatable = TRUE, - * route_base_path = "admin/structure/types/manage/{bundle}/comment", - * bundle_prefix = "comment_node_", + * route_base_path = "admin/structure/comments/manage/{bundle}", * entity_keys = { * "id" = "cid", - * "bundle" = "node_type", + * "bundle" = "field_id", * "label" = "subject", * "uuid" = "uuid" * }, + * bundle_keys = { + * "bundle" = "field_id" + * }, * links = { * "canonical" = "/comment/{comment}", * "edit-form" = "/comment/{comment}/edit" @@ -55,7 +57,7 @@ class Comment extends EntityNG implements CommentInterface { /** * The comment ID. * - * @todo Rename to 'id'. + * @todo Rename to 'id'. https://drupal.org/node/2031935 * * @var \Drupal\Core\Entity\Field\FieldInterface */ @@ -69,20 +71,34 @@ class Comment extends EntityNG implements CommentInterface { public $uuid; /** - * The parent comment ID if this is a reply to a comment. + * The parent comment ID if this is a reply to another comment. * - * @todo: Rename to 'parent_id'. + * @todo: Rename to 'parent_id'. https://drupal.org/node/2031931 * * @var \Drupal\Core\Entity\Field\FieldInterface */ public $pid; /** - * The ID of the node to which the comment is attached. + * The entity ID for the entity to which this comment is attached. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $entity_id; + + /** + * The entity type of the entity to which this comment is attached. * * @var \Drupal\Core\Entity\Field\FieldInterface */ - public $nid; + public $entity_type; + + /** + * The field to which this comment is attached. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $field_id; /** * The comment language code. @@ -168,13 +184,6 @@ class Comment extends EntityNG implements CommentInterface { public $thread; /** - * The comment node type. - * - * @var \Drupal\Core\Entity\Field\FieldInterface - */ - public $node_type; - - /** * The comment 'new' marker for the current user. * * @var \Drupal\Core\Entity\Field\FieldInterface @@ -190,7 +199,8 @@ protected function init() { unset($this->cid); unset($this->uuid); unset($this->pid); - unset($this->nid); + unset($this->entity_id); + unset($this->field_id); unset($this->subject); unset($this->uid); unset($this->name); @@ -201,7 +211,7 @@ protected function init() { unset($this->changed); unset($this->status); unset($this->thread); - unset($this->node_type); + unset($this->entity_type); unset($this->new); } @@ -215,26 +225,12 @@ public function id() { /** * {@inheritdoc} */ - public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { - if (empty($values['node_type']) && !empty($values['nid'])) { - $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']); - $values['node_type'] = 'comment_node_' . $node->getType(); - } - } - - /** - * {@inheritdoc} - */ public function preSave(EntityStorageControllerInterface $storage_controller) { global $user; if (!isset($this->status->value)) { $this->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED; } - // Make sure we have a proper bundle name. - if (!isset($this->node_type->value)) { - $this->node_type->value = 'comment_node_' . $this->nid->entity->type; - } if ($this->isNew()) { // Add the comment to database. This next section builds the thread field. // Also see the documentation for comment_view(). @@ -291,7 +287,7 @@ public function preSave(EntityStorageControllerInterface $storage_controller) { // has the lock, just move to the next integer. do { $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/'; - } while (!lock()->acquire("comment:{$this->nid->target_id}:$thread")); + } while (!lock()->acquire("comment:{$this->entity_id->value}:$thread")); $this->threadLock = $thread; } if (empty($this->created->value)) { @@ -316,8 +312,8 @@ public function preSave(EntityStorageControllerInterface $storage_controller) { */ public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { $this->releaseThreadLock(); - // Update the {node_comment_statistics} table prior to executing the hook. - $storage_controller->updateNodeStatistics($this->nid->target_id); + // Update the {comment_entity_statistics} table prior to executing the hook. + $storage_controller->updateEntityStatistics($this); if ($this->status->value == COMMENT_PUBLISHED) { module_invoke_all('comment_publish', $this); } @@ -341,7 +337,7 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont entity_delete_multiple('comment', $child_cids); foreach ($entities as $id => $entity) { - $storage_controller->updateNodeStatistics($entity->nid->target_id); + $storage_controller->updateEntityStatistics($entity); } } @@ -349,8 +345,9 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont * {@inheritdoc} */ public function permalink() { - - $url['path'] = 'node/' . $this->nid->value; + $entity = entity_load($this->get('entity_type')->value, $this->get('entity_id')->value); + $uri = $entity->uri(); + $url['path'] = $uri['path']; $url['options'] = array('fragment' => 'comment-' . $this->id()); return $url; @@ -377,9 +374,9 @@ public static function baseFieldDefinitions($entity_type) { 'type' => 'entity_reference_field', 'settings' => array('target_type' => 'comment'), ); - $properties['nid'] = array( - 'label' => t('Node ID'), - 'description' => t('The ID of the node of which this comment is a reply.'), + $properties['entity_id'] = array( + 'label' => t('Entity ID'), + 'description' => t('The ID of the entity of which this comment is a reply.'), 'type' => 'entity_reference_field', 'settings' => array('target_type' => 'node'), 'required' => TRUE, @@ -444,12 +441,15 @@ public static function baseFieldDefinitions($entity_type) { 'description' => t("The alphadecimal representation of the comment's place in a thread, consisting of a base 36 string prefixed by an integer indicating its length."), 'type' => 'string_field', ); - $properties['node_type'] = array( - // @todo: The bundle property should be stored so it's queryable. - 'label' => t('Node type'), - 'description' => t("The comment node type."), + $properties['entity_type'] = array( + 'label' => t('Entity type'), + 'description' => t("The entity type to which this comment is attached."), + 'type' => 'string_field', + ); + $properties['field_id'] = array( + 'label' => t('Field ID'), + 'description' => t("The comment field id."), 'type' => 'string_field', - 'queryable' => FALSE, ); $properties['new'] = array( 'label' => t('Comment new marker'), @@ -458,6 +458,13 @@ public static function baseFieldDefinitions($entity_type) { 'computed' => TRUE, 'class' => '\Drupal\comment\CommentNewItem', ); + $properties['field_name'] = array( + 'label' => t('Comment field name'), + 'description' => t("The field name through which this comment was added."), + 'type' => 'string_field', + 'computed' => TRUE, + 'class' => '\Drupal\comment\CommentFieldName', + ); return $properties; } @@ -468,4 +475,13 @@ public function getChangedTime() { return $this->changed->value; } + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + if (empty($values['field_id']) && !empty($values['field_name']) && !empty($values['entity_type'])) { + $values['field_id'] = $values['entity_type'] . '__' . $values['field_name']; + } + } + } diff --git a/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php b/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php index fd86ddf..69f7609 100644 --- a/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php +++ b/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php @@ -9,6 +9,8 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityNGConfirmFormBase; +use Drupal\comment\CommentManager; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; /** @@ -17,6 +19,32 @@ class DeleteForm extends EntityNGConfirmFormBase { /** + * The comment manager. + * + * @var \Drupal\comment\CommentManager + */ + protected $commentManager; + + /** + * Constructs a DeleteForm object. + * + * @param \Drupal\comment\CommentManager $comment_manager + * The comment manager service. + */ + public function __construct(CommentManager $comment_manager) { + $this->commentManager = $comment_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('comment.manager') + ); + } + + /** * {@inheritdoc} */ public function getQuestion() { @@ -27,7 +55,8 @@ public function getQuestion() { * {@inheritdoc} */ public function getCancelPath() { - return 'node/' . $this->entity->nid->target_id; + $uri = $this->commentManager->getParentEntityUri($this->entity); + return $uri['path']; } /** @@ -55,7 +84,8 @@ public function submit(array $form, array &$form_state) { // Clear the cache so an anonymous user sees that his comment was deleted. Cache::invalidateTags(array('content' => TRUE)); - $form_state['redirect'] = "node/{$this->entity->nid->target_id}"; + $uri = $this->commentManager->getParentEntityUri($this->entity); + $form_state['redirect'] = $uri['path']; } } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/entity_reference/selection/CommentSelection.php b/core/modules/comment/lib/Drupal/comment/Plugin/entity_reference/selection/CommentSelection.php index fff8d59..13ce3d0 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/entity_reference/selection/CommentSelection.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/entity_reference/selection/CommentSelection.php @@ -50,7 +50,7 @@ public function entityQueryAlter(SelectInterface $query) { // The Comment module doesn't implement any proper comment access, // and as a consequence doesn't make sure that comments cannot be viewed // when the user doesn't have access to the node. - $node_alias = $query->innerJoin('node_field_data', 'n', '%alias.nid = ' . $base_table . '.nid'); + $node_alias = $query->innerJoin('node_field_data', 'n', '%alias.nid = ' . $base_table . '.entity_id AND ' . $base_table . ".entity_type = 'node'"); // Pass the query to the node access control. $this->reAlterQuery($query, 'node_access', $node_alias); diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/field/field_type/CommentItem.php b/core/modules/comment/lib/Drupal/comment/Plugin/field/field_type/CommentItem.php new file mode 100644 index 0000000..33f33e4 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/field/field_type/CommentItem.php @@ -0,0 +1,232 @@ + array( + 'type' => 'integer', + 'label' => t('Comment status value'), + 'settings' => array('default_value' => COMMENT_OPEN), + ), + 'cid' => array( + 'type' => 'entity_reference_field', + 'label' => t('Last comment ID'), + 'settings' => array( + 'target_type' => 'comment', + 'default_value' => 0, + ), + ), + 'last_comment_timestamp' => array( + 'label' => t('Last comment timestamp'), + 'description' => t('The time that the last comment was created.'), + 'type' => 'integer_field', + 'settings' => array('default_value' => 0) + ), + 'last_comment_name' => array( + 'label' => t('Last comment name'), + 'description' => t('The name of the user posting the last comment.'), + 'type' => 'string_field', + 'settings' => array('default_value' => '') + ), + 'last_comment_uid' => array( + 'type' => 'entity_reference_field', + 'label' => t('Last comment user ID'), + 'settings' => array( + 'target_type' => 'user', + 'default_value' => 0, + ), + ), + 'comment_count' => array( + 'label' => t('Number of comments'), + 'description' => t('The number of comments.'), + 'type' => 'integer_field', + 'settings' => array('default_value' => 0) + ), + ); + } + return static::$propertyDefinitions; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldInterface $field) { + return array( + 'columns' => array( + 'status' => array( + 'description' => 'Whether comments are allowed on this entity: 0 = no, 1 = closed (read only), 2 = open (read/write).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + // Required when upgrading and comment module is disabled. + // @todo Remove in D9. + 'indexes' => array(), + 'foreign keys' => array(), + ); + } + + /** + * {@inheritdoc} + */ + public function instanceSettingsForm(array $form, array &$form_state) { + $element = array(); + + $settings = $this->getFieldSettings(); + + $entity_type = $this->getParent()->getParent()->entityType(); + $field_name = $this->getFieldDefinition()->getFieldName(); + + $element['comment'] = array( + '#type' => 'details', + '#title' => t('Comment form settings'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#bundle' => "{$entity_type}__{$field_name}", + '#process' => array(array(get_class($this), 'processSettingsElement')), + '#attributes' => array( + 'class' => array('comment-instance-settings-form'), + ), + '#attached' => array( + 'library' => array(array('comment', 'drupal.comment')), + ), + ); + $element['comment']['default_mode'] = array( + '#type' => 'checkbox', + '#title' => t('Threading'), + '#default_value' => $settings['default_mode'], + '#description' => t('Show comment replies in a threaded list.'), + ); + $element['comment']['per_page'] = array( + '#type' => 'select', + '#title' => t('Comments per page'), + '#default_value' => $settings['per_page'], + '#options' => _comment_per_page(), + ); + $element['comment']['anonymous'] = array( + '#type' => 'select', + '#title' => t('Anonymous commenting'), + '#default_value' => $settings['anonymous'], + '#options' => array( + COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'), + COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'), + COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information'), + ), + '#access' => drupal_anonymous_user()->hasPermission('post comments'), + ); + $element['comment']['subject'] = array( + '#type' => 'checkbox', + '#title' => t('Allow comment title'), + '#default_value' => $settings['subject'], + ); + $element['comment']['form_location'] = array( + '#type' => 'checkbox', + '#title' => t('Show reply form on the same page as comments'), + '#default_value' => $settings['form_location'], + ); + $element['comment']['preview'] = array( + '#type' => 'radios', + '#title' => t('Preview comment'), + '#default_value' => $settings['preview'], + '#options' => array( + DRUPAL_DISABLED => t('Disabled'), + DRUPAL_OPTIONAL => t('Optional'), + DRUPAL_REQUIRED => t('Required'), + ), + ); + + return $element; + } + + /** + * {@inheritdoc} + */ + public function applyDefaultValue($notify = TRUE) { + // Retrieve the configured default value for the instance. + $this->setValue(array( + 'status' => COMMENT_OPEN, + 'cid' => 0, + 'last_comment_timestamp' => 0, + 'last_comment_name' => '', + 'last_comment_uid' => 0, + 'comment_count' => 0, + ), $notify); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + // We always want the values saved so we can rely on them. + return FALSE; + } + + /** + * Process callback to add submit handler for instance settings form. + * + * Attaches the required translation entity handlers for the instance which + * correlates one to one with the comment bundle. + */ + public static function processSettingsElement($element) { + // Settings should not be stored as nested. + $parents = $element['#parents']; + array_pop($parents); + $element['#parents'] = $parents; + // Add translation entity handlers. + if (\Drupal::moduleHandler()->moduleExists('content_translation')) { + $comment_form = $element; + $comment_form_state['content_translation']['key'] = 'language_configuration'; + $element += content_translation_enable_widget('comment', $element['#bundle'], $comment_form, $comment_form_state); + $element['content_translation']['#parents'] = $element['content_translation']['#array_parents'] = array( + 'content_translation' + ); + } + return $element; + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/field/formatter/CommentDefaultFormatter.php b/core/modules/comment/lib/Drupal/comment/Plugin/field/formatter/CommentDefaultFormatter.php new file mode 100644 index 0000000..aae116d --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/field/formatter/CommentDefaultFormatter.php @@ -0,0 +1,89 @@ +fieldDefinition; + $field_name = $field->getFieldName(); + + $commenting_status = $items->status; + if ($commenting_status != COMMENT_HIDDEN && empty($entity->in_preview)) { + $comment_settings = $this->getFieldSettings(); + + // Only attempt to render comments if the entity has visible comments. + // Unpublished comments are not included in + // $entity->get($field_name)->comment_count, but unpublished comments + // should display if the user is an administrator. + if ((($entity->get($field_name)->comment_count && user_access('access comments')) || user_access('administer comments')) && + !empty($entity->content['#view_mode']) && + !in_array($entity->content['#view_mode'], array('search_result', 'search_index'))) { + + // Comment threads aren't added to search results/indexes using the + // formatter, @see comment_node_update_index(). + $mode = $comment_settings['default_mode']; + $comments_per_page = $comment_settings['per_page']; + if ($cids = comment_get_thread($entity, $field_name, $mode, $comments_per_page)) { + $comments = comment_load_multiple($cids); + comment_prepare_thread($comments); + $build = comment_view_multiple($comments); + $build['pager']['#theme'] = 'pager'; + $additions['comments'] = $build; + } + } + + // Append comment form if the comments are open and the form + // is set to display below the entity. + if ($commenting_status == COMMENT_OPEN && $comment_settings['form_location'] == COMMENT_FORM_BELOW) { + // Only show the add comment form if the user has permission and the + // view mode is not search_result or search_index. + if (user_access('post comments') && !empty($entity->content['#view_mode']) && + !in_array($entity->content['#view_mode'], array('search_result', 'search_index'))) { + $additions['comment_form'] = comment_add($entity, $field_name); + } + } + } + + if (!empty($additions)) { + $elements[] = $additions + array( + '#theme' => 'comment_wrapper__' . $entity->entityType() . '__' . $entity->bundle() . '__' . $field_name, + '#entity' => $entity, + '#display_mode' => $this->getFieldSetting('default_mode'), + 'comments' => array(), + 'comment_form' => array(), + ); + } + + return $elements; + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/field/widget/CommentWidget.php b/core/modules/comment/lib/Drupal/comment/Plugin/field/widget/CommentWidget.php new file mode 100644 index 0000000..c604ec4 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/field/widget/CommentWidget.php @@ -0,0 +1,104 @@ +fieldDefinition; + $entity = $items->getParent(); + + $element['status'] = array( + '#type' => 'radios', + '#title' => t('Comments'), + '#title_display' => 'invisible', + //'#default_value' => $items[$delta]->status, + '#default_value' => $entity->get($field->getFieldName())->status, + '#options' => array( + COMMENT_OPEN => t('Open'), + COMMENT_CLOSED => t('Closed'), + COMMENT_HIDDEN => t('Hidden'), + ), + COMMENT_OPEN => array( + '#description' => t('Users with the "Post comments" permission can post comments.'), + ), + COMMENT_CLOSED => array( + '#description' => t('Users cannot post comments, but existing comments will be displayed.'), + ), + COMMENT_HIDDEN => array( + '#description' => t('Comments are hidden from view.'), + ), + ); + // If used for the field settings form or the entity doesn't have any + // comments, the "hidden" option makes no sense, so don't even bother + // presenting it to the user. + if ($element['#field_parents'] == array('default_value_input') && !$entity->get($field->getFieldName())->comment_count) { + $element['status'][COMMENT_HIDDEN]['#access'] = FALSE; + // Also adjust the description of the "closed" option. + $element['status'][COMMENT_CLOSED]['#description'] = t('Users cannot post comments.'); + } + // Integrate with advanced settings, if available. + if (isset($form['advanced'])) { + $element += array( + '#type' => 'details', + // Collapse the advanced settings when they are the same + // as the defaults for the instance. + // @todo Add $this->defaultStatus($field) and compare actual values. + '#collapsed' => ($items->getValue() == $field->getFieldDefaultValue($entity)), + '#group' => 'advanced', + '#attributes' => array( + 'class' => array('comment-' . drupal_html_class($element['#entity_type']) . '-settings-form'), + ), + '#attached' => array( + 'library' => array('comment', 'drupal.comment'), + ), + '#weight' => 30, + ); + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, array &$form_state) { + // Add default values for statistics properties because we don't want to + // have them in form. + foreach ($values as &$value) { + $value += array( + 'cid' => 0, + 'last_comment_timestamp' => 0, + 'last_comment_name' => '', + 'last_comment_uid' => 0, + 'comment_count' => 0, + ); + } + return $values; + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/argument/UserUid.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/argument/UserUid.php index be6c4c3..065f285 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/argument/UserUid.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/argument/UserUid.php @@ -85,16 +85,25 @@ protected function defaultActions($which = NULL) { public function query($group_by = FALSE) { $this->ensureMyTable(); - $subselect = $this->database->select('comment', 'c'); - $subselect->addField('c', 'cid'); - $subselect->condition('c.uid', $this->argument); - $subselect->where("c.nid = $this->tableAlias.nid"); - - $condition = db_or() - ->condition("$this->tableAlias.uid", $this->argument, '=') - ->exists($subselect); - - $this->query->addWhere(0, $condition); + // Load the table information to get the entity information to finally + // be able to join to filter by the original entity type this join is + // attached to. + if ($this->table != 'comment') { + $subselect = $this->database->select('comment', 'c'); + $subselect->addField('c', 'cid'); + $subselect->condition('c.uid', $this->argument); + + $entity_id = $this->definition['entity_id']; + $entity_type = $this->definition['entity_type']; + $subselect->where("c.entity_id = $this->tableAlias.$entity_id"); + $subselect->condition('c.entity_type', $entity_type); + + $condition = db_or() + ->condition("$this->tableAlias.uid", $this->argument, '=') + ->exists($subselect); + + $this->query->addWhere(0, $condition); + } } /** diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Comment.php index 1fd7050..a534175 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Comment.php @@ -32,14 +32,15 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o if (!empty($this->options['link_to_comment'])) { $this->additional_fields['cid'] = 'cid'; - $this->additional_fields['nid'] = 'nid'; + $this->additional_fields['entity_id'] = 'entity_id'; + $this->additional_fields['entity_type'] = 'entity_type'; } } protected function defineOptions() { $options = parent::defineOptions(); $options['link_to_comment'] = array('default' => TRUE, 'bool' => TRUE); - $options['link_to_node'] = array('default' => FALSE, 'bool' => TRUE); + $options['link_to_entity'] = array('default' => FALSE, 'bool' => TRUE); return $options; } @@ -54,10 +55,10 @@ public function buildOptionsForm(&$form, &$form_state) { '#type' => 'checkbox', '#default_value' => $this->options['link_to_comment'], ); - $form['link_to_node'] = array( - '#title' => t('Link field to the node if there is no comment.'), + $form['link_to_entity'] = array( + '#title' => t('Link field to the entity if there is no comment.'), '#type' => 'checkbox', - '#default_value' => $this->options['link_to_node'], + '#default_value' => $this->options['link_to_entity'], ); parent::buildOptionsForm($form, $form_state); } @@ -65,15 +66,18 @@ public function buildOptionsForm(&$form, &$form_state) { protected function renderLink($data, ResultRow $values) { if (!empty($this->options['link_to_comment'])) { $this->options['alter']['make_link'] = TRUE; - $nid = $this->getValue($values, 'nid'); $cid = $this->getValue($values, 'cid'); if (!empty($cid)) { $this->options['alter']['path'] = "comment/" . $cid; $this->options['alter']['fragment'] = "comment-" . $cid; } // If there is no comment link to the node. - elseif ($this->options['link_to_node']) { - $this->options['alter']['path'] = "node/" . $nid; + elseif ($this->options['link_to_entity']) { + $entity_id = $this->getValue($values, 'entity_id'); + $entity_type = $this->getValue($values, 'entity_type'); + $entity = entity_load($entity_type, $entity_id); + $uri = $entity->uri(); + $this->options['alter']['path'] = $uri['path']; } } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeLink.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/EntityLink.php similarity index 44% rename from core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeLink.php rename to core/modules/comment/lib/Drupal/comment/Plugin/views/field/EntityLink.php index d824118..189159d 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeLink.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/EntityLink.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Plugin\views\field\NodeLink. + * Contains \Drupal\comment\Plugin\views\field\EntityLink. */ namespace Drupal\comment\Plugin\views\field; @@ -12,13 +12,20 @@ use Drupal\views\ResultRow; /** - * Handler for showing comment module's node link. + * Handler for showing comment module's entity links. * * @ingroup views_field_handlers * - * @PluginID("comment_node_link") + * @PluginID("comment_entity_link") */ -class NodeLink extends FieldPluginBase { +class EntityLink extends FieldPluginBase { + + /** + * Stores the result of node_view_multiple for all rows to reuse it later. + * + * @var array + */ + protected $build; protected function defineOptions() { $options = parent::defineOptions(); @@ -31,7 +38,7 @@ public function buildOptionsForm(&$form, &$form_state) { '#type' => 'checkbox', '#title' => t('Show teaser-style link'), '#default_value' => $this->options['teaser'], - '#description' => t('Show the comment link in the form used on standard node teasers, rather than the full node form.'), + '#description' => t('Show the comment link in the form used on standard entity teasers, rather than the full entity form.'), ); parent::buildOptionsForm($form, $form_state); @@ -40,14 +47,28 @@ public function buildOptionsForm(&$form, &$form_state) { public function query() {} /** + * Implements \Drupal\views\Plugin\views\field\FieldPluginBase::pre_render(). + */ + public function preRender(&$values) { + // Render all nodes, so you can grep the comment links. + $entities = array(); + foreach ($values as $row) { + $entity = $row->_entity; + $entities[$entity->id()] = $entity; + } + if ($entities) { + $this->build = entity_view_multiple($entities, $this->options['teaser'] ? 'teaser' : 'full'); + } + } + + /** * {@inheritdoc} */ public function render(ResultRow $values) { - $node = $this->getEntity($values); - comment_node_view($node, $this->options['teaser'] ? 'teaser' : 'full'); + $entity = $this->getEntity($values); - // Only render the links if they are defined. - return !empty($node->content['links']['comment']) ? drupal_render($node->content['links']['comment']) : ''; + // Only render the links, if they are defined. + return !empty($this->build[$entity->id()]['links']['comment__comment']) ? drupal_render($this->build[$entity->id()]['links']['comment__comment']) : ''; } } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Link.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Link.php index 3c6ba13..c51b6d9 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Link.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/Link.php @@ -23,7 +23,7 @@ class Link extends FieldPluginBase { protected function defineOptions() { $options = parent::defineOptions(); $options['text'] = array('default' => '', 'translatable' => TRUE); - $options['link_to_node'] = array('default' => FALSE, 'bool' => TRUE); + $options['link_to_entity'] = array('default' => FALSE, 'bool' => TRUE); return $options; } @@ -33,10 +33,10 @@ public function buildOptionsForm(&$form, &$form_state) { '#title' => t('Text to display'), '#default_value' => $this->options['text'], ); - $form['link_to_node'] = array( - '#title' => t('Link field to the node if there is no comment.'), + $form['link_to_entity'] = array( + '#title' => t('Link field to the entity if there is no comment.'), '#type' => 'checkbox', - '#default_value' => $this->options['link_to_node'], + '#default_value' => $this->options['link_to_entity'], ); parent::buildOptionsForm($form, $form_state); } @@ -54,7 +54,6 @@ public function render(ResultRow $values) { protected function renderLink($data, ResultRow $values) { $text = !empty($this->options['text']) ? $this->options['text'] : t('view'); $comment = $data; - $nid = $comment->nid; $cid = $comment->id(); $this->options['alter']['make_link'] = TRUE; @@ -66,7 +65,11 @@ protected function renderLink($data, ResultRow $values) { } // If there is no comment link to the node. elseif ($this->options['link_to_node']) { - $this->options['alter']['path'] = "node/" . $nid; + $entity_id = $comment->entity_id; + $entity_type = $comment->entity_type; + $entity = entity_load($entity_type, $entity_id); + $uri = $entity->uri(); + $this->options['alter']['path'] = $uri['path']; } return $text; diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkApprove.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkApprove.php index 78090c8..e236b82 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkApprove.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkApprove.php @@ -33,11 +33,11 @@ protected function renderLink($data, ResultRow $values) { } $text = !empty($this->options['text']) ? $this->options['text'] : t('approve'); - $cid = $this->getValue($values, 'cid'); + $comment = $this->get_entity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "comment/" . $cid . "/approve"; - $this->options['alter']['query'] = drupal_get_destination() + array('token' => drupal_get_token("comment/$cid/approve")); + $this->options['alter']['path'] = "comment/" . $comment->id() . "/approve"; + $this->options['alter']['query'] = drupal_get_destination() + array('token' => drupal_get_token($this->options['alter']['path'])); return $text; } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkDelete.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkDelete.php index e5b7ca3..7f204b9 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkDelete.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkDelete.php @@ -26,10 +26,10 @@ public function access() { protected function renderLink($data, ResultRow $values) { $text = !empty($this->options['text']) ? $this->options['text'] : t('delete'); - $cid = $this->getValue($values, 'cid'); + $comment = $this->get_entity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "comment/" . $cid . "/delete"; + $this->options['alter']['path'] = "comment/" . $comment->id(). "/delete"; $this->options['alter']['query'] = drupal_get_destination(); return $text; diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkReply.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkReply.php index a4dd59a..6bdc7da 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkReply.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkReply.php @@ -26,11 +26,10 @@ public function access() { protected function renderLink($data, ResultRow $values) { $text = !empty($this->options['text']) ? $this->options['text'] : t('reply'); - $nid = $this->getValue($values, 'nid'); - $cid = $this->getValue($values, 'cid'); + $comment = $this->getEntity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "comment/reply/" . $nid . '/' . $cid; + $this->options['alter']['path'] = "comment/reply/{$comment->entity_type->value}/{$comment->entity_id->value}/{$comment->field_name->value}/{$comment->id()}"; return $text; } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeComment.php index 3beb7e2..41d6a4f 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeComment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeComment.php @@ -26,12 +26,12 @@ class NodeComment extends FieldPluginBase { public function render(ResultRow $values) { $value = $this->getValue($values); switch ($value) { - case COMMENT_NODE_HIDDEN: + case COMMENT_HIDDEN: default: return t('Hidden'); - case COMMENT_NODE_CLOSED: + case COMMENT_CLOSED: return t('Closed'); - case COMMENT_NODE_OPEN: + case COMMENT_OPEN: return t('Open'); } } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php index 6da4cb1..d3f869f 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php @@ -62,9 +62,9 @@ public static function create(ContainerInterface $container, array $configuratio public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { parent::init($view, $display, $options); - $this->additional_fields['nid'] = 'nid'; + $this->additional_fields['entity_id'] = 'nid'; $this->additional_fields['type'] = 'type'; - $this->additional_fields['comment_count'] = array('table' => 'node_comment_statistics', 'field' => 'comment_count'); + $this->additional_fields['comment_count'] = array('table' => 'comment_entity_statistics', 'field' => 'comment_count'); } protected function defineOptions() { @@ -111,15 +111,14 @@ public function preRender(&$values) { } if ($nids) { - $result = $this->database->query('SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment} c ON n.nid = c.nid + $result = $this->database->query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment} c ON n.nid = c.entity_id AND c.entity_type = 'node' LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = :h_uid WHERE n.nid IN (:nids) - AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp), :timestamp) AND c.status = :status GROUP BY n.nid', array( - ':status' => COMMENT_PUBLISHED, - ':h_uid' => $user->id(), - ':nids' => $nids, - ':timestamp' => HISTORY_READ_LIMIT, - )); - + AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp), :timestamp) AND c.status = :status GROUP BY n.nid", array( + ':status' => COMMENT_PUBLISHED, + ':h_uid' => $user->id(), + ':nids' => $nids, + ':timestamp' => HISTORY_READ_LIMIT, + )); foreach ($result as $node) { foreach ($ids[$node->id()] as $id) { $values[$id]->{$this->field_alias} = $node->num_comments; diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NcsLastCommentName.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastCommentName.php similarity index 84% rename from core/modules/comment/lib/Drupal/comment/Plugin/views/field/NcsLastCommentName.php rename to core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastCommentName.php index 600eb97..63d881b 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NcsLastCommentName.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastCommentName.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Plugin\views\field\NcsLastCommentName. + * Contains \Drupal\comment\Plugin\views\field\StatisticsLastCommentName. */ namespace Drupal\comment\Plugin\views\field; @@ -16,9 +16,9 @@ * * @ingroup views_field_handlers * - * @PluginID("comment_ncs_last_comment_name") + * @PluginID("comment_ces_last_comment_name") */ -class NcsLastCommentName extends FieldPluginBase { +class StatisticsLastCommentName extends FieldPluginBase { public function query() { // last_comment_name only contains data if the user is anonymous. So we @@ -28,7 +28,7 @@ public function query() { $definition = array( 'table' => 'users', 'field' => 'uid', - 'left_table' => $this->tableAlias, + 'left_table' => 'comment_entity_statistics', 'left_field' => 'last_comment_uid', 'extra' => array( array( @@ -40,8 +40,8 @@ public function query() { ); $join = drupal_container()->get('plugin.manager.views.join')->createInstance('standard', $definition); - // ncs_user alias so this can work with the sort handler, below. - $this->user_table = $this->query->ensureTable('ncs_users', $this->relationship, $join); + // nes_user alias so this can work with the sort handler, below. + $this->user_table = $this->query->ensureTable('ces_users', $this->relationship, $join); $this->field_alias = $this->query->addField(NULL, "COALESCE($this->user_table.name, $this->tableAlias.$this->field)", $this->tableAlias . '_' . $this->field); diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NcsLastUpdated.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastUpdated.php similarity index 79% rename from core/modules/comment/lib/Drupal/comment/Plugin/views/field/NcsLastUpdated.php rename to core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastUpdated.php index f6209f2..5cced6a 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NcsLastUpdated.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastUpdated.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Plugin\views\field\NcsLastUpdated. + * Contains \Drupal\comment\Plugin\views\field\StatisticsLastUpdated. */ namespace Drupal\comment\Plugin\views\field; @@ -15,9 +15,9 @@ * * @ingroup views_field_handlers * - * @PluginID("comment_ncs_last_updated") + * @PluginID("comment_ces_last_updated") */ -class NcsLastUpdated extends Date { +class StatisticsLastUpdated extends Date { public function query() { $this->ensureMyTable(); diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NodeComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NodeComment.php index 05c95a4..d9ec7a9 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NodeComment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NodeComment.php @@ -21,9 +21,9 @@ class NodeComment extends InOperator { public function getValueOptions() { $this->value_options = array( - COMMENT_NODE_HIDDEN => t('Hidden'), - COMMENT_NODE_CLOSED => t('Closed'), - COMMENT_NODE_OPEN => t('Open'), + COMMENT_HIDDEN => t('Hidden'), + COMMENT_CLOSED => t('Closed'), + COMMENT_OPEN => t('Open'), ); } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NcsLastUpdated.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/StatisticsLastUpdated.php similarity index 81% rename from core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NcsLastUpdated.php rename to core/modules/comment/lib/Drupal/comment/Plugin/views/filter/StatisticsLastUpdated.php index 89f25a1..4e263db 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/NcsLastUpdated.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/StatisticsLastUpdated.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Plugin\views\filter\NcsLastUpdated. + * Contains \Drupal\comment\Plugin\views\filter\StatisticsLastUpdated. */ namespace Drupal\comment\Plugin\views\filter; @@ -15,9 +15,9 @@ * * @ingroup views_filter_handlers * - * @PluginID("ncs_last_updated") + * @PluginID("comment_ces_last_updated") */ -class NcsLastUpdated extends Date { +class StatisticsLastUpdated extends Date { public function query() { $this->ensureMyTable(); diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/UserUid.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/UserUid.php index ab74b4d..a2713df 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/UserUid.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/UserUid.php @@ -26,7 +26,11 @@ public function query() { $subselect = db_select('comment', 'c'); $subselect->addField('c', 'cid'); $subselect->condition('c.uid', $this->value, $this->operator); - $subselect->where("c.nid = $this->tableAlias.nid"); + + $entity_id = $this->definition['entity_id']; + $entity_type = $this->definition['entity_type']; + $subselect->where("c.entity_id = $this->tableAlias.$entity_id"); + $subselect->condition('c.entity_type', $entity_type); $condition = db_or() ->condition("$this->tableAlias.uid", $this->value, $this->operator) diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/NcsLastCommentName.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastCommentName.php similarity index 79% rename from core/modules/comment/lib/Drupal/comment/Plugin/views/sort/NcsLastCommentName.php rename to core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastCommentName.php index f87203d..41c518f 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/NcsLastCommentName.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastCommentName.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Plugin\views\sort\NcsLastCommentName. + * Contains \Drupal\comment\Plugin\views\sort\StatisticsLastCommentName. */ namespace Drupal\comment\Plugin\views\sort; @@ -16,16 +16,16 @@ * * @ingroup views_sort_handlers * - * @PluginID("comment_ncs_last_comment_name") + * @PluginID("comment_ces_last_comment_name") */ -class NcsLastCommentName extends SortPluginBase { +class StatisticsLastCommentName extends SortPluginBase { public function query() { $this->ensureMyTable(); $definition = array( 'table' => 'users', 'field' => 'uid', - 'left_table' => $this->tableAlias, + 'left_table' => 'comment_entity_statistics', 'left_field' => 'last_comment_uid', ); $join = drupal_container()->get('plugin.manager.views.join')->createInstance('standard', $definition); @@ -33,7 +33,7 @@ public function query() { // @todo this might be safer if we had an ensure_relationship rather than guessing // the table alias. Though if we did that we'd be guessing the relationship name // so that doesn't matter that much. - $this->user_table = $this->query->ensureTable('ncs_users', $this->relationship, $join); + $this->user_table = $this->query->ensureTable('ces_users', $this->relationship, $join); $this->user_field = $this->query->addField($this->user_table, 'name'); // Add the field. diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/NcsLastUpdated.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastUpdated.php similarity index 72% rename from core/modules/comment/lib/Drupal/comment/Plugin/views/sort/NcsLastUpdated.php rename to core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastUpdated.php index 7eae853..233cdb6 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/NcsLastUpdated.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastUpdated.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Plugin\views\sort\NcsLastUpdated. + * Contains \Drupal\comment\Plugin\views\sort\StatisticsLastUpdated. */ namespace Drupal\comment\Plugin\views\sort; @@ -11,13 +11,13 @@ use Drupal\Component\Annotation\PluginID; /** - * Sort handler for the newer of last comment / node updated. + * Sort handler for the newer of last comment / entity updated. * * @ingroup views_sort_handlers * - * @PluginID("ncs_last_updated") + * @PluginID("comment_ces_last_updated") */ -class NcsLastUpdated extends Date { +class StatisticsLastUpdated extends Date { public function query() { $this->ensureMyTable(); diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/wizard/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/wizard/Comment.php index 410025b..9efa948 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/wizard/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/wizard/Comment.php @@ -61,8 +61,8 @@ class Comment extends WizardPluginBase { 'value' => TRUE, 'table' => 'node_field_data', 'field' => 'status', + 'relationship' => 'node', 'provider' => 'user', - 'relationship' => 'nid' ) ); @@ -145,10 +145,10 @@ protected function defaultDisplayOptions() { $display_options['access']['type'] = 'perm'; // Add a relationship to nodes. - $display_options['relationships']['nid']['id'] = 'nid'; - $display_options['relationships']['nid']['table'] = 'comment'; - $display_options['relationships']['nid']['field'] = 'nid'; - $display_options['relationships']['nid']['required'] = 1; + $display_options['relationships']['node']['id'] = 'node'; + $display_options['relationships']['node']['table'] = 'comment'; + $display_options['relationships']['node']['field'] = 'node'; + $display_options['relationships']['node']['required'] = 1; // Remove the default fields, since we are customizing them here. unset($display_options['fields']); diff --git a/core/modules/comment/lib/Drupal/comment/Routing/RouteSubscriber.php b/core/modules/comment/lib/Drupal/comment/Routing/RouteSubscriber.php new file mode 100644 index 0000000..0fe2af3 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Routing/RouteSubscriber.php @@ -0,0 +1,61 @@ +moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[RoutingEvents::DYNAMIC] = 'routes'; + return $events; + } + + /** + * Adds routes for comment. + */ + public function routes(RouteBuildEvent $event) { + $collection = $event->getRouteCollection(); + if ($this->moduleHandler->moduleExists('node')) { + $route = new Route( + "/comment/{node}/reply", + array('_controller' => 'Drupal\comment\Controller\CommentController::redirectNode'), + array('_entity_access' => 'node.view') + ); + $collection->add('comment_node_redirect', $route); + } + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentAdminTest.php similarity index 79% rename from core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php rename to core/modules/comment/lib/Drupal/comment/Tests/CommentAdminTest.php index 6f70b0c..da6b48b 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentAdminTest.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\comment\Tests\CommentApprovalTest. + * Contains \Drupal\comment\Tests\CommentAdminTest. */ namespace Drupal\comment\Tests; @@ -10,11 +10,11 @@ /** * Tests comment approval functionality. */ -class CommentApprovalTest extends CommentTestBase { +class CommentAdminTest extends CommentTestBase { public static function getInfo() { return array( - 'name' => 'Comment approval', - 'description' => 'Test comment approval functionality.', + 'name' => 'Comment admin', + 'description' => 'Test comment admin functionality.', 'group' => 'Comment', ); } @@ -47,7 +47,14 @@ function testApprovalAdminInterface() { // Get unapproved comment id. $this->drupalLogin($this->admin_user); $anonymous_comment4 = $this->getUnapprovedComment($subject); - $anonymous_comment4 = entity_create('comment', array('cid' => $anonymous_comment4, 'node_type' => '', 'subject' => $subject, 'comment_body' => $body, 'nid' => $this->node->id())); + $anonymous_comment4 = entity_create('comment', array( + '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.'); @@ -111,7 +118,14 @@ function testApprovalNodeInterface() { // Get unapproved comment id. $this->drupalLogin($this->admin_user); $anonymous_comment4 = $this->getUnapprovedComment($subject); - $anonymous_comment4 = entity_create('comment', array('cid' => $anonymous_comment4, 'node_type' => '', 'subject' => $subject, 'comment_body' => $body, 'nid' => $this->node->id())); + $anonymous_comment4 = entity_create('comment', array( + '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.'); @@ -129,4 +143,25 @@ function testApprovalNodeInterface() { $this->drupalGet('node/' . $this->node->id()); $this->assertTrue($this->commentExists($anonymous_comment4), 'Anonymous comment visible.'); } + + /** + * Tests comment bundle admin. + */ + public function testCommentAdmin() { + // Login. + $this->drupalLogin($this->admin_user); + // Browse to comment bundle overview. + $this->drupalGet('admin/structure/comments'); + $this->assertResponse(200); + // Make sure titles visible. + $this->assertText('Field name'); + $this->assertText('Used in'); + // Manage fields. + $this->clickLink('Manage fields'); + $this->assertResponse(200); + // Make sure comment_body field is shown. + $this->assertText('comment_body'); + // Rest from here on in is field_ui. + } + } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php index 5fe19ba..bfb7fd1 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php @@ -61,7 +61,7 @@ function testAnonymous() { $this->drupalLogout(); // Post anonymous comment with contact info (optional). - $this->drupalGet('comment/reply/' . $this->node->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); $this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.'); $anonymous_comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); @@ -75,7 +75,7 @@ function testAnonymous() { 'subject' => $this->randomName(), "comment_body[$langcode][0][value]" => $this->randomName(), ); - $this->drupalPost('comment/reply/' . $this->node->id(), $edit, t('Save')); + $this->drupalPost('comment/reply/node/' . $this->node->id() . '/comment', $edit, t('Save')); $this->assertText(t('The name you used belongs to a registered user.')); // Require contact info. @@ -84,7 +84,7 @@ function testAnonymous() { $this->drupalLogout(); // Try to post comment with contact info (required). - $this->drupalGet('comment/reply/' . $this->node->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); $this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.'); $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); @@ -138,7 +138,7 @@ function testAnonymous() { $this->assertNoLink('Add new comment', 'Link to add comment was found.'); // Attempt to view node-comment form while disallowed. - $this->drupalGet('comment/reply/' . $this->node->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); $this->assertText('You are not authorized to post comments', 'Error attempting to post comment.'); $this->assertNoFieldByName('subject', '', 'Subject field not found.'); $this->assertNoFieldByName("comment_body[$langcode][0][value]", '', 'Comment field not found.'); @@ -163,7 +163,7 @@ function testAnonymous() { $this->assertFieldByName('subject', '', 'Subject field found.'); $this->assertFieldByName("comment_body[$langcode][0][value]", '', 'Comment field found.'); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $anonymous_comment3->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $anonymous_comment3->id()); $this->assertText('You are not authorized to view comments', 'Error attempting to post reply.'); $this->assertNoText($author_name, 'Comment not displayed.'); } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php index 3948995..914a634 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php @@ -51,8 +51,9 @@ function testCommentClasses() { // Add a comment. $comment = entity_create('comment', array( - 'nid' => $node->id(), - 'node_type' => 'node_type_' . $node->bundle(), + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => $case['comment_uid'], 'status' => $case['comment_status'], 'subject' => $this->randomName(), diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php index f9d25f1..a351410 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php @@ -34,30 +34,29 @@ function testCommentDefaultFields() { // Do not make assumptions on default node types created by the test // installation profile, and create our own. $this->drupalCreateContentType(array('type' => 'test_node_type')); + $this->container->get('comment.manager')->addDefaultField('node', 'test_node_type'); - // Check that the 'comment_body' field is present on all comment bundles. - $instances = field_info_instances('comment'); - foreach (node_type_get_types() as $type_name => $info) { - $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name))); + // Check that the 'comment_body' field is present on the comment bundle. + $instance = $this->container->get('field.info')->getInstance('comment', 'node__comment', 'comment_body'); + $this->assertTrue(!empty($instance), 'The comment_body field is added when a comment bundle is created'); - // Delete the instance along the way. - $instances['comment_node_' . $type_name]['comment_body']->delete(); - } + $instance->delete(); // Check that the 'comment_body' field is deleted. - $field = field_info_field('comment', 'comment_body'); + $field = $this->container->get('field.info')->getField('comment', 'comment_body'); $this->assertTrue(empty($field), 'The comment_body field was deleted'); // Create a new content type. $type_name = 'test_node_type_2'; $this->drupalCreateContentType(array('type' => $type_name)); + $this->container->get('comment.manager')->addDefaultField('node', $type_name); // Check that the 'comment_body' field exists and has an instance on the // new comment bundle. - $field = field_info_field('comment', 'comment_body'); + $field = $this->container->get('field.info')->getField('comment', 'comment_body'); $this->assertTrue($field, 'The comment_body field exists'); - $instances = field_info_instances('comment'); - $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name))); + $instances = $this->container->get('field.info')->getInstances('comment'); + $this->assertTrue(isset($instances['node__comment']['comment_body']), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name))); } /** @@ -68,12 +67,26 @@ function testCommentEnable() { $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules')); $this->drupalLogin($this->admin_user); + // Drop default comment field added in CommentTestBase::setup(). + // @todo WTF#1497374 + entity_load('field_entity', 'node.comment')->delete(); + if ($field = field_info_field('node', 'comment_node_forum')) { + $field->delete(); + } + + // Purge field data now to allow comment module to be disabled once the + // field has been deleted. + field_purge_batch(10); + // Call again as field_purge_batch() won't remove both the instances and + // field in a single pass. + field_purge_batch(10); + // Disable the comment module. $edit = array(); $edit['modules[Core][comment][enable]'] = FALSE; $this->drupalPost('admin/modules', $edit, t('Save configuration')); $this->rebuildContainer(); - $this->assertFalse(module_exists('comment'), 'Comment module disabled.'); + $this->assertFalse($this->container->get('module_handler')->moduleExists('comment'), 'Comment module disabled.'); // Enable core content type module (book). $edit = array(); @@ -85,9 +98,10 @@ function testCommentEnable() { $edit['modules[Core][comment][enable]'] = 'comment'; $this->drupalPost('admin/modules', $edit, t('Save configuration')); $this->rebuildContainer(); - $this->assertTrue(module_exists('comment'), 'Comment module enabled.'); + $this->assertTrue($this->container->get('module_handler')->moduleExists('comment'), 'Comment module enabled.'); // Create nodes of each type. + $this->container->get('comment.manager')->addDefaultField('node', 'book'); $book_node = $this->drupalCreateNode(array('type' => 'book')); $this->drupalLogout(); @@ -107,7 +121,7 @@ function testCommentFormat() { // Disable text processing for comments. $this->drupalLogin($this->admin_user); $edit = array('instance[settings][text_processing]' => 0); - $this->drupalPost('admin/structure/types/manage/article/comment/fields/comment.comment_node_article.comment_body', $edit, t('Save settings')); + $this->drupalPost('admin/structure/comments/manage/comment/fields/comment.node__comment.comment_body', $edit, t('Save settings')); // Post a comment without an explicit subject. $this->drupalLogin($this->web_user); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php index 7ebb12e..fc29ccc 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php @@ -32,7 +32,7 @@ function testCommentInterface() { $this->setCommentPreview(DRUPAL_DISABLED); $this->setCommentForm(TRUE); $this->setCommentSubject(FALSE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); $this->drupalLogout(); // Post comment #1 without subject or preview. @@ -88,23 +88,40 @@ function testCommentInterface() { // Reply to comment #2 creating comment #3 with optional preview and no // subject though field enabled. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment->id()); + // Deliberately use the wrong url to test + // \Drupal\comment\Controller\CommentController::redirectNode(). + $this->drupalGet('comment/' . $this->node->id() . '/reply'); + // Verify we were correctly redirected. + $this->assertUrl(url('comment/reply/node/' . $this->node->id() . '/comment', array('absolute' => TRUE))); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment->id()); $this->assertText($subject_text, 'Individual comment-reply subject found.'); $this->assertText($comment_text, 'Individual comment-reply body found.'); $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); $reply_loaded = comment_load($reply->id()); $this->assertTrue($this->commentExists($reply, TRUE), 'Reply found.'); $this->assertEqual($comment->id(), $reply_loaded->pid->target_id, 'Pid of a reply to a comment is set correctly.'); - $this->assertEqual(rtrim($comment->thread->value, '/') . '.00/', $reply_loaded->thread->value, 'Thread of reply grows correctly.'); + // Check the thread of reply grows correctly. + $this->assertEqual(rtrim($comment->thread->value, '/') . '.00/', $reply_loaded->thread->value); - // Second reply to comment #3 creating comment #4. - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment->id()); - $this->assertText($subject_text, 'Individual comment-reply subject found.'); - $this->assertText($comment_text, 'Individual comment-reply body found.'); + // Second reply to comment #2 creating comment #4. + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment->id()); + $this->assertText($comment->subject->value, 'Individual comment-reply subject found.'); + $this->assertText($comment->comment_body->value, 'Individual comment-reply body found.'); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $reply_loaded = comment_load($reply->id()); + $this->assertTrue($this->commentExists($reply, TRUE), 'Second reply found.'); + // Check the thread of second reply grows correctly. + $this->assertEqual(rtrim($comment->thread->value, '/') . '.01/', $reply_loaded->thread->value); + + // Reply to comment #4 creating comment #5. + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $reply_loaded->id()); + $this->assertText($reply_loaded->subject->value, 'Individual comment-reply subject found.'); + $this->assertText($reply_loaded->comment_body->value, 'Individual comment-reply body found.'); $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); $reply_loaded = comment_load($reply->id()); $this->assertTrue($this->commentExists($reply, TRUE), 'Second reply found.'); - $this->assertEqual(rtrim($comment->thread->value, '/') . '.01/', $reply_loaded->thread->value, 'Thread of second reply grows correctly.'); + // Check the thread of reply to second reply grows correctly. + $this->assertEqual(rtrim($comment->thread->value, '/') . '.01.00/', $reply_loaded->thread->value); // Edit reply. $this->drupalGet('comment/' . $reply->id() . '/edit'); @@ -115,34 +132,34 @@ function testCommentInterface() { $this->setCommentsPerPage(2); $comment_new_page = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); $this->assertTrue($this->commentExists($comment_new_page), 'Page one exists. %s'); - $this->drupalGet('node/' . $this->node->id(), array('query' => array('page' => 1))); + $this->drupalGet('node/' . $this->node->id(), array('query' => array('page' => 2))); $this->assertTrue($this->commentExists($reply, TRUE), 'Page two exists. %s'); $this->setCommentsPerPage(50); // Attempt to reply to an unpublished comment. $reply_loaded->status->value = COMMENT_NOT_PUBLISHED; $reply_loaded->save(); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $reply_loaded->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $reply_loaded->id()); $this->assertText(t('The comment you are replying to does not exist.'), 'Replying to an unpublished comment'); // Attempt to post to node with comments disabled. - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_HIDDEN)); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(array('status' => COMMENT_HIDDEN)))); $this->assertTrue($this->node, 'Article node created.'); - $this->drupalGet('comment/reply/' . $this->node->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); $this->assertText('This discussion is closed', 'Posting to node with comments disabled'); $this->assertNoField('edit-comment', 'Comment body field found.'); // Attempt to post to node with read-only comments. - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_CLOSED)); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(array('status' => COMMENT_CLOSED)))); $this->assertTrue($this->node, 'Article node created.'); - $this->drupalGet('comment/reply/' . $this->node->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); $this->assertText('This discussion is closed', 'Posting to node with comments read-only'); $this->assertNoField('edit-comment', 'Comment body field found.'); // Attempt to post to node with comments enabled (check field names etc). - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(array('status' => COMMENT_OPEN)))); $this->assertTrue($this->node, 'Article node created.'); - $this->drupalGet('comment/reply/' . $this->node->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); $this->assertNoText('This discussion is closed', 'Posting to node with comments enabled'); $this->assertField('edit-comment-body-' . $langcode . '-0-value', 'Comment body field found.'); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php index 4064b22..0c7abfa 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php @@ -70,6 +70,9 @@ function setUp() { $edit = array('preferred_langcode' => 'fr'); $this->drupalPost("user/" . $admin_user->id() . "/edit", $edit, t('Save')); + // Create comment field on article. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); + // Make comment body translatable. $field = field_info_field('comment', 'comment_body'); $field['translatable'] = TRUE; @@ -98,6 +101,7 @@ function testCommentLanguage() { "title" => $title, "body[$langcode_not_specified][0][value]" => $this->randomName(), "langcode" => $node_langcode, + "comment[$langcode_not_specified][0][status]" => COMMENT_OPEN, ); $this->drupalPost("node/add/article", $edit, t('Save')); $node = $this->drupalGetNodeByTitle($title); @@ -117,7 +121,9 @@ function testCommentLanguage() { // Check that comment language matches the current content language. $cid = db_select('comment', 'c') ->fields('c', array('cid')) - ->condition('nid', $node->id()) + ->condition('entity_id', $node->id()) + ->condition('entity_type', 'node') + ->condition('field_id', 'node__comment') ->orderBy('cid', 'DESC') ->range(0, 1) ->execute() diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php index bf7cdb5..fb2ca6f 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php @@ -67,7 +67,7 @@ function testCommentLinks() { // test; there is only a difference between open and closed registration. 'user_register' => array(USER_REGISTER_VISITORS, USER_REGISTER_ADMINISTRATORS_ONLY), // @todo Complete test coverage for: - //'comments' => array(COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, COMMENT_NODE_HIDDEN), + //'comments' => array(COMMENT_OPEN, COMMENT_CLOSED, COMMENT_HIDDEN), //// COMMENT_ANONYMOUS_MUST_CONTACT is irrelevant for this test. //'contact ' => array(COMMENT_ANONYMOUS_MAY_CONTACT, COMMENT_ANONYMOUS_MAYNOT_CONTACT), ); @@ -94,8 +94,7 @@ function testCommentLinks() { * USER_REGISTER_VISITORS. * - contact: COMMENT_ANONYMOUS_MAY_CONTACT or * COMMENT_ANONYMOUS_MAYNOT_CONTACT. - * - comments: COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, or - * COMMENT_NODE_HIDDEN. + * - comments: COMMENT_OPEN, COMMENT_CLOSED, or COMMENT_HIDDEN. * - User permissions: * These are granted or revoked for the user, according to the * 'authenticated' flag above. Pass 0 or 1 as parameter values. See @@ -116,7 +115,7 @@ function setEnvironment(array $info) { 'form' => COMMENT_FORM_BELOW, 'user_register' => USER_REGISTER_VISITORS, 'contact' => COMMENT_ANONYMOUS_MAY_CONTACT, - 'comments' => COMMENT_NODE_OPEN, + 'comments' => COMMENT_OPEN, 'access comments' => 0, 'post comments' => 0, // Enabled by default, because it's irrelevant for this test. @@ -142,8 +141,9 @@ function setEnvironment(array $info) { // $this->postComment() relies on actual user permissions. $comment = entity_create('comment', array( 'cid' => NULL, - 'nid' => $this->node->id(), - 'node_type' => $this->node->getType(), + 'entity_id' => $this->node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'pid' => 0, 'uid' => 0, 'status' => COMMENT_PUBLISHED, @@ -167,8 +167,8 @@ function setEnvironment(array $info) { } // Change comment settings. - variable_set('comment_form_location_' . $this->node->getType(), $info['form']); - variable_set('comment_anonymous_' . $this->node->getType(), $info['contact']); + $this->setCommentSettings('form_location', $info['form'], 'Set comment form location'); + $this->setCommentAnonymous($info['contact']); if ($this->node->comment->value != $info['comments']) { $this->node->comment = $info['comments']; $this->node->save(); @@ -194,9 +194,9 @@ function setEnvironment(array $info) { COMMENT_ANONYMOUS_MUST_CONTACT => 'required', ); $t_comments = array( - COMMENT_NODE_OPEN => 'open', - COMMENT_NODE_CLOSED => 'closed', - COMMENT_NODE_HIDDEN => 'hidden', + COMMENT_OPEN => 'open', + COMMENT_CLOSED => 'closed', + COMMENT_HIDDEN => 'hidden', ); $verbose = $info; $verbose['form'] = $t_form[$info['form']]; @@ -295,12 +295,12 @@ function assertCommentLinks(array $info) { // Verify that the "Add new comment" link points to the correct URL // based on the comment form location configuration. if ($info['form'] == COMMENT_FORM_SEPARATE_PAGE) { - $this->assertLinkByHref("comment/reply/$nid#comment-form", 0, 'Comment form link destination is on a separate page.'); + $this->assertLinkByHref("comment/reply/node/$nid/comment#comment-form", 0, 'Comment form link destination is on a separate page.'); $this->assertNoLinkByHref("node/$nid#comment-form"); } else { $this->assertLinkByHref("node/$nid#comment-form", 0, 'Comment form link destination is on node.'); - $this->assertNoLinkByHref("comment/reply/$nid#comment-form"); + $this->assertNoLinkByHref("comment/reply/node/$nid/comment#comment-form"); } } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php index 55deb61..94b3fda 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php @@ -38,7 +38,6 @@ public function testCommentNewCommentsIndicator() { // Test if the right links are displayed when no comment is present for the // node. $this->drupalLogin($this->admin_user); - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); $this->drupalGet('node'); $this->assertNoLink(t('@count comments', array('@count' => 0))); $this->assertNoLink(t('@count new comments', array('@count' => 0))); @@ -48,8 +47,9 @@ public function testCommentNewCommentsIndicator() { // comment settings so use $comment->save() to avoid complex setup. $comment = entity_create('comment', array( 'cid' => NULL, - 'nid' => $this->node->id(), - 'node_type' => $this->node->getType(), + 'entity_id' => $this->node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'pid' => 0, 'uid' => $this->loggedInUser->id(), 'status' => COMMENT_PUBLISHED, diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php index 9a4dc17..36411c8 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php @@ -57,7 +57,7 @@ function testThreadedCommentView() { $this->setCommentPreview(DRUPAL_DISABLED); $this->setCommentForm(TRUE); $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); $this->drupalLogout(); // Post comment. @@ -73,7 +73,7 @@ function testThreadedCommentView() { $this->assertText($comment_text, 'Individual comment body found.'); // Reply to comment, creating second comment. - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment->id()); $reply_text = $this->randomName(); $reply_subject = $this->randomName(); $reply = $this->postComment(NULL, $reply_text, $reply_subject, TRUE); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeChangesTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeChangesTest.php index 29d72d0..89abd33 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeChangesTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeChangesTest.php @@ -29,5 +29,13 @@ function testNodeDeletion() { $this->assertTrue($comment->id(), 'The comment could be loaded.'); $this->node->delete(); $this->assertFalse(comment_load($comment->id()), 'The comment could not be loaded after the node was deleted.'); + // Make sure the comment field and all its instances are deleted when node + // type is deleted. + $this->assertNotNull(entity_load('field_entity', 'node.comment'), 'Comment field exists'); + $this->assertNotNull(entity_load('field_instance', 'node.article.comment'), 'Comment instance exists'); + // Delete the node type. + entity_delete_multiple('node_type', array($this->node->bundle())); + $this->assertNull(entity_load('field_entity', 'node.comment'), 'Comment field deleted'); + $this->assertNull(entity_load('field_instance', 'node.article.comment'), 'Comment instance deleted'); } } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php index a082931..099d64a 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php @@ -37,7 +37,7 @@ function testCommentPaging() { $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_FLAT, 'Comment paging changed.'); // Set comments to one per page so that we are able to test paging without // needing to insert large numbers of comments. @@ -64,9 +64,8 @@ function testCommentPaging() { $this->assertFalse($this->commentExists($comments[1]), 'Comment 2 does not appear on page 3.'); // Post a reply to the oldest comment and test again. - $replies = array(); $oldest_comment = reset($comments); - $this->drupalGet('comment/reply/' . $node->id() . '/' . $oldest_comment->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $oldest_comment->id()); $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); $this->setCommentsPerPage(2); @@ -78,7 +77,7 @@ function testCommentPaging() { // If we switch to threaded mode, the replies on the oldest comment // should be bumped to the first page and comment 6 should be bumped // to the second page. - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Switched to threaded mode.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Switched to threaded mode.'); $this->drupalGet('node/' . $node->id(), array('query' => array('page' => 0))); $this->assertTrue($this->commentExists($reply, TRUE), 'In threaded mode, reply appears on page 1.'); $this->assertFalse($this->commentExists($comments[1]), 'In threaded mode, comment 2 has been bumped off of page 1.'); @@ -114,19 +113,19 @@ function testCommentOrderingThreading() { $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[1]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[1]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the first comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[0]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[0]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the last comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[2]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[2]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[3]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[3]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // At this point, the comment tree is: @@ -138,7 +137,7 @@ function testCommentOrderingThreading() { // - 2 // - 5 - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_FLAT, 'Comment paging changed.'); $expected_order = array( 0, @@ -152,7 +151,7 @@ function testCommentOrderingThreading() { $this->drupalGet('node/' . $node->id()); $this->assertCommentOrder($comments, $expected_order); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Switched to threaded mode.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Switched to threaded mode.'); $expected_order = array( 0, @@ -214,15 +213,15 @@ function testCommentNewPageIndicator() { $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[1]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[1]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the first comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[0]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[0]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the last comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $comments[2]->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $comments[2]->id()); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // At this point, the comment tree is: @@ -233,7 +232,7 @@ function testCommentNewPageIndicator() { // - 2 // - 5 - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_FLAT, 'Comment paging changed.'); $expected_pages = array( 1 => 5, // Page of comment 5 @@ -246,12 +245,12 @@ function testCommentNewPageIndicator() { $node = node_load($node->id()); foreach ($expected_pages as $new_replies => $expected_page) { - $returned = comment_new_page_count($node->comment_count, $new_replies, $node); + $returned = comment_new_page_count($node->get('comment')->comment_count, $new_replies, $node); $returned_page = is_array($returned) ? $returned['page'] : 0; $this->assertIdentical($expected_page, $returned_page, format_string('Flat mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page))); } - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Switched to threaded mode.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Switched to threaded mode.'); $expected_pages = array( 1 => 5, // Page of comment 5 @@ -264,7 +263,7 @@ function testCommentNewPageIndicator() { $node = node_load($node->id()); foreach ($expected_pages as $new_replies => $expected_page) { - $returned = comment_new_page_count($node->comment_count, $new_replies, $node); + $returned = comment_new_page_count($node->get('comment')->comment_count, $new_replies, $node); $returned_page = is_array($returned) ? $returned['page'] : 0; $this->assertEqual($expected_page, $returned_page, format_string('Threaded mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page))); } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php index e69507a..dc83c53 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php @@ -68,7 +68,7 @@ function testCommentPreview() { $this->setCommentPreview(DRUPAL_OPTIONAL); $this->setCommentForm(TRUE); $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); $this->drupalLogout(); // Login as web user and add a signature and a user picture. @@ -112,7 +112,7 @@ function testCommentEditPreviewSave() { $this->setCommentPreview(DRUPAL_OPTIONAL); $this->setCommentForm(TRUE); $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); $edit = array(); $date = new DrupalDateTime('2008-03-02 17:23'); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php index ab9ab97..9d55446 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php @@ -39,7 +39,7 @@ function testCommentRss() { $this->assertRaw($raw, 'Comments as part of RSS feed.'); // Hide comments from RSS feed and check presence. - $this->node->comment = COMMENT_NODE_HIDDEN; + $this->node->set('comment', COMMENT_HIDDEN); $this->node->save(); $this->drupalGet('rss.xml'); $this->assertNoRaw($raw, 'Hidden comments is not a part of RSS feed.'); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentStatisticsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentStatisticsTest.php index e471421..4db4dec 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentStatisticsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentStatisticsTest.php @@ -44,15 +44,15 @@ function testCommentNodeCommentStatistics() { $this->setCommentPreview(DRUPAL_DISABLED); $this->setCommentForm(TRUE); $this->setCommentSubject(FALSE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); $this->drupalLogout(); // Checks the initial values of node comment statistics with no comment. $node = node_load($this->node->id()); - $this->assertEqual($node->last_comment_timestamp, $this->node->getCreatedTime(), 'The initial value of node last_comment_timestamp is the node created date.'); - $this->assertEqual($node->last_comment_name, NULL, 'The initial value of node last_comment_name is NULL.'); - $this->assertEqual($node->last_comment_uid, $this->web_user->id(), 'The initial value of node last_comment_uid is the node uid.'); - $this->assertEqual($node->comment_count, 0, 'The initial value of node comment_count is zero.'); + $this->assertEqual($node->get('comment')->last_comment_timestamp, $this->node->getCreatedTime(), 'The initial value of node last_comment_timestamp is the node created date.'); + $this->assertEqual($node->get('comment')->last_comment_name, NULL, 'The initial value of node last_comment_name is NULL.'); + $this->assertEqual($node->get('comment')->last_comment_uid, $this->web_user->id(), 'The initial value of node last_comment_uid is the node uid.'); + $this->assertEqual($node->get('comment')->comment_count, 0, 'The initial value of node comment_count is zero.'); // Post comment #1 as web_user2. $this->drupalLogin($this->web_user2); @@ -62,9 +62,9 @@ function testCommentNodeCommentStatistics() { // Checks the new values of node comment statistics with comment #1. // The node needs to be reloaded with a node_load_multiple cache reset. $node = node_load($this->node->id(), TRUE); - $this->assertEqual($node->last_comment_name, NULL, 'The value of node last_comment_name is NULL.'); - $this->assertEqual($node->last_comment_uid, $this->web_user2->id(), 'The value of node last_comment_uid is the comment #1 uid.'); - $this->assertEqual($node->comment_count, 1, 'The value of node comment_count is 1.'); + $this->assertEqual($node->get('comment')->last_comment_name, NULL, 'The value of node last_comment_name is NULL.'); + $this->assertEqual($node->get('comment')->last_comment_uid, $this->web_user2->id(), 'The value of node last_comment_uid is the comment #1 uid.'); + $this->assertEqual($node->get('comment')->comment_count, 1, 'The value of node comment_count is 1.'); // Prepare for anonymous comment submission (comment approval enabled). $this->drupalLogin($this->admin_user); @@ -78,16 +78,17 @@ function testCommentNodeCommentStatistics() { $this->drupalLogout(); // Post comment #2 as anonymous (comment approval enabled). - $this->drupalGet('comment/reply/' . $this->node->id()); - $this->postComment($this->node, $this->randomName(), '', TRUE); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); + $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', TRUE); + $comment_unpublished_loaded = comment_load($anonymous_comment->id()); // Checks the new values of node comment statistics with comment #2 and // ensure they haven't changed since the comment has not been moderated. // The node needs to be reloaded with a node_load_multiple cache reset. $node = node_load($this->node->id(), TRUE); - $this->assertEqual($node->last_comment_name, NULL, 'The value of node last_comment_name is still NULL.'); - $this->assertEqual($node->last_comment_uid, $this->web_user2->id(), 'The value of node last_comment_uid is still the comment #1 uid.'); - $this->assertEqual($node->comment_count, 1, 'The value of node comment_count is still 1.'); + $this->assertEqual($node->get('comment')->last_comment_name, NULL, 'The value of node last_comment_name is still NULL.'); + $this->assertEqual($node->get('comment')->last_comment_uid, $this->web_user2->id(), 'The value of node last_comment_uid is still the comment #1 uid.'); + $this->assertEqual($node->get('comment')->comment_count, 1, 'The value of node comment_count is still 1.'); // Prepare for anonymous comment submission (no approval required). $this->drupalLogin($this->admin_user); @@ -99,15 +100,16 @@ function testCommentNodeCommentStatistics() { $this->drupalLogout(); // Post comment #3 as anonymous. - $this->drupalGet('comment/reply/' . $this->node->id()); - $comment_loaded = $this->postComment($this->node, $this->randomName(), '', array('name' => $this->randomName())); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment'); + $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', array('name' => $this->randomName())); + $comment_loaded = comment_load($anonymous_comment->id()); // Checks the new values of node comment statistics with comment #3. // The node needs to be reloaded with a node_load_multiple cache reset. $node = node_load($this->node->id(), TRUE); - $this->assertEqual($node->last_comment_name, $comment_loaded->name->value, 'The value of node last_comment_name is the name of the anonymous user.'); - $this->assertEqual($node->last_comment_uid, 0, 'The value of node last_comment_uid is zero.'); - $this->assertEqual($node->comment_count, 2, 'The value of node comment_count is 2.'); + $this->assertEqual($node->get('comment')->last_comment_name, $comment_loaded->name->value, 'The value of node last_comment_name is the name of the anonymous user.'); + $this->assertEqual($node->get('comment')->last_comment_uid, 0, 'The value of node last_comment_uid is zero.'); + $this->assertEqual($node->get('comment')->comment_count, 2, 'The value of node comment_count is 2.'); } } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php index 8b9ab47..c914082 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php @@ -73,6 +73,9 @@ function setUp() { 'access content', )); + // Create comment field on article. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); + // Create a test node authored by the web user. $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->id())); } @@ -98,12 +101,18 @@ function postComment($entity, $comment, $subject = '', $contact = NULL) { $edit = array(); $edit['comment_body[' . $langcode . '][0][value]'] = $comment; - $preview_mode = variable_get('comment_preview_article', DRUPAL_OPTIONAL); - $subject_mode = variable_get('comment_subject_field_article', 1); + if ($entity !== NULL) { + $instance = $this->container->get('field.info')->getInstance('node', $entity->bundle(), 'comment'); + } + else { + $instance = $this->container->get('field.info')->getInstance('node', 'article', 'comment'); + } + $preview_mode = $instance->settings['preview']; + $subject_mode = $instance->settings['subject']; // Must get the page before we test for fields. if ($entity !== NULL) { - $this->drupalGet('comment/reply/' . $entity->id()); + $this->drupalGet('comment/reply/node/' . $entity->id() . '/comment'); } if ($subject_mode == TRUE) { @@ -197,7 +206,7 @@ function deleteComment(CommentInterface $comment) { * Boolean specifying whether the subject field should be enabled. */ function setCommentSubject($enabled) { - $this->setCommentSettings('comment_subject_field', ($enabled ? '1' : '0'), 'Comment subject ' . ($enabled ? 'enabled' : 'disabled') . '.'); + $this->setCommentSettings('subject', ($enabled ? '1' : '0'), 'Comment subject ' . ($enabled ? 'enabled' : 'disabled') . '.'); } /** @@ -220,7 +229,7 @@ function setCommentPreview($mode) { $mode_text = 'required'; break; } - $this->setCommentSettings('comment_preview', $mode, format_string('Comment preview @mode_text.', array('@mode_text' => $mode_text))); + $this->setCommentSettings('preview', $mode, format_string('Comment preview @mode_text.', array('@mode_text' => $mode_text))); } /** @@ -231,7 +240,7 @@ function setCommentPreview($mode) { * comments; FALSE if it should be displayed on its own page. */ function setCommentForm($enabled) { - $this->setCommentSettings('comment_form_location', ($enabled ? COMMENT_FORM_BELOW : COMMENT_FORM_SEPARATE_PAGE), 'Comment controls ' . ($enabled ? 'enabled' : 'disabled') . '.'); + $this->setCommentSettings('form_location', ($enabled ? COMMENT_FORM_BELOW : COMMENT_FORM_SEPARATE_PAGE), 'Comment controls ' . ($enabled ? 'enabled' : 'disabled') . '.'); } /** @@ -244,7 +253,7 @@ function setCommentForm($enabled) { * - 2: Contact information required. */ function setCommentAnonymous($level) { - $this->setCommentSettings('comment_anonymous', $level, format_string('Anonymous commenting set to level @level.', array('@level' => $level))); + $this->setCommentSettings('anonymous', $level, format_string('Anonymous commenting set to level @level.', array('@level' => $level))); } /** @@ -254,7 +263,7 @@ function setCommentAnonymous($level) { * Comments per page value. */ function setCommentsPerPage($number) { - $this->setCommentSettings('comment_default_per_page', $number, format_string('Number of comments per page set to @number.', array('@number' => $number))); + $this->setCommentSettings('per_page', $number, format_string('Number of comments per page set to @number.', array('@number' => $number))); } /** @@ -268,7 +277,9 @@ function setCommentsPerPage($number) { * Status message to display. */ function setCommentSettings($name, $value, $message) { - variable_set($name . '_article', $value); + $instance = $this->container->get('field.info')->getInstance('node', 'article', 'comment'); + $instance->settings[$name] = $value; + $instance->save(); // Display status message. $this->pass($message); } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php index a36ae73..0f7bd2a 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php @@ -31,7 +31,7 @@ function testCommentThreading() { $this->setCommentPreview(DRUPAL_DISABLED); $this->setCommentForm(TRUE); $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->setCommentSettings('default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); $this->drupalLogout(); // Create a node. @@ -51,7 +51,7 @@ function testCommentThreading() { // Reply to comment #1 creating comment #2. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment1->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment1->id()); $comment2 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $this->assertTrue($this->commentExists($comment2, TRUE), 'Comment #2. Reply found.'); @@ -60,7 +60,7 @@ function testCommentThreading() { $this->assertParentLink($comment2->id(), $comment1->id()); // Reply to comment #2 creating comment #3. - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment2->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment2->id()); $comment3 = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Confirm that the comment was created and has the correct threading. $this->assertTrue($this->commentExists($comment3, TRUE), 'Comment #3. Second reply found.'); @@ -70,7 +70,7 @@ function testCommentThreading() { // Reply to comment #1 creating comment #4. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment1->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment1->id()); $comment4 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $this->assertTrue($this->commentExists($comment4), 'Comment #4. Third reply found.'); @@ -91,7 +91,7 @@ function testCommentThreading() { // Reply to comment #5 creating comment #6. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment5->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment5->id()); $comment6 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $this->assertTrue($this->commentExists($comment6, TRUE), 'Comment #6. Reply found.'); @@ -100,7 +100,7 @@ function testCommentThreading() { $this->assertParentLink($comment6->id(), $comment5->id()); // Reply to comment #6 creating comment #7. - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment6->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment6->id()); $comment7 = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Confirm that the comment was created and has the correct threading. $this->assertTrue($this->commentExists($comment7, TRUE), 'Comment #7. Second reply found.'); @@ -110,7 +110,7 @@ function testCommentThreading() { // Reply to comment #5 creating comment #8. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->id() . '/' . $comment5->id()); + $this->drupalGet('comment/reply/node/' . $this->node->id() . '/comment/' . $comment5->id()); $comment8 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $this->assertTrue($this->commentExists($comment8), 'Comment #8. Third reply found.'); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php index 1819b10..7e157d4 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php @@ -42,7 +42,7 @@ function testCommentTokenReplacement() { $parent_comment = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the comment. - $this->drupalGet('comment/reply/' . $node->id() . '/' . $parent_comment->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $parent_comment->id()); $child_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); $comment = comment_load($child_comment->id()); $comment->homepage->value = 'http://example.org/'; @@ -66,7 +66,7 @@ function testCommentTokenReplacement() { $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed->value, 2, $language_interface->id); $tests['[comment:parent:cid]'] = $comment->pid->target_id; $tests['[comment:parent:title]'] = check_plain($parent_comment->subject->value); - $tests['[comment:node:nid]'] = $comment->nid->target_id; + $tests['[comment:node:nid]'] = $comment->entity_id->value; $tests['[comment:node:title]'] = check_plain($node->getTitle()); $tests['[comment:author:uid]'] = $comment->uid->target_id; $tests['[comment:author:name]'] = check_plain($this->admin_user->getUsername()); @@ -100,11 +100,11 @@ function testCommentTokenReplacement() { // Generate comment tokens for the node (it has 2 comments, both new). $tests = array(); - $tests['[node:comment-count]'] = 2; - $tests['[node:comment-count-new]'] = 2; + $tests['[entity:comment-count]'] = 2; + $tests['[entity:comment-count-new]'] = 2; foreach ($tests as $input => $expected) { - $output = $token_service->replace($input, array('node' => $node), array('langcode' => $language_interface->id)); + $output = $token_service->replace($input, array('entity' => $node), array('langcode' => $language_interface->id)); $this->assertEqual($output, $expected, format_string('Node comment token %token replaced.', array('%token' => $input))); } } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php index 96e2d20..759a142 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php @@ -37,7 +37,7 @@ public static function getInfo() { function setUp() { $this->entityType = 'comment'; $this->nodeBundle = 'article'; - $this->bundle = 'comment_node_' . $this->nodeBundle; + $this->bundle = 'node__comment_article'; $this->testLanguageSelector = FALSE; $this->subject = $this->randomName(); parent::setUp(); @@ -49,6 +49,16 @@ function setUp() { function setupBundle() { parent::setupBundle(); $this->drupalCreateContentType(array('type' => $this->nodeBundle, 'name' => $this->nodeBundle)); + // Add a comment field to the article content type. + $this->container->get('comment.manager')->addDefaultField('node', 'article', 'comment_article'); + // Add another comment field with new bundle to page content type. + $this->container->get('comment.manager')->addDefaultField('node', 'page'); + // Mark this bundle as translatable. + content_translation_set_config('comment', 'node__comment_article', 'enabled', TRUE); + // Refresh entity info. + entity_info_cache_clear(); + // Flush the permissions after adding the translatable comment bundle. + $this->checkPermissions(array(), TRUE); } /** @@ -71,14 +81,26 @@ function setupTestFields() { /** * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::createEntity(). */ - protected function createEntity($values, $langcode, $node_bundle = NULL) { - if (!isset($node_bundle)) { - $node_bundle = $this->nodeBundle; + protected function createEntity($values, $langcode, $node_bundle = 'node__comment_article') { + if ($node_bundle == 'node__comment_article') { + $node_type = 'article'; + $field_name = 'comment_article'; } - $node = $this->drupalCreateNode(array('type' => $node_bundle)); - $values['nid'] = $node->id(); + else { + $node_type = 'page'; + $field_name = 'comment'; + } + $node = $this->drupalCreateNode(array( + 'type' => $node_type, + $field_name => array( + array('status' => COMMENT_OPEN) + ), + )); + $values['entity_id'] = $node->id(); + $values['entity_type'] = 'node'; + $values['field_id'] = $node_bundle; $values['uid'] = $node->getAuthorId(); - return parent::createEntity($values, $langcode); + return parent::createEntity($values, $langcode, $node_bundle); } /** @@ -128,8 +150,8 @@ function testTranslateLinkCommentAdminPage() { $this->admin_user = $this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), array('access administration pages', 'administer comments'))); $this->drupalLogin($this->admin_user); - $cid_translatable = $this->createEntity(array(), $this->langcodes[0], $this->nodeBundle); - $cid_untranslatable = $this->createEntity(array(), $this->langcodes[0], 'page'); + $cid_translatable = $this->createEntity(array(), $this->langcodes[0]); + $cid_untranslatable = $this->createEntity(array(), $this->langcodes[0], 'comment'); // Verify translation links. $this->drupalGet('admin/content/comment'); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentUninstallTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentUninstallTest.php index 127a611..1bed5df 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentUninstallTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentUninstallTest.php @@ -32,9 +32,10 @@ public static function getInfo() { protected function setUp() { parent::setup(); - // Create a content type so that the comment module creates the - // 'comment_body' field upon installation. - $this->drupalCreateContentType(); + // Create an article content type. + $this->drupalCreateContentType(array('type' => 'article', 'name' => t('Article'))); + // Create comment field on article so that adds 'comment_body' field. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); } /** diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentUserTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentUserTest.php new file mode 100644 index 0000000..b5cdaeb --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentUserTest.php @@ -0,0 +1,402 @@ + 'Comment user tests', + 'description' => 'Test commenting on users.', + 'group' => 'Comment', + ); + } + + function setUp() { + parent::setUp(); + + // Create comment field on user bundle. + $this->container->get('comment.manager')->addDefaultField('user', 'user'); + + // Create two test users. + $this->admin_user = $this->drupalCreateUser(array( + 'administer comments', + 'skip comment approval', + 'post comments', + 'access comments', + 'access content', + 'administer users', + 'access user profiles', + )); + $this->web_user = $this->drupalCreateUser(array( + 'access comments', + 'post comments', + 'edit own comments', + 'post comments', + 'skip comment approval', + 'access content', + 'access user profiles', + )); + + // Enable anonymous and authenticated user comments. + user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments', + 'post comments', + 'skip comment approval', + )); + user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array( + 'access comments', + 'post comments', + 'skip comment approval', + )); + } + + /** + * Posts a comment. + * + * @param \Drupal\user\UserInterface|null $account + * User to post comment on or NULL to post to the previously loaded page. + * @param $comment + * Comment body. + * @param $subject + * Comment subject. + * @param $contact + * Set to NULL for no contact info, TRUE to ignore success checking, and + * array of values to set contact info. + */ + function postComment(UserInterface $account, $comment, $subject = '', $contact = NULL) { + $langcode = Language::LANGCODE_NOT_SPECIFIED; + $edit = array(); + $edit['comment_body[' . $langcode . '][0][value]'] = $comment; + + $instance = field_info_instance('user', 'comment', 'user'); + $preview_mode = $instance['settings']['preview']; + $subject_mode = $instance['settings']['subject']; + + // Must get the page before we test for fields. + if ($account !== NULL) { + $this->drupalGet('comment/reply/user/' . $account->id() . '/comment'); + } + + if ($subject_mode == TRUE) { + $edit['subject'] = $subject; + } + else { + $this->assertNoFieldByName('subject', '', 'Subject field not found.'); + } + + if ($contact !== NULL && is_array($contact)) { + $edit += $contact; + } + switch ($preview_mode) { + case DRUPAL_REQUIRED: + // Preview required so no save button should be found. + $this->assertNoFieldByName('op', t('Save'), 'Save button not found.'); + $this->drupalPost(NULL, $edit, t('Preview')); + // Don't break here so that we can test post-preview field presence and + // function below. + case DRUPAL_OPTIONAL: + $this->assertFieldByName('op', t('Preview'), 'Preview button found.'); + $this->assertFieldByName('op', t('Save'), 'Save button found.'); + $this->drupalPost(NULL, $edit, t('Save')); + break; + + case DRUPAL_DISABLED: + $this->assertNoFieldByName('op', t('Preview'), 'Preview button not found.'); + $this->assertFieldByName('op', t('Save'), 'Save button found.'); + $this->drupalPost(NULL, $edit, t('Save')); + break; + } + $match = array(); + // Get comment ID + preg_match('/#comment-([0-9]+)/', $this->getURL(), $match); + + // Get comment. + if ($contact !== TRUE) { // If true then attempting to find error message. + if ($subject) { + $this->assertText($subject, 'Comment subject posted.'); + } + $this->assertText($comment, 'Comment body posted.'); + $this->assertTrue((!empty($match) && !empty($match[1])), 'Comment id found.'); + } + + if (isset($match[1])) { + return comment_load($match[1]); + } + } + + /** + * Checks current page for specified comment. + * + * @param \Drupal\comment\CommentInterface $comment + * The comment object. + * @param boolean $reply + * Boolean indicating whether the comment is a reply to another comment. + * + * @return boolean + * Boolean indicating whether the comment was found. + */ + function commentExists(CommentInterface $comment = NULL, $reply = FALSE) { + if ($comment) { + $regex = '/' . ($reply ? '
(.*?)' : ''); + $regex .= 'subject->value . '(.*?)'; // Match subject. + $regex .= $comment->comment_body->value . '(.*?)'; // Match comment. + $regex .= '/s'; + + return (boolean)preg_match($regex, $this->drupalGetContent()); + } + else { + return FALSE; + } + } + + /** + * Deletes a comment. + * + * @param \Drupal\comment\CommentInterface $comment + * Comment to delete. + */ + function deleteComment(CommentInterface $comment) { + $this->drupalPost('comment/' . $comment->id() . '/delete', array(), t('Delete')); + $this->assertText(t('The comment and all its replies have been deleted.'), 'Comment deleted.'); + } + + /** + * Checks whether the commenter's contact information is displayed. + * + * @return boolean + * Contact info is available. + */ + function commentContactInfoAvailable() { + return preg_match('/(input).*?(name="name").*?(input).*?(name="mail").*?(input).*?(name="homepage")/s', $this->drupalGetContent()); + } + + /** + * Performs the specified operation on the specified comment. + * + * @param object $comment + * Comment to perform operation on. + * @param string $operation + * Operation to perform. + * @param boolean $aproval + * Operation is found on approval page. + */ + function performCommentOperation($comment, $operation, $approval = FALSE) { + $edit = array(); + $edit['operation'] = $operation; + $edit['comments[' . $comment->id() . ']'] = TRUE; + $this->drupalPost('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update')); + + if ($operation == 'delete') { + $this->drupalPost(NULL, array(), t('Delete comments')); + $this->assertRaw(format_plural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', array('@operation' => $operation))); + } + else { + $this->assertText(t('The update has been performed.'), format_string('Operation "@operation" was performed on comment.', array('@operation' => $operation))); + } + } + + /** + * Gets the comment ID for an unapproved comment. + * + * @param string $subject + * Comment subject to find. + * + * @return integer + * Comment id. + */ + function getUnapprovedComment($subject) { + $this->drupalGet('admin/content/comment/approval'); + preg_match('/href="(.*?)#comment-([^"]+)"(.*?)>(' . $subject . ')/', $this->drupalGetContent(), $match); + + return $match[2]; + } + + /** + * Tests anonymous comment functionality. + */ + function testCommentUser() { + $limited_user = $this->drupalCreateUser(array( + 'administer user fields' + )); + $this->drupalLogin($limited_user); + // Test that default field exists. + $this->drupalGet('admin/config/people/accounts/fields'); + $this->assertText(t('Comment settings')); + $this->assertLinkByHref('admin/config/people/accounts/fields/user.user.comment'); + // Test widget hidden option is not visible when there's no comments. + $this->drupalGet('admin/config/people/accounts/fields/user.user.comment'); + $this->assertNoField('edit-default-value-input-comment-und-0-status-0'); + + $this->drupalLogin($this->admin_user); + + // Post a comment. + $comment1 = $this->postComment($this->web_user, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($comment1), 'Comment on web user exists.'); + + // Assert the breadcrumb is valid. + $this->drupalGet('comment/reply/user/' . $this->web_user->id() . '/comment'); + $this->assertLink($this->web_user->label()); + + // Unpublish comment. + $this->performCommentOperation($comment1, 'unpublish'); + + $this->drupalGet('admin/content/comment/approval'); + $this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was unpublished.'); + + // Publish comment. + $this->performCommentOperation($comment1, 'publish', TRUE); + + $this->drupalGet('admin/content/comment'); + $this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was published.'); + + // Delete comment. + $this->performCommentOperation($comment1, 'delete'); + + $this->drupalGet('admin/content/comment'); + $this->assertNoRaw('comments[' . $comment1->id() . ']', 'Comment was deleted.'); + + // Post another comment. + $comment1 = $this->postComment($this->web_user, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($comment1), 'Comment on web user exists.'); + + // Check comment was found. + $this->drupalGet('admin/content/comment'); + $this->assertRaw('comments[' . $comment1->id() . ']', 'Comment was published.'); + + // Check that entity access applies to administrative page. + $this->assertText($this->web_user->label(), 'Name of commented account found.'); + $limited_user = $this->drupalCreateUser(array( + 'administer comments', + )); + $this->drupalLogin($limited_user); + $this->drupalGet('admin/content/comment'); + $this->assertNoText($this->web_user->label(), 'No commented account name found.'); + + $this->drupalLogout(); + + // Reset. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => FALSE, + 'post comments' => FALSE, + 'skip comment approval' => FALSE, + 'access user profiles' => TRUE, + )); + + // Attempt to view comments while disallowed. + // NOTE: if authenticated user has permission to post comments, then a + // "Login or register to post comments" type link may be shown. + $this->drupalGet('user/' . $this->web_user->id()); + $this->assertNoPattern('@]*>Comments@', 'Comments were not displayed.'); + $this->assertNoLink('Add new comment', 'Link to add comment was found.'); + + // Attempt to view user-comment form while disallowed. + $this->drupalGet('comment/reply/user/' . $this->web_user->id() . '/comment'); + $this->assertText('You are not authorized to post comments', 'Error attempting to post comment.'); + $this->assertNoFieldByName('subject', '', 'Subject field not found.'); + $langcode = Language::LANGCODE_NOT_SPECIFIED; + $this->assertNoFieldByName("comment_body[$langcode][0][value]", '', 'Comment field not found.'); + + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => FALSE, + 'access user profiles' => TRUE, + 'skip comment approval' => FALSE, + )); + // Ensure the page cache is flushed. + drupal_flush_all_caches(); + $this->drupalGet('user/' . $this->web_user->id()); + $this->assertPattern('@]*>Comments@', 'Comments were displayed.'); + $this->assertLink('Log in', 0, 'Link to log in was found.'); + $this->assertLink('register', 0, 'Link to register was found.'); + + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => FALSE, + 'post comments' => TRUE, + 'skip comment approval' => TRUE, + 'access user profiles' => TRUE, + )); + $this->drupalGet('user/' . $this->web_user->id()); + $this->assertNoPattern('@]*>Comments@', 'Comments were not displayed.'); + $this->assertFieldByName('subject', '', 'Subject field found.'); + $this->assertFieldByName("comment_body[$langcode][0][value]", '', 'Comment field found.'); + + $this->drupalGet('comment/reply/user/' . $this->web_user->id() . '/comment/' . $comment1->id()); + $this->assertText('You are not authorized to view comments'); + $this->assertNoText($comment1->subject->value, 'Comment not displayed.'); + + // Test comment field widget changes. + $limited_user = $this->drupalCreateUser(array( + 'administer user fields' + )); + $this->drupalLogin($limited_user); + $this->drupalGet('admin/config/people/accounts/fields/user.user.comment'); + $this->assertNoField('edit-default-value-input-comment-und-0-status-0'); + $this->assertNoFieldChecked('edit-default-value-input-comment-und-0-status-1'); + $this->assertFieldChecked('edit-default-value-input-comment-und-0-status-2'); + // Test hidden option change in field settings. + $edit = array('default_value_input[comment][und][0][status]' => COMMENT_CLOSED); + $this->drupalPost(NULL, $edit, t('Save settings')); + $this->drupalGet('admin/config/people/accounts/fields/user.user.comment'); + $this->assertNoFieldChecked('edit-default-value-input-comment-und-0-status-0'); + $this->assertFieldChecked('edit-default-value-input-comment-und-0-status-1'); + $this->assertNoFieldChecked('edit-default-value-input-comment-und-0-status-2'); + $this->drupalGet('user/' . $this->web_user->id()); + $this->assertNoPattern('@]*>Comments@', 'Comments were not displayed.'); + $this->assertNoLink('Add new comment', 'Link to add comment was found.'); + + // Test the new user commenting inherits default. + $limited_user = $this->drupalCreateUser(array( + 'access user profiles', + )); + $this->drupalLogin($limited_user); + $this->drupalGet('user/' . $limited_user->id() . '/edit'); + // @todo somehow default status applied, should be 1 checked. + $this->assertFieldChecked('edit-comment-und-0-status-2'); + $this->assertNoFieldChecked('edit-comment-und-0-status-1'); + $this->assertNoFieldChecked('edit-comment-und-0-status-0'); + + $this->drupalGet('comment/reply/user/comment/' . $limited_user->id()); + $this->assertNoFieldByName('subject', '', 'Subject field found.'); + $this->assertNoFieldByName("comment_body[$langcode][0][value]", '', 'Comment field found.'); + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php b/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php index 7027ef4..3dee6d0 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php @@ -40,12 +40,16 @@ function setUp() { $this->account2 = $this->drupalCreateUser(); $this->drupalLogin($this->account); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); + $this->node_user_posted = $this->drupalCreateNode(); $this->node_user_commented = $this->drupalCreateNode(array('uid' => $this->account2->id())); $comment = array( 'uid' => $this->loggedInUser->id(), - 'nid' => $this->node_user_commented->id(), + 'entity_id' => $this->node_user_commented->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'cid' => '', 'pid' => '', 'node_type' => '', diff --git a/core/modules/comment/lib/Drupal/comment/Tests/Views/DefaultViewRecentComments.php b/core/modules/comment/lib/Drupal/comment/Tests/Views/DefaultViewRecentComments.php index f4a4316..3f3759e 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/Views/DefaultViewRecentComments.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/Views/DefaultViewRecentComments.php @@ -73,13 +73,22 @@ public function setUp() { 'type' => $content_type->type, ); + $this->container->get('comment.manager')->addDefaultField('node', $content_type->type); $this->node = $this->drupalCreateNode($node_data); + // Force a flush of the in-memory storage. + $this->container->get('views.views_data')->clear(); + // Create some comments and attach them to the created node. for ($i = 0; $i < $this->masterDisplayResults; $i++) { - $comment = entity_create('comment', array('node_type' => 'comment_node_' . $this->node->getType())); + $comment = entity_create('comment', array( + 'field_name' => 'comment', + 'entity_type' => 'node', + 'entity_id' => $this->node->id(), + )); $comment->uid->target_id = 0; - $comment->nid->target_id = $this->node->id(); + // Stagger the comments so the timestamp sorting works. + $comment->created->value = REQUEST_TIME - $i; $comment->subject->value = 'Test comment ' . $i; $comment->comment_body->value = 'Test body ' . $i; $comment->comment_body->format = 'full_html'; @@ -108,14 +117,14 @@ public function testBlockDisplay() { $this->executeView($view); $map = array( - 'comment_nid' => 'nid', + 'comment_entity_id' => 'entity_id', 'comment_subject' => 'subject', 'cid' => 'cid', 'comment_changed' => 'changed' ); $expected_result = array(); foreach (array_values($this->commentsCreated) as $key => $comment) { - $expected_result[$key]['nid'] = $comment->nid->target_id; + $expected_result[$key]['entity_id'] = $comment->entity_id->value; $expected_result[$key]['subject'] = $comment->subject->value; $expected_result[$key]['cid'] = $comment->id(); $expected_result[$key]['changed'] = $comment->changed->value; @@ -139,7 +148,7 @@ public function testPageDisplay() { $this->executeView($view); $map = array( - 'comment_nid' => 'nid', + 'comment_entity_id' => 'entity_id', 'comment_subject' => 'subject', 'comment_changed' => 'changed', 'comment_changed' => 'created', @@ -147,7 +156,7 @@ public function testPageDisplay() { ); $expected_result = array(); foreach (array_values($this->commentsCreated) as $key => $comment) { - $expected_result[$key]['nid'] = $comment->nid->target_id; + $expected_result[$key]['entity_id'] = $comment->entity_id->value; $expected_result[$key]['subject'] = $comment->subject->value; $expected_result[$key]['changed'] = $comment->changed->value; $expected_result[$key]['created'] = $comment->created->value; diff --git a/core/modules/comment/lib/Drupal/comment/Tests/Views/WizardTest.php b/core/modules/comment/lib/Drupal/comment/Tests/Views/WizardTest.php index de1487a..f030d71 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/Views/WizardTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/Views/WizardTest.php @@ -36,6 +36,8 @@ public static function getInfo() { * Tests adding a view of comments. */ public function testCommentWizard() { + // Add comment field to page node type. + $this->container->get('comment.manager')->addDefaultField('node', 'page'); $view = array(); $view['label'] = $this->randomName(16); $view['id'] = strtolower($this->randomName(16)); diff --git a/core/modules/comment/templates/comment-wrapper.html.twig b/core/modules/comment/templates/comment-wrapper.html.twig index a75ef11..b19c051 100644 --- a/core/modules/comment/templates/comment-wrapper.html.twig +++ b/core/modules/comment/templates/comment-wrapper.html.twig @@ -18,7 +18,7 @@ * be displayed after the main title tag that appears in the template. * * The following variables are provided for contextual information. - * - node: The node entity to which the comments belong. + * - entity: The entity to which the comments belong. * - display_mode: The display mode for the comment listing, flat or threaded. * The constants below show the possible values and should be used for * comparison, as in the following example: @@ -36,7 +36,7 @@ */ #} - {% if comments and node.type != 'forum' %} + {% if comments and (entity.entityType != 'node' or entity.bundle != 'forum') %} {{ title_prefix }}

{{ 'Comments'|t }}

{{ title_suffix }} diff --git a/core/modules/comment/templates/comment.html.twig b/core/modules/comment/templates/comment.html.twig index 11dab84..7467d1a 100644 --- a/core/modules/comment/templates/comment.html.twig +++ b/core/modules/comment/templates/comment.html.twig @@ -28,7 +28,8 @@ * through CSS. The default values can be one or more of the following: * - comment: The current template type; e.g., 'theming hook'. * - by-anonymous: Comment by an unregistered user. - * - by-node-author: Comment by the author of the parent node. + * - by-{entity-type}-author: Comment by the author of the parent entity, + * eg. by-node-author. * - preview: When previewing a new or edited comment. * The following applies only to viewers who are registered users: * - unpublished: An unpublished comment visible only to administrators. @@ -56,7 +57,7 @@ * * These two variables are provided for context: * - comment: Full comment object. - * - node: Node entity the comments are attached to. + * - entity: Entity the comments are attached to. * * @see template_preprocess_comment() * diff --git a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_row.yml b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_row.yml index 40c25a0..5e8b96e 100644 --- a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_row.yml +++ b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_row.yml @@ -64,11 +64,11 @@ display: links: '1' view_mode: full relationships: - nid: - id: nid - table: comment - field: nid + node: + field: node + id: node required: '1' + table: comment relationship: none group_type: group admin_label: Content @@ -118,7 +118,7 @@ display: value: '1' table: node_field_data field: status - relationship: nid + relationship: node id: status_node expose: operator: '0' diff --git a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_rss.yml b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_rss.yml index e87ac87..18255cf 100644 --- a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_rss.yml +++ b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_rss.yml @@ -25,11 +25,11 @@ display: row: type: fields relationships: - nid: - id: nid - table: comment - field: nid + node: + field: node + id: node required: '1' + table: comment provider: views fields: subject: diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php index f4e2f13..6e5eb85 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSettingsTest.php @@ -20,7 +20,7 @@ class ContentTranslationSettingsTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'content_translation', 'comment'); + public static $modules = array('language', 'content_translation', 'comment', 'field_ui'); public static function getInfo() { return array( @@ -37,8 +37,9 @@ function setUp() { // bundles. $this->drupalCreateContentType(array('type' => 'article')); $this->drupalCreateContentType(array('type' => 'page')); + $this->container->get('comment.manager')->addDefaultField('node', 'article', 'comment_article'); - $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content translation', 'administer content types')); + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content translation', 'administer content types', 'administer comment fields')); $this->drupalLogin($admin_user); } @@ -48,21 +49,21 @@ function setUp() { function testSettingsUI() { // Test that the translation settings are ignored if the bundle is marked // translatable but the entity type is not. - $edit = array('settings[comment][comment_node_article][translatable]' => TRUE); + $edit = array('settings[comment][node__comment_article][translatable]' => TRUE); $this->assertSettings('comment', NULL, FALSE, $edit); // Test that the translation settings are ignored if only a field is marked // as translatable and not the related entity type and bundle. - $edit = array('settings[comment][comment_node_article][fields][comment_body]' => TRUE); + $edit = array('settings[comment][node__comment_article][fields][comment_body]' => TRUE); $this->assertSettings('comment', NULL, FALSE, $edit); // Test that the translation settings are not stored if an entity type and // bundle are marked as translatable but no field is. $edit = array( 'entity_types[comment]' => TRUE, - 'settings[comment][comment_node_article][translatable]' => TRUE, + 'settings[comment][node__comment_article][translatable]' => TRUE, ); - $this->assertSettings('comment', 'comment_node_article', FALSE, $edit); + $this->assertSettings('comment', 'node__comment_article', FALSE, $edit); $xpath_err = '//div[contains(@class, "error")]'; $this->assertTrue($this->xpath($xpath_err), 'Enabling translation only for entity bundles generates a form error.'); @@ -70,37 +71,37 @@ function testSettingsUI() { // language is set as default and the language selector is hidden. $edit = array( 'entity_types[comment]' => TRUE, - 'settings[comment][comment_node_article][settings][language][langcode]' => Language::LANGCODE_NOT_SPECIFIED, - 'settings[comment][comment_node_article][settings][language][language_show]' => FALSE, - 'settings[comment][comment_node_article][translatable]' => TRUE, - 'settings[comment][comment_node_article][fields][comment_body]' => TRUE, + 'settings[comment][node__comment_article][settings][language][langcode]' => Language::LANGCODE_NOT_SPECIFIED, + 'settings[comment][node__comment_article][settings][language][language_show]' => FALSE, + 'settings[comment][node__comment_article][translatable]' => TRUE, + 'settings[comment][node__comment_article][fields][comment_body]' => TRUE, ); - $this->assertSettings('comment', 'comment_node_article', FALSE, $edit); + $this->assertSettings('comment', 'node__comment_article', FALSE, $edit); $this->assertTrue($this->xpath($xpath_err), 'Enabling translation with a fixed non-configurable language generates a form error.'); // Test that a field shared among different bundles can be enabled without // needing to make all the related bundles translatable. $edit = array( 'entity_types[comment]' => TRUE, - 'settings[comment][comment_node_article][settings][language][langcode]' => 'current_interface', - 'settings[comment][comment_node_article][settings][language][language_show]' => TRUE, - 'settings[comment][comment_node_article][translatable]' => TRUE, - 'settings[comment][comment_node_article][fields][comment_body]' => TRUE, + 'settings[comment][node__comment_article][settings][language][langcode]' => 'current_interface', + 'settings[comment][node__comment_article][settings][language][language_show]' => TRUE, + 'settings[comment][node__comment_article][translatable]' => TRUE, + 'settings[comment][node__comment_article][fields][comment_body]' => TRUE, ); - $this->assertSettings('comment', 'comment_node_article', TRUE, $edit); + $this->assertSettings('comment', 'node__comment_article', TRUE, $edit); field_info_cache_clear(); $field = field_info_field('comment', 'comment_body'); $this->assertTrue($field['translatable'], 'Comment body is translatable.'); // Test that language settings are correctly stored. - $language_configuration = language_get_default_configuration('comment', 'comment_node_article'); + $language_configuration = language_get_default_configuration('comment', 'node__comment_article'); $this->assertEqual($language_configuration['langcode'], 'current_interface', 'The default language for article comments is set to the current interface language.'); $this->assertTrue($language_configuration['language_show'], 'The language selector for article comments is shown.'); // Verify language widget appears on node type form. - $this->drupalGet('admin/structure/types/manage/article'); - $this->assertField('content_translation'); - $this->assertFieldChecked('edit-content-translation'); + $this->drupalGet('admin/structure/comments/manage/node__comment_article/fields/comment.node__comment_article.comment_body/field'); + $this->assertField('field[translatable]'); + $this->assertFieldChecked('edit-field-translatable'); // Verify that translation may be enabled for the article content type. $edit = array( diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceSelectionAccessTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceSelectionAccessTest.php index b97dce4..aba580c 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceSelectionAccessTest.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceSelectionAccessTest.php @@ -398,9 +398,14 @@ public function testCommentHandler() { $nodes[$key] = $node; } + // Create comment field on article. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); + $comment_values = array( 'published_published' => array( - 'nid' => $nodes['published']->id(), + 'entity_id' => $nodes['published']->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => 1, 'cid' => NULL, 'pid' => 0, @@ -409,7 +414,9 @@ public function testCommentHandler() { 'language' => Language::LANGCODE_NOT_SPECIFIED, ), 'published_unpublished' => array( - 'nid' => $nodes['published']->id(), + 'entity_id' => $nodes['published']->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => 1, 'cid' => NULL, 'pid' => 0, @@ -418,7 +425,9 @@ public function testCommentHandler() { 'language' => Language::LANGCODE_NOT_SPECIFIED, ), 'unpublished_published' => array( - 'nid' => $nodes['unpublished']->id(), + 'entity_id' => $nodes['unpublished']->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => 1, 'cid' => NULL, 'pid' => 0, @@ -447,7 +456,7 @@ public function testCommentHandler() { array(NULL, 'CONTAINS'), ), 'result' => array( - 'comment_node_article' => array( + 'node__comment' => array( $comments['published_published']->cid->value => $comment_labels['published_published'], ), ), @@ -457,7 +466,7 @@ public function testCommentHandler() { array('Published', 'CONTAINS'), ), 'result' => array( - 'comment_node_article' => array( + 'node__comment' => array( $comments['published_published']->cid->value => $comment_labels['published_published'], ), ), @@ -486,7 +495,7 @@ public function testCommentHandler() { array(NULL, 'CONTAINS'), ), 'result' => array( - 'comment_node_article' => array( + 'node__comment' => array( $comments['published_published']->cid->value => $comment_labels['published_published'], $comments['published_unpublished']->cid->value => $comment_labels['published_unpublished'], ), @@ -504,7 +513,7 @@ public function testCommentHandler() { array(NULL, 'CONTAINS'), ), 'result' => array( - 'comment_node_article' => array( + 'node__comment' => array( $comments['published_published']->cid->value => $comment_labels['published_published'], $comments['published_unpublished']->cid->value => $comment_labels['published_unpublished'], $comments['unpublished_published']->cid->value => $comment_labels['unpublished_published'], diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php index 3c6aa76..04199c2 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php @@ -204,6 +204,9 @@ function testMultiValuedWidget() { * Tests a file field with a "Private files" upload destination setting. */ function testPrivateFileSetting() { + // Grant the admin user required permissions. + user_role_grant_permissions($this->admin_user->roles[0]->value, array('administer node fields')); + $type_name = 'article'; $field_name = strtolower($this->randomName()); $this->createFileField($field_name, 'node', $type_name); @@ -242,7 +245,7 @@ function testPrivateFileComment() { // Grant the admin user required comment permissions. $roles = $this->admin_user->getRoles(); - user_role_grant_permissions($roles[1], array('administer comment fields')); + user_role_grant_permissions($roles[1], array('administer comment fields', 'administer comments')); // Revoke access comments permission from anon user, grant post to // authenticated. @@ -250,12 +253,13 @@ function testPrivateFileComment() { user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('post comments', 'skip comment approval')); // Create a new field. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); $edit = array( 'fields[_add_new_field][label]' => $label = $this->randomName(), 'fields[_add_new_field][field_name]' => $name = strtolower($this->randomName()), 'fields[_add_new_field][type]' => 'file', ); - $this->drupalPost('admin/structure/types/manage/article/comment/fields', $edit, t('Save')); + $this->drupalPost('admin/structure/comments/manage/node__comment/fields', $edit, t('Save')); $edit = array('field[settings][uri_scheme]' => 'private'); $this->drupalPost(NULL, $edit, t('Save field settings')); $this->drupalPost(NULL, array(), t('Save settings')); @@ -276,7 +280,7 @@ function testPrivateFileComment() { 'files[field_' . $name . '_' . Language::LANGCODE_NOT_SPECIFIED . '_' . 0 . ']' => drupal_realpath($text_file->getFileUri()), 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $comment_body = $this->randomName(), ); - $this->drupalPost(NULL, $edit, t('Save')); + $this->drupalPost('node/' . $node->id(), $edit, t('Save')); // Get the comment ID. preg_match('/comment-([0-9]+)/', $this->getUrl(), $matches); diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php index 90aaf1c..f3f560e 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php @@ -67,6 +67,8 @@ function setUp() { // Setup a node to comment and test on. $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + // Add a comment field. + $this->container->get('comment.manager')->addDefaultField('node', 'page'); $this->node = $this->drupalCreateNode(); } diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index e4b1e26..16bc861 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -109,6 +109,16 @@ function forum_enable() { )) ->save(); } + // Add the comment field to the forum node type. + $fields = entity_load_multiple_by_properties('field_entity', array( + 'type' => 'comment', + 'name' => 'comment_forum', + 'include_inactive' => TRUE, + 'include_deleted' => FALSE, + )); + if (empty($fields)) { + Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum', COMMENT_OPEN); + } } /** @@ -134,6 +144,13 @@ function forum_uninstall() { $field->delete(); } + // Load the dependent Comment module, in case it has been disabled. + drupal_load('module', 'comment'); + + if ($field = field_info_field('node', 'comment_forum')) { + $field->delete(); + } + if ($field = field_info_field('taxonomy_term', 'forum_container')) { $field->delete(); } @@ -231,7 +248,7 @@ function forum_schema() { 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, - 'default'=> 0, + 'default' => 0, ), 'last_comment_timestamp' => array( 'type' => 'int', @@ -266,7 +283,6 @@ function forum_schema() { ), ); - return $schema; } diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 0f076c4..1ba79f4 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -6,6 +6,8 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Language\Language; +use Drupal\taxonomy\Entity\Term; use Drupal\field\Field; use Drupal\node\NodeInterface; @@ -482,7 +484,9 @@ function forum_permission() { * $comment->save() calls hook_comment_publish() for all published comments. */ function forum_comment_publish($comment) { - Drupal::service('forum_manager')->updateIndex($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + Drupal::service('forum_manager')->updateIndex($comment->entity_id->value); + } } /** @@ -494,8 +498,8 @@ function forum_comment_publish($comment) { function forum_comment_update($comment) { // $comment->save() calls hook_comment_publish() for all published comments, // so we need to handle all other values here. - if (!$comment->status->value) { - Drupal::service('forum_manager')->updateIndex($comment->nid->target_id); + if (!$comment->status->value && $comment->entity_type->value == 'node') { + Drupal::service('forum_manager')->updateIndex($comment->entity_id->value); } } @@ -503,14 +507,18 @@ function forum_comment_update($comment) { * Implements hook_comment_unpublish(). */ function forum_comment_unpublish($comment) { - Drupal::service('forum_manager')->updateIndex($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + Drupal::service('forum_manager')->updateIndex($comment->entity_id->value); + } } /** * Implements hook_comment_delete(). */ function forum_comment_delete($comment) { - Drupal::service('forum_manager')->updateIndex($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + Drupal::service('forum_manager')->updateIndex($comment->entity_id->value); + } } /** @@ -798,7 +806,7 @@ function template_preprocess_forum_topic_list(&$variables) { $variables['topics'][$id]->new_url = ''; if ($topic->new_replies) { $variables['topics'][$id]->new_text = format_plural($topic->new_replies, '1 new post in topic %title', '@count new posts in topic %title', array('%title' => $variables['topics'][$id]->label())); - $variables['topics'][$id]->new_url = url('node/' . $topic->id(), array('query' => comment_new_page_count($topic->comment_count, $topic->new_replies, $topic), 'fragment' => 'new')); + $variables['topics'][$id]->new_url = url('node/' . $topic->id(), array('query' => comment_new_page_count($topic->comment_count, $topic->new_replies, $topic, 'comment_node_forum'), 'fragment' => 'new')); } } @@ -842,7 +850,7 @@ function template_preprocess_forum_icon(&$variables) { $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic'); } - if ($variables['comment_mode'] == COMMENT_NODE_CLOSED || $variables['comment_mode'] == COMMENT_NODE_HIDDEN) { + if ($variables['comment_mode'] == COMMENT_CLOSED || $variables['comment_mode'] == COMMENT_HIDDEN) { $icon_status_class = 'closed'; $variables['icon_title'] = t('Closed topic'); } diff --git a/core/modules/forum/lib/Drupal/forum/ForumManager.php b/core/modules/forum/lib/Drupal/forum/ForumManager.php index 3ea82f2..ce7af95 100644 --- a/core/modules/forum/lib/Drupal/forum/ForumManager.php +++ b/core/modules/forum/lib/Drupal/forum/ForumManager.php @@ -185,23 +185,23 @@ public function getTopics($tid) { ->extend('Drupal\Core\Database\Query\TableSortExtender'); $query->fields('n', array('nid')); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); - $query->fields('ncs', array( + $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_id = 'node__comment_forum' AND ces.entity_type = 'node'"); + $query->fields('ces', array( 'cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count' )); - $query->join('forum_index', 'f', 'f.nid = ncs.nid'); + $query->join('forum_index', 'f', 'f.nid = n.nid'); $query->addField('f', 'tid', 'forum_tid'); $query->join('users', 'u', 'n.uid = u.uid'); $query->addField('u', 'name'); - $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid'); + $query->join('users', 'u2', 'ces.last_comment_uid = u2.uid'); - $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name'); + $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u2.name END', 'last_comment_name'); $query ->orderBy('f.sticky', 'DESC') @@ -356,12 +356,12 @@ protected function getLastPost($tid) { // Query "Last Post" information for this forum. $query = $this->connection->select('node_field_data', 'n'); $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $tid)); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); - $query->join('users', 'u', 'ncs.last_comment_uid = u.uid'); - $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u.name END', 'last_comment_name'); + $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_id = 'node__comment_forum' AND ces.entity_type = 'node'"); + $query->join('users', 'u', 'ces.last_comment_uid = u.uid'); + $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u.name END', 'last_comment_name'); $topic = $query - ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid')) + ->fields('ces', array('last_comment_timestamp', 'last_comment_uid')) ->condition('n.status', 1) ->orderBy('last_comment_timestamp', 'DESC') ->range(0, 1) @@ -394,10 +394,10 @@ protected function getForumStatistics($tid) { if (empty($this->forumStatistics)) { // Prime the statistics. $query = $this->connection->select('node_field_data', 'n'); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); + $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_id = 'node__comment_forum' AND ces.entity_type = 'node'"); $query->join('forum', 'f', 'n.vid = f.vid'); $query->addExpression('COUNT(n.nid)', 'topic_count'); - $query->addExpression('SUM(ncs.comment_count)', 'comment_count'); + $query->addExpression('SUM(ces.comment_count)', 'comment_count'); $this->forumStatistics = $query ->fields('f', array('tid')) ->condition('n.status', 1) @@ -515,14 +515,14 @@ public function unreadTopics($term, $uid) { * {@inheritdoc} */ public function updateIndex($nid) { - $count = $this->connection->query('SELECT COUNT(cid) FROM {comment} c INNER JOIN {forum_index} i ON c.nid = i.nid WHERE c.nid = :nid AND c.status = :status', array( + $count = $this->connection->query("SELECT COUNT(cid) FROM {comment} c INNER JOIN {forum_index} i ON c.entity_id = i.nid WHERE c.entity_id = :nid AND c.field_id = 'node__comment_forum' AND c.entity_type = 'node' AND c.status = :status", array( ':nid' => $nid, ':status' => COMMENT_PUBLISHED, ))->fetchField(); if ($count > 0) { // Comments exist. - $last_reply = $this->connection->queryRange('SELECT cid, name, created, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array( + $last_reply = $this->connection->queryRange("SELECT cid, name, created, uid FROM {comment} WHERE entity_id = :nid AND field_id = 'node__comment_forum' AND entity_type = 'node' AND status = :status ORDER BY cid DESC", 0, 1, array( ':nid' => $nid, ':status' => COMMENT_PUBLISHED, ))->fetchObject(); diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php index 2ba2d86..112185e 100644 --- a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php +++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php @@ -105,7 +105,9 @@ public function testActiveForumTopicsBlock() { $node = $this->drupalGetNodeByTitle($topics[$index]); $date->modify('+1 minute'); $comment = entity_create('comment', array( - 'nid' => $node->id(), + 'entity_id' => $node->id(), + 'field_name' => 'comment_forum', + 'entity_type' => 'node', 'node_type' => 'node_type_' . $node->bundle(), 'subject' => $this->randomString(20), 'comment_body' => $this->randomString(256), diff --git a/core/modules/forum/lib/Drupal/forum/Tests/Views/ForumIntegrationTest.php b/core/modules/forum/lib/Drupal/forum/Tests/Views/ForumIntegrationTest.php index 2a04b4c..d402db6 100644 --- a/core/modules/forum/lib/Drupal/forum/Tests/Views/ForumIntegrationTest.php +++ b/core/modules/forum/lib/Drupal/forum/Tests/Views/ForumIntegrationTest.php @@ -65,9 +65,9 @@ public function testForumIntegration() { $comments = array(); foreach ($nodes as $index => $node) { for ($i = 0; $i <= $index; $i++) { - $comment = $comment_storage_controller->create(array('node_type' => 'node_type_forum', 'nid' => $node->id())); + $comment = $comment_storage_controller->create(array('entity_type' => 'node', 'entity_id' => $node->id(), 'field_name' => 'comment_forum')); $comment->save(); - $comments[$comment->get('nid')->target_id][$comment->id()] = $comment; + $comments[$comment->get('entity_id')->target_id][$comment->id()] = $comment; } } diff --git a/core/modules/history/lib/Drupal/history/Plugin/views/field/HistoryUserTimestamp.php b/core/modules/history/lib/Drupal/history/Plugin/views/field/HistoryUserTimestamp.php index 62915f4..91db271 100644 --- a/core/modules/history/lib/Drupal/history/Plugin/views/field/HistoryUserTimestamp.php +++ b/core/modules/history/lib/Drupal/history/Plugin/views/field/HistoryUserTimestamp.php @@ -36,7 +36,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o $this->additional_fields['created'] = array('table' => 'node_field_data', 'field' => 'created'); $this->additional_fields['changed'] = array('table' => 'node_field_data', 'field' => 'changed'); if (module_exists('comment') && !empty($this->options['comments'])) { - $this->additional_fields['last_comment'] = array('table' => 'node_comment_statistics', 'field' => 'last_comment_timestamp'); + $this->additional_fields['last_comment'] = array('table' => 'comment_entity_statistics', 'field' => 'last_comment_timestamp'); } } } diff --git a/core/modules/history/lib/Drupal/history/Plugin/views/filter/HistoryUserTimestamp.php b/core/modules/history/lib/Drupal/history/Plugin/views/filter/HistoryUserTimestamp.php index beae395..20eaac4 100644 --- a/core/modules/history/lib/Drupal/history/Plugin/views/filter/HistoryUserTimestamp.php +++ b/core/modules/history/lib/Drupal/history/Plugin/views/filter/HistoryUserTimestamp.php @@ -76,9 +76,9 @@ public function query() { $clause = ''; $clause2 = ''; if (module_exists('comment')) { - $ncs = $this->query->ensureTable('node_comment_statistics', $this->relationship); - $clause = ("OR $ncs.last_comment_timestamp > (***CURRENT_TIME*** - $limit)"); - $clause2 = "OR $field < $ncs.last_comment_timestamp"; + $ces = $this->query->ensureTable('comment_entity_statistics', $this->relationship); + $clause = ("OR $ces.last_comment_timestamp > (***CURRENT_TIME*** - $limit)"); + $clause2 = "OR $field < $ces.last_comment_timestamp"; } // NULL means a history record doesn't exist. That's clearly new content. diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php index f69bf1a..af49fbf 100644 --- a/core/modules/node/lib/Drupal/node/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php @@ -379,11 +379,6 @@ public static function baseFieldDefinitions($entity_type) { 'value' => array('EntityChanged' => array()), ), ); - $properties['comment'] = array( - 'label' => t('Comment'), - 'description' => t('Whether comments are allowed on this node: 0 = no, 1 = closed (read only), 2 = open (read/write).'), - 'type' => 'integer_field', - ); $properties['promote'] = array( 'label' => t('Promote'), 'description' => t('A boolean indicating whether the node should be displayed on the front page.'), diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index 1360af3..9a8b862 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -71,16 +71,6 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) { } /** - * {@inheritdoc} - */ - protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) { - // @todo Remove this once comment is a regular entity field. - $record = parent::mapToDataStorageRecord($entity, $langcode); - $record->comment = isset($record->comment) ? intval($record->comment) : 0; - return $record; - } - - /** * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete(). */ protected function postDelete($nodes) { diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/row/NodeRow.php b/core/modules/node/lib/Drupal/node/Plugin/views/row/NodeRow.php index 5f1df9b..01ea81b 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/views/row/NodeRow.php +++ b/core/modules/node/lib/Drupal/node/Plugin/views/row/NodeRow.php @@ -27,7 +27,6 @@ protected function defineOptions() { $options['view_mode']['default'] = 'teaser'; $options['links'] = array('default' => TRUE, 'bool' => TRUE); - $options['comments'] = array('default' => FALSE, 'bool' => TRUE); return $options; } @@ -43,11 +42,6 @@ public function buildOptionsForm(&$form, &$form_state) { '#title' => t('Display links'), '#default_value' => $this->options['links'], ); - $form['comments'] = array( - '#type' => 'checkbox', - '#title' => t('Display comments'), - '#default_value' => $this->options['comments'], - ); } } diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php b/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php index 29e6a46..56e394b 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php +++ b/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php @@ -116,16 +116,6 @@ protected function buildFormStyle(array &$form, array &$form_state, $type) { ), '#default_value' => 1, ); - $style_form['row_options']['comments'] = array( - '#type' => 'select', - '#title_display' => 'invisible', - '#title' => t('Should comments be displayed below each node'), - '#options' => array( - 1 => t('with comments'), - 0 => t('without comments'), - ), - '#default_value' => 0, - ); break; } } @@ -223,13 +213,11 @@ protected function display_options_row(&$display_options, $row_plugin, $row_opt $display_options['row']['type'] = 'entity:node'; $display_options['row']['options']['build_mode'] = 'full'; $display_options['row']['options']['links'] = !empty($row_options['links']); - $display_options['row']['options']['comments'] = !empty($row_options['comments']); break; case 'teasers': $display_options['row']['type'] = 'entity:node'; $display_options['row']['options']['build_mode'] = 'teaser'; $display_options['row']['options']['links'] = !empty($row_options['links']); - $display_options['row']['options']['comments'] = !empty($row_options['comments']); break; case 'titles_linked': $display_options['row']['type'] = 'fields'; diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php index 287ec65..020ced2 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php @@ -34,6 +34,7 @@ public function setUp() { parent::setUp(); node_access_rebuild(); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'node test view')); } @@ -47,8 +48,9 @@ public function testCommentPager() { // Create 60 comments. for ($i = 0; $i < 60; $i++) { $comment = entity_create('comment', array( - 'nid' => $node->id(), - 'node_type' => 'node_type_' . $node->bundle(), + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'subject' => $this->randomName(), 'comment_body' => array( array('value' => $this->randomName()), diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php index c23332e..40015ec 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php @@ -34,6 +34,7 @@ function setUp() { $this->admin_user = $this->drupalCreateUser(array('administer nodes', 'create article content', 'create page content', 'post comments')); $this->drupalLogin($this->admin_user); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); } /** @@ -54,7 +55,7 @@ function testNodeTitle() { $this->assertEqual(current($this->xpath($xpath)), $node->label() .' | Drupal', 'Page title is equal to node title.', 'Node'); // Test breadcrumb in comment preview. - $this->drupalGet('comment/reply/' . $node->id()); + $this->drupalGet('comment/reply/node/' . $node->id() . '/comment'); $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a'; $this->assertEqual(current($this->xpath($xpath)), $node->label(), 'Node breadcrumb is equal to node title.', 'Node'); diff --git a/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php b/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php index 6d347f9..e2b4f45 100644 --- a/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php @@ -55,6 +55,8 @@ protected function setUp() { parent::setUp(); $this->drupalCreateContentType(array('type' => 'article')); + // Create comment field on article. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); // Create two nodes, with 5 comments on all of them. for ($i = 0; $i < 2; $i++) { @@ -74,7 +76,7 @@ protected function setUp() { foreach ($this->nodes as $node) { for ($i = 0; $i < 5; $i++) { - $this->comments[$node->id()][] = $this->drupalCreateComment(array('nid' => $node->id())); + $this->comments[$node->id()][] = $this->drupalCreateComment(array('entity_id' => $node->id())); } } } @@ -90,10 +92,11 @@ protected function setUp() { * Returns the created and saved comment. */ public function drupalCreateComment(array $settings = array()) { - $node = node_load($settings['nid']); $settings += array( 'subject' => $this->randomName(), - 'node_type' => 'comment_node_' . $node->bundle(), + 'entity_id' => $settings['entity_id'], + 'field_name' => 'comment', + 'entity_type' => 'node', 'comment_body' => $this->randomName(40), ); @@ -147,25 +150,6 @@ public function testRowPlugin() { $this->assertTrue($this->xpath('//li[contains(@class, :class)]/a[contains(@href, :href)]', array(':class' => 'node-readmore', ':href' => "node/{$node->id()}")), 'Make sure no readmore link appears.'); } - // Test with comments enabled. - $view->rowPlugin->options['comments'] = TRUE; - $output = $view->preview(); - $output = drupal_render($output); - foreach ($this->nodes as $node) { - foreach ($this->comments[$node->id()] as $comment) { - $this->assertTrue(strpos($output, $comment->comment_body->value) !== FALSE, 'Make sure the comment appears in the output.'); - } - } - - // Test with comments disabled. - $view->rowPlugin->options['comments'] = FALSE; - $output = $view->preview(); - $output = drupal_render($output); - foreach ($this->nodes as $node) { - foreach ($this->comments[$node->id()] as $comment) { - $this->assertFalse(strpos($output, $comment->comment_body->value) !== FALSE, 'Make sure the comment does not appears in the output when the comments option disabled.'); - } - } } } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 59d27b5..2405a38 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -1,6 +1,8 @@ get('types'); if (count(array_intersect($types_we_want_to_process, $types))) { // Gather our extra data for each of these nodes. $result = db_query('SELECT nid, foo FROM {mytable} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes))); @@ -604,8 +607,8 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $lang * @ingroup node_api_hooks */ function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $form_display, $operation, array &$form_state) { - if (!isset($node->comment->value)) { - $node->comment = variable_get('comment_' . $node->getType(), COMMENT_NODE_OPEN); + if (!isset($node->my_rating)) { + $node->my_rating = Drupal::config("my_rating_{$node->bundle()}")->get('enabled'); } } @@ -632,8 +635,8 @@ function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $form_display, * @ingroup node_api_hooks */ function hook_node_search_result(\Drupal\Core\Entity\EntityInterface $node, $langcode) { - $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->id()))->fetchField(); - return array('comment' => format_plural($comments, '1 comment', '@count comments')); + $rating = db_query('SELECT SUM(points) FROM {my_rating} WHERE nid = :nid', array('nid' => $node->id()))->fetchField(); + return array('rating' => format_plural($rating, '1 point', '@count points')); } /** @@ -701,9 +704,9 @@ function hook_node_update(\Drupal\Core\Entity\EntityInterface $node) { */ function hook_node_update_index(\Drupal\Core\Entity\EntityInterface $node, $langcode) { $text = ''; - $comments = db_query('SELECT subject, comment, format FROM {comment} WHERE nid = :nid AND status = :status', array(':nid' => $node->id(), ':status' => COMMENT_PUBLISHED)); - foreach ($comments as $comment) { - $text .= '

' . check_plain($comment->subject->value) . '

' . $comment->comment_body->processed; + $ratings = db_query('SELECT title, description FROM {my_ratings} WHERE nid = :nid', array(':nid' => $node->id())); + foreach ($ratings as $rating) { + $text .= '

' . String::checkPlain($rating->title) . '

' . Xss::filter($rating->description); } return $text; } @@ -790,7 +793,6 @@ function hook_node_submit(\Drupal\Core\Entity\EntityInterface $node, $form, &$fo * The language code used for rendering. * * @see forum_node_view() - * @see comment_node_view() * @see hook_entity_view() * * @ingroup node_api_hooks diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 02cc7a0..1e36920 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -152,12 +152,6 @@ function node_schema() { 'not null' => TRUE, 'default' => 0, ), - 'comment' => array( - 'description' => 'Whether comments are allowed on this node translation: 0 = no, 1 = closed (read only), 2 = open (read/write).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), 'promote' => array( 'description' => 'Boolean indicating whether the node translation should be displayed on the front page.', 'type' => 'int', @@ -276,12 +270,6 @@ function node_schema() { 'not null' => TRUE, 'default' => 0, ), - 'comment' => array( - 'description' => 'Whether comments are allowed on this node (at the time of this revision): 0 = no, 1 = closed (read only), 2 = open (read/write).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), 'promote' => array( 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed on the front page.', 'type' => 'int', @@ -817,12 +805,6 @@ function _node_update_8016_schema() { 'not null' => TRUE, 'default' => 0, ), - 'comment' => array( - 'description' => 'Whether comments are allowed on this node translation: 0 = no, 1 = closed (read only), 2 = open (read/write).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), 'promote' => array( 'description' => 'Boolean indicating whether the node translation should be displayed on the front page.', 'type' => 'int', @@ -939,12 +921,6 @@ function _node_update_8016_schema() { 'not null' => TRUE, 'default' => 0, ), - 'comment' => array( - 'description' => 'Whether comments are allowed on this node (at the time of this revision): 0 = no, 1 = closed (read only), 2 = open (read/write).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), 'promote' => array( 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed on the front page.', 'type' => 'int', @@ -1041,7 +1017,7 @@ function node_update_8018() { foreach ($indexes as $index) { db_drop_index('node', $index); } - $fields = array('title', 'uid', 'status', 'created', 'changed', 'comment', 'promote', 'sticky'); + $fields = array('title', 'uid', 'status', 'created', 'changed', 'promote', 'sticky'); foreach ($fields as $field) { db_drop_field('node', $field); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 5b448f4..b9cb53b 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -241,7 +241,7 @@ function node_admin_paths() { * * @param $result * A database result object from a query to fetch node entities. If your - * query joins the {node_comment_statistics} table so that the comment_count + * query joins the {entity_comment_statistics} table so that the comment_count * field is available, a title attribute will be added to show the number of * comments. * @param $title @@ -2239,7 +2239,9 @@ function node_node_update(EntityInterface $node) { */ function node_comment_insert($comment) { // Reindex the node when comments are added. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } /** @@ -2247,7 +2249,9 @@ function node_comment_insert($comment) { */ function node_comment_update($comment) { // Reindex the node when comments are changed. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } /** @@ -2255,7 +2259,9 @@ function node_comment_update($comment) { */ function node_comment_delete($comment) { // Reindex the node when comments are deleted. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } /** @@ -2263,7 +2269,9 @@ function node_comment_delete($comment) { */ function node_comment_publish($comment) { // Reindex the node when comments are published. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } /** @@ -2271,5 +2279,7 @@ function node_comment_publish($comment) { */ function node_comment_unpublish($comment) { // Reindex the node when comments are unpublished. - node_reindex_node_search($comment->nid->target_id); + if ($comment->entity_type->value == 'node') { + node_reindex_node_search($comment->entity_id->value); + } } diff --git a/core/modules/node/node.views.inc b/core/modules/node/node.views.inc index deacf4d..3d74c44 100644 --- a/core/modules/node/node.views.inc +++ b/core/modules/node/node.views.inc @@ -623,21 +623,11 @@ function node_views_data() { * Implements hook_preprocess_node(). */ function node_row_node_view_preprocess_node(&$variables) { - $node = $variables['node']; $options = $variables['view']->rowPlugin->options; - // Prevent the comment form from showing up if this is not a page display. - if ($variables['view_mode'] == 'full' && !$variables['view']->display_handler->hasPath()) { - $node->comment = FALSE; - } - if (!$options['links']) { unset($variables['content']['links']); } - - if (!empty($options['comments']) && user_access('access comments') && $node->comment->value) { - $variables['content']['comments'] = comment_node_page_additions($node); - } } /** diff --git a/core/modules/node/templates/node.html.twig b/core/modules/node/templates/node.html.twig index daf308a..1f8c823 100644 --- a/core/modules/node/templates/node.html.twig +++ b/core/modules/node/templates/node.html.twig @@ -15,13 +15,6 @@ * - sticky: Whether the node is 'sticky'. Sticky nodes are ordered above * other non-sticky nodes in teaser listings * - published: Whether the node is published. - * - comment: A value representing the comment status of the current node. May - * be one of the following: - * - 0: The comment form and any existing comments are hidden. - * - 1: Comments are closed. No new comments may be posted, but existing - * comments are displayed. - * - 2: Comments are open on this node. - * - comment_count: Number of comments attached to the node. * - label: The title of the node. * - content: All node items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use @@ -100,13 +93,11 @@ {% endif %} - {# We hide the comments and links now so that we can render them later. #} - {% hide(content.comments) %} + {# We hide links now so that we can render them later. #} {% hide(content.links) %} {{ content }}
{{ content.links }} - {{ content.comments }} diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php index 44e147c..8a6b8c7 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php @@ -67,7 +67,7 @@ public function setUp() { $user_mapping->setFieldMapping('homepage', array('properties' => array('foaf:page'), 'mapping_type' => 'rel'))->save(); // Save comment mapping. - $mapping = rdf_get_mapping('comment', 'comment_node_article'); + $mapping = rdf_get_mapping('comment', 'node__comment'); $mapping->setBundleMapping(array('types' => array('sioc:Post', 'sioct:Comment')))->save(); $field_mappings = array( 'subject' => array( @@ -334,10 +334,11 @@ function _testBasicCommentRdfaMarkup($graph, $comment, $account = array()) { */ function saveComment($nid, $uid, $contact = NULL, $pid = 0) { $values = array( - 'nid' => $nid, + 'entity_id' => $nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => $uid, 'pid' => $pid, - 'node_type' => 'comment_node_article', 'subject' => $this->randomName(), 'comment_body' => $this->randomName(), 'status' => 1, diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php index f4d2eb8..98a1783 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php @@ -161,7 +161,7 @@ public function setUp() { $this->drupalCreateNode(array('type' => 'article', 'promote' => NODE_PROMOTED,)); // Create article comment. - $this->articleComment = $this->saveComment($this->article->id(), $this->webUser->id(), NULL, 0, 'comment_node_article'); + $this->articleComment = $this->saveComment($this->article->id(), $this->webUser->id(), NULL, 0); // Create page. $this->page = $this->drupalCreateNode(array('type' => 'page')); @@ -437,8 +437,13 @@ protected function assertRdfaArticleProperties($graph, $message_prefix) { * The EasyRDF graph object. */ protected function assertRdfaNodeCommentProperties($graph) { - // @todo Test relationship between comment and node once it is a field: - // https://drupal.org/node/731724 + // Relationship between node and comment. + $expected_value = array( + 'type' => 'uri', + 'value' => $this->articleCommentUri, + ); + $this->assertTrue($graph->hasProperty($this->articleUri, 'http://schema.org/comment', $expected_value), 'Relationship between node and comment found (schema:comment).'); + // Comment type. $this->assertEqual($graph->type($this->articleCommentUri), 'schema:Comment', 'Comment type was found (schema:Comment).'); @@ -500,18 +505,17 @@ protected function assertRdfaNodeCommentProperties($graph) { * array of values to set contact info. * @param int $pid * Comment id of the parent comment in a thread. - * @param string $bundle - * The bundle of the comment. * * @return \Drupal\comment\Entity\Comment * The saved comment. */ - protected function saveComment($nid, $uid, $contact = NULL, $pid = 0, $bundle = '') { + protected function saveComment($nid, $uid, $contact = NULL, $pid = 0) { $values = array( - 'nid' => $nid, + 'entity_id' => $nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => $uid, 'pid' => $pid, - 'node_type' => $bundle, 'subject' => $this->randomName(), 'comment_body' => $this->randomName(), 'status' => 1, diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php index 072fcc5..09a51b3 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php @@ -87,6 +87,9 @@ function setUp() { 'skip comment approval' => TRUE, )); + // Create comment field on article. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); + // Sets base URI of the site used by the RDFa parser. $this->base_uri = url('', array('absolute' => TRUE)); } @@ -170,7 +173,7 @@ function _testBasicTrackerRdfaMarkup(EntityInterface $node) { 'subject' => $this->randomName(), 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $this->randomName(), ); - $this->drupalPost('comment/reply/' . $node->id(), $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->id() .'/comment', $comment, t('Save')); // Parses tracker page where the nodes are displayed in a table. $parser = new \EasyRdf_Parser_Rdfa(); diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 428f16b..62df08f 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -207,7 +207,9 @@ function rdf_comment_load($comments) { $created_mapping = rdf_get_mapping('comment', $comment->bundle()) ->getPreparedFieldMapping('created'); $comment->rdf_data['date'] = rdf_rdfa_attributes($created_mapping, $comment->created->value); - $comment->rdf_data['nid_uri'] = url('node/' . $comment->nid->target_id); + $entity = entity_load($comment->entity_type->value, $comment->entity_id->value); + $uri = $entity->uri(); + $comment->rdf_data['entity_uri'] = url($uri['path']); if ($comment->pid->target_id) { $comment->rdf_data['pid_uri'] = url('comment/' . $comment->pid->target_id); } @@ -287,32 +289,41 @@ function rdf_preprocess_node(&$variables) { // Adds RDFa markup annotating the number of comments a node has. $comment_count_mapping = $mapping->getPreparedFieldMapping('comment_count'); - if (isset($variables['node']->comment_count) && !empty($comment_count_mapping['properties'])) { - // Annotates the 'x comments' link in teaser view. - if (isset($variables['content']['links']['comment']['#links']['comment-comments'])) { - $comment_count_attributes = rdf_rdfa_attributes($comment_count_mapping, $variables['node']->comment_count); - // According to RDFa parsing rule number 4, a new subject URI is created - // from the href attribute if no rel/rev attribute is present. To get the - // original node URL from the about attribute of the parent container we - // set an empty rel attribute which triggers rule number 5. See - // http://www.w3.org/TR/rdfa-syntax/#sec_5.5. - $comment_count_attributes['rel'] = ''; - $variables['content']['links']['comment']['#links']['comment-comments']['attributes'] += $comment_count_attributes; - } - // In full node view, the number of comments is not displayed by - // node.html.twig so it is expressed in RDFa in the tag of the HTML - // page. - if ($variables['page'] && user_access('access comments')) { - $element = array( - '#tag' => 'meta', - '#attributes' => array( - 'about' => $variables['node_url'], - 'property' => $comment_count_mapping['properties'], - 'content' => $variables['node']->comment_count, - 'datatype' => $comment_count_mapping['datatype'], - ), - ); - drupal_add_html_head($element, 'rdf_node_comment_count'); + if (Drupal::moduleHandler()->moduleExists('comment')) { + if (!empty($comment_count_mapping['properties'])) { + $fields = array_keys(Drupal::service('comment.manager')->getFields('node')); + $definitions = array_keys($variables['node']->getPropertyDefinitions()); + $valid_fields = array_intersect($fields, $definitions); + $count = 0; + foreach ($valid_fields as $field_name) { + $count += $variables['node']->get($field_name)->comment_count; + // Annotates the 'x comments' link in teaser view. + if (isset($variables['content']['links']['comment__' . $field_name]['#links']['comment-comments'])) { + $comment_count_attributes = rdf_rdfa_attributes($comment_count_mapping, $variables['node']->get($field_name)->comment_count); + // According to RDFa parsing rule number 4, a new subject URI is created + // from the href attribute if no rel/rev attribute is present. To get + // the original node URL from the about attribute of the parent + // container we set an empty rel attribute which triggers rule number 5. + // See http://www.w3.org/TR/rdfa-syntax/#sec_5.5. + $comment_count_attributes['rel'] = ''; + $variables['content']['links']['comment__' . $field_name]['#links']['comment-comments']['attributes'] += $comment_count_attributes; + } + } + // In full node view, the number of comments is not displayed by + // node.html.twig so it is expressed in RDFa in the tag of the HTML + // page. + if ($variables['page'] && user_access('access comments')) { + $element = array( + '#tag' => 'meta', + '#attributes' => array( + 'about' => $variables['node_url'], + 'property' => $comment_count_mapping['properties'], + 'content' => $count, + 'datatype' => $comment_count_mapping['datatype'], + ), + ); + drupal_add_html_head($element, 'rdf_node_comment_count'); + } } } } @@ -508,12 +519,12 @@ function rdf_preprocess_comment(&$variables) { // Currently there is no mapping relating a comment to its node. $pid_mapping = $mapping->getPreparedFieldMapping('pid'); if (!empty($pid_mapping)) { - // Adds the relation to the parent node. - $parent_node_attributes['rel'] = $pid_mapping['properties']; - // The parent node URI is precomputed as part of the rdf_data so that it can - // be cached as part of the entity. - $parent_node_attributes['resource'] = $comment->rdf_data['nid_uri']; - $variables['rdf_metadata_attributes'][] = $parent_node_attributes; + // Adds the relation to the parent entity. + $parent_entity_attributes['rel'] = $pid_mapping['properties']; + // The parent entity URI is precomputed as part of the rdf_data so that it + // can be cached as part of the entity. + $parent_entity_attributes['resource'] = $comment->rdf_data['entity_uri']; + $variables['rdf_metadata_attributes'][] = $parent_entity_attributes; // Adds the relation to parent comment, if it exists. if ($comment->pid->target_id != 0) { diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php index aab4c13..3ea7d36 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php @@ -48,6 +48,8 @@ function setUp() { // Create searching user. $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'skip comment approval')); + // Add a comment field. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); // Create initial nodes. $node_params = array('type' => 'article', 'body' => array(array('value' => 'SearchCommentToggleTestCase'))); @@ -63,7 +65,7 @@ function setUp() { $edit_comment['comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]'] = $this->randomName(); // Post comment to the test node with comment - $this->drupalPost('comment/reply/' . $this->searchable_nodes['1 comment']->id(), $edit_comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $this->searchable_nodes['1 comment']->id() . '/comment', $edit_comment, t('Save')); // First update the index. This does the initial processing. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); @@ -89,9 +91,9 @@ function testSearchCommentCountToggle() { $this->assertText(t('1 comment'), 'Non-empty comment count displays for nodes with comment status set to Open'); // Test comment count display for nodes with comment status set to Closed - $this->searchable_nodes['0 comments']->comment = COMMENT_NODE_CLOSED; + $this->searchable_nodes['0 comments']->set('comment', COMMENT_CLOSED); $this->searchable_nodes['0 comments']->save(); - $this->searchable_nodes['1 comment']->comment = COMMENT_NODE_CLOSED; + $this->searchable_nodes['1 comment']->set('comment', COMMENT_CLOSED); $this->searchable_nodes['1 comment']->save(); $this->drupalPost('', $edit, t('Search')); @@ -99,9 +101,9 @@ function testSearchCommentCountToggle() { $this->assertText(t('1 comment'), 'Non-empty comment count displays for nodes with comment status set to Closed'); // Test comment count display for nodes with comment status set to Hidden - $this->searchable_nodes['0 comments']->comment = COMMENT_NODE_HIDDEN; + $this->searchable_nodes['0 comments']->set('comment', COMMENT_HIDDEN); $this->searchable_nodes['0 comments']->save(); - $this->searchable_nodes['1 comment']->comment = COMMENT_NODE_HIDDEN; + $this->searchable_nodes['1 comment']->set('comment', COMMENT_HIDDEN); $this->searchable_nodes['1 comment']->save(); $this->drupalPost('', $edit, t('Search')); diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php index 2b256a2..b06ca0e 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php @@ -49,6 +49,8 @@ function setUp() { ); $this->admin_user = $this->drupalCreateUser($permissions); $this->drupalLogin($this->admin_user); + // Add a comment field. + $this->container->get('comment.manager')->addDefaultField('node', 'article'); } /** @@ -57,7 +59,10 @@ function setUp() { function testSearchResultsComment() { $comment_body = 'Test comment body'; - variable_set('comment_preview_article', DRUPAL_OPTIONAL); + // Make preview optional. + $instance = field_info_instance('node', 'comment', 'article'); + $instance['settings']['preview'] = DRUPAL_OPTIONAL; + $instance->save(); // Enable check_plain() for 'Basic HTML' text format. $basic_html_format_id = 'basic_html'; $edit = array( @@ -80,7 +85,7 @@ function testSearchResultsComment() { $edit_comment['comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]'] = '

' . $comment_body . '

'; $full_html_format_id = 'full_html'; $edit_comment['comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][format]'] = $full_html_format_id; - $this->drupalPost('comment/reply/' . $node->id(), $edit_comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->id() .'/comment', $edit_comment, t('Save')); // Invoke search index update. $this->drupalLogout(); @@ -109,7 +114,7 @@ function testSearchResultsComment() { // Hide comments. $this->drupalLogin($this->admin_user); - $node->comment = 0; + $node->set('comment', COMMENT_HIDDEN); $node->save(); // Invoke search index update. @@ -131,14 +136,17 @@ function testSearchResultsCommentAccess() { $this->admin_role = $roles[0]; // Create a node. - variable_set('comment_preview_article', DRUPAL_OPTIONAL); + // Make preview optional. + $instance = field_info_instance('node', 'comment', 'article'); + $instance['settings']['preview'] = DRUPAL_OPTIONAL; + $instance->save(); $this->node = $this->drupalCreateNode(array('type' => 'article')); // Post a comment using 'Full HTML' text format. $edit_comment = array(); $edit_comment['subject'] = $this->comment_subject; $edit_comment['comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]'] = '

' . $comment_body . '

'; - $this->drupalPost('comment/reply/' . $this->node->id(), $edit_comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $this->node->id() . '/comment', $edit_comment, t('Save')); $this->drupalLogout(); $this->setRolePermissions(DRUPAL_ANONYMOUS_RID); @@ -156,6 +164,7 @@ function testSearchResultsCommentAccess() { $this->setRolePermissions($this->admin_role); $this->assertCommentAccess(FALSE, 'Admin user has search permission but no access comments permission, comments should not be indexed'); + $this->drupalGet('node/' . $this->node->id()); $this->setRolePermissions($this->admin_role, TRUE); $this->assertCommentAccess(TRUE, 'Admin user has search permission and access comments permission, comments should be indexed'); diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php index c7055f3..48d6255 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php @@ -43,6 +43,8 @@ public function setUp() { public function testRankings() { // Login with sufficient privileges. $this->drupalLogin($this->drupalCreateUser(array('post comments', 'skip comment approval', 'create page content'))); + // Add a comment field. + $this->container->get('comment.manager')->addDefaultField('node', 'page'); // Build a list of the rankings to test. $node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views'); @@ -51,6 +53,9 @@ public function testRankings() { foreach ($node_ranks as $node_rank) { $settings = array( 'type' => 'page', + 'comment' => array(array( + 'status' => COMMENT_HIDDEN, + )), 'title' => 'Drupal rocks', 'body' => array(array('value' => "Drupal's search rocks")), ); @@ -68,7 +73,7 @@ public function testRankings() { $settings['created'] = REQUEST_TIME + 3600; break; case 'comments': - $settings['comment'] = 2; + $settings['comment'][0]['status'] = COMMENT_OPEN; break; } } @@ -87,7 +92,7 @@ public function testRankings() { $edit = array(); $edit['subject'] = 'my comment title'; $edit['comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]'] = 'some random comment'; - $this->drupalGet('comment/reply/' . $nodes['comments'][1]->id()); + $this->drupalGet('comment/reply/node/' . $nodes['comments'][1]->id() . '/comment'); $this->drupalPost(NULL, $edit, t('Preview')); $this->drupalPost(NULL, $edit, t('Save')); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 5e32613..051b512 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -233,7 +233,7 @@ function drupalGetNodeByTitle($title, $reset = FALSE) { * ); * @endcode * - title: Random string. - * - comment: COMMENT_NODE_OPEN. + * - comment: COMMENT_OPEN. * - changed: REQUEST_TIME. * - promote: NODE_NOT_PROMOTED. * - log: Empty string. @@ -266,7 +266,7 @@ protected function drupalCreateNode(array $settings = array()) { // Add in comment settings for nodes. if (module_exists('comment')) { $settings += array( - 'comment' => COMMENT_NODE_OPEN, + 'comment' => array(array('status' => COMMENT_OPEN)), ); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php index f9746d2..4e91a84 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php @@ -19,7 +19,7 @@ class EntityBCDecoratorTest extends EntityUnitTestBase { * * @var array */ - public static $modules = array('filter', 'text', 'node', 'comment'); + public static $modules = array('filter', 'text', 'node', 'entity', 'comment'); public static function getInfo() { return array( @@ -33,7 +33,7 @@ public function setUp() { parent::setUp(); $this->installSchema('user', array('users_roles', 'users_data')); $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_access')); - $this->installSchema('comment', array('comment', 'node_comment_statistics')); + $this->installSchema('comment', array('comment', 'comment_entity_statistics')); } /** @@ -44,13 +44,16 @@ public function setUp() { public function testBCDecorator() { // Test using comment subject via the BC decorator. $this->createUser(); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); $node = entity_create('node', array( 'type' => 'page', 'uid' => 1, )); $node->save(); $comment = entity_create('comment', array( - 'nid' => $node->id(), + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'subject' => 'old-value', )); $comment->save(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php index 2c452d4..fc6ccdb 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php @@ -45,7 +45,7 @@ public function setUp() { parent::setUp(); $this->installSchema('user', array('users_roles', 'users_data')); $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_access')); - $this->installSchema('comment', array('comment', 'node_comment_statistics')); + $this->installSchema('comment', array('comment', 'comment_entity_statistics')); } /** @@ -132,13 +132,14 @@ public function testBlockHooks() { */ public function testCommentHooks() { $account = $this->createUser(); + $this->enableModules(array('entity', 'filter')); + $this->container->get('comment.manager')->addDefaultField('node', 'article', 'comment', COMMENT_OPEN); $node = entity_create('node', array( 'uid' => $account->id(), 'type' => 'article', 'title' => 'Test node', 'status' => 1, - 'comment' => 2, 'promote' => 0, 'sticky' => 0, 'langcode' => Language::LANGCODE_NOT_SPECIFIED, @@ -150,10 +151,11 @@ public function testCommentHooks() { $_SESSION['entity_crud_hook_test'] = array(); $comment = entity_create('comment', array( - 'node_type' => 'node_type_' . $node->bundle(), 'cid' => NULL, 'pid' => 0, - 'nid' => $nid, + 'entity_id' => $nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => $account->id(), 'subject' => 'Test comment', 'created' => REQUEST_TIME, @@ -281,7 +283,6 @@ public function testNodeHooks() { 'type' => 'article', 'title' => 'Test node', 'status' => 1, - 'comment' => 2, 'promote' => 0, 'sticky' => 0, 'langcode' => Language::LANGCODE_NOT_SPECIFIED, diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php index 5be7001..df5bfe7 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php @@ -141,8 +141,6 @@ function testBreadCrumbs() { "admin/structure/types/manage/$type/display" => t('Manage display'), ); $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser); - $this->assertBreadcrumb("admin/structure/types/manage/$type/comment/fields", $trail); - $this->assertBreadcrumb("admin/structure/types/manage/$type/comment/display", $trail); $this->assertBreadcrumb("admin/structure/types/manage/$type/delete", $trail); $trail += array( "admin/structure/types/manage/$type/fields" => t('Manage fields'), diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php index 59dc6a2..060d6d3 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php @@ -175,25 +175,27 @@ function testUninstallDependents() { $this->drupalPost(NULL, array(), t('Continue')); $this->assertModules(array('forum'), TRUE); - // Disable forum and comment. Both should now be installed but disabled. + // Disable forum module. It should now be installed but disabled. $edit = array('modules[Core][forum][enable]' => FALSE); $this->drupalPost('admin/modules', $edit, t('Save configuration')); $this->assertModules(array('forum'), FALSE); - $edit = array('modules[Core][comment][enable]' => FALSE); - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertModules(array('comment'), FALSE); - // Check that the taxonomy module cannot be uninstalled. - $this->drupalGet('admin/modules/uninstall'); - $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="uninstall[comment]"]'); - $this->assert(count($checkbox) == 1, 'Checkbox for uninstalling the comment module is disabled.'); + // Check that the comment module cannot be uninstalled. + $this->drupalGet('admin/modules'); + $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Core][comment][enable]"]'); + $this->assert(count($checkbox) == 1, 'Checkbox for disabling the comment module is disabled.'); - // Uninstall the forum module, and check that taxonomy now can also be - // uninstalled. + // Uninstall the forum module. $edit = array('uninstall[forum]' => 'forum'); $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); $this->drupalPost(NULL, NULL, t('Uninstall')); $this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.'); + + // Disable the comment module. + $edit = array('modules[Core][comment][enable]' => FALSE); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('comment'), FALSE); + // Uninstall comment module. $edit = array('uninstall[comment]' => 'comment'); $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); $this->drupalPost(NULL, NULL, t('Uninstall')); diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php index d2d65bc..bb1b8e6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php @@ -199,6 +199,10 @@ function testDependencyResolution() { $info = install_profile_info($profile); $this->assertTrue(in_array('comment', $info['dependencies']), 'Comment module is listed as a dependency of the installation profile.'); $this->assertTrue(module_exists('comment'), 'Comment module is enabled.'); + // Delete comment field to allow comment module disable. + entity_load('field_entity', 'node.comment_forum')->delete(); + // Purge comment field storage data. + field_purge_batch(10); module_disable(array('comment')); $this->assertFalse(module_exists('comment'), 'Comment module was disabled.'); $disabled_modules = \Drupal::state()->get('module_test.disable_order') ?: array(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php index bf62114..e05a514 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php @@ -97,6 +97,8 @@ function setUp() { )); $this->term->save(); + // Add a comment field. + $this->container->get('comment.manager')->addDefaultField('node', 'article', 'comment', COMMENT_OPEN); // Create a test node tagged with the test term. $this->node = $this->drupalCreateNode(array( 'title' => $this->xss_label, @@ -107,8 +109,9 @@ function setUp() { // Create a test comment on the test node. $this->comment = entity_create('comment', array( - 'nid' => $this->node->id(), - 'node_type' => $this->node->getType(), + 'entity_id' => $this->node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'status' => COMMENT_PUBLISHED, 'subject' => $this->xss_label, 'comment_body' => array($this->randomName()), diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/CommentUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/CommentUpgradePathTest.php new file mode 100644 index 0000000..92aa9ce --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/CommentUpgradePathTest.php @@ -0,0 +1,115 @@ + 'Comment upgrade test', + 'description' => 'Upgrade tests with comment data.', + 'group' => 'Upgrade path', + ); + } + + public function setUp() { + // Path to the database dump files. + $this->databaseDumpFiles = array( + drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.filled.standard_all.database.php.gz', + // Language dataset includes nodes with comments so can be reused. + drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.language.database.php', + ); + parent::setUp(); + } + + /** + * Tests a successful upgrade. + */ + public function testCommentUpgrade() { + $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); + + // Check that comments display on the node. + $this->drupalGet('node/50'); + $this->assertText('Node title 50', 'Node 50 displayed after update.'); + $this->assertText('First test comment', 'Comment 1 displayed after update.'); + $this->assertText('Reply to first test comment', 'Comment 2 displayed after update.'); + + $expected_settings = array( + 'article' => array ( + 'default_mode' => 1, + 'per_page' => 50, + 'form_location' => 1, + 'anonymous' => 0, + 'subject' => 1, + 'preview' => 1, + ), + 'blog' => array ( + 'default_mode' => 1, + 'per_page' => 50, + 'form_location' => 1, + 'anonymous' => 0, + 'subject' => 1, + 'preview' => 1, + ), + 'book' => array ( + 'default_mode' => 1, + 'per_page' => 50, + 'form_location' => 1, + 'anonymous' => 0, + 'subject' => 1, + 'preview' => 1, + ), + 'forum' => array ( + 'default_mode' => 1, + 'per_page' => 50, + 'form_location' => 1, + 'anonymous' => 0, + 'subject' => 1, + 'preview' => 1, + ), + 'page' => array ( + 'default_mode' => 1, + 'per_page' => 50, + 'form_location' => 1, + 'anonymous' => 0, + 'subject' => 1, + 'preview' => 1, + ), + // Poll module exists in Drupal 7 and hence will be present during an + // upgrade. + 'poll' => array ( + 'default_mode' => 1, + 'per_page' => 50, + 'form_location' => 1, + 'anonymous' => 0, + 'subject' => 1, + 'preview' => 1, + ), + ); + // Check one instance exists for each node type. + $types = node_type_get_types(); + $types = array_keys($types); + sort($types); + $this->assertIdentical(array_keys($expected_settings), $types, 'All node types are upgraded'); + foreach ($types as $type) { + $instance = field_info_instance('node', 'comment_' . $type, $type); + $this->assertTrue($instance, format_string('Comment field found for the %type node type', array( + '%type' => $type + ))); + $this->assertIdentical($instance->settings, $expected_settings[$type], format_string('Comment field settings migrated for the %type node type', array( + '%type' => $type, + ))); + } + } + +} diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php index 4598aae..57828bf 100644 --- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php +++ b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php @@ -32,6 +32,7 @@ public static function getInfo() { public function setUp() { parent::setUp(); node_access_rebuild(); + $this->container->get('comment.manager')->addDefaultField('node', 'page', 'comment', COMMENT_OPEN); \Drupal::state()->set('node_access_test.private', TRUE); } diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php index b11df97..90d029a 100644 --- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php +++ b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php @@ -52,9 +52,7 @@ function setUp() { $permissions = array('access comments', 'create page content', 'post comments', 'skip comment approval'); $this->user = $this->drupalCreateUser($permissions); $this->other_user = $this->drupalCreateUser($permissions); - - // Make node preview optional. - variable_set('comment_preview_page', 0); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); } /** @@ -113,7 +111,7 @@ function testTrackerUser() { 'subject' => $this->randomName(), 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), ); - $this->drupalPost('comment/reply/' . $other_published_my_comment->id(), $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $other_published_my_comment->id() . '/comment', $comment, t('Save')); $this->drupalGet('user/' . $this->user->id() . '/track'); $this->assertNoText($unpublished->label(), "Unpublished nodes do not show up in the users's tracker listing."); @@ -164,7 +162,6 @@ function testTrackerNewComments() { $this->drupalLogin($this->user); $node = $this->drupalCreateNode(array( - 'comment' => 2, 'title' => $this->randomName(8), )); @@ -174,7 +171,7 @@ function testTrackerNewComments() { 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), ); // The new comment is automatically viewed by the current user. - $this->drupalPost('comment/reply/' . $node->id(), $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->id() . '/comment', $comment, t('Save')); $this->drupalLogin($this->other_user); $this->drupalGet('tracker'); @@ -189,7 +186,7 @@ function testTrackerNewComments() { // If the comment is posted in the same second as the last one then Drupal // can't tell the difference, so we wait one second here. sleep(1); - $this->drupalPost('comment/reply/' . $node->id(), $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->id() . '/comment', $comment, t('Save')); $this->drupalLogin($this->user); $this->drupalGet('tracker'); @@ -207,7 +204,6 @@ function testTrackerCronIndexing() { $nodes = array(); for ($i = 1; $i <= 3; $i++) { $edits[$i] = array( - 'comment' => 2, 'title' => $this->randomName(), ); $nodes[$i] = $this->drupalCreateNode($edits[$i]); @@ -219,7 +215,7 @@ function testTrackerCronIndexing() { 'subject' => $this->randomName(), 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), ); - $this->drupalPost('comment/reply/' . $nodes[3]->id(), $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $nodes[3]->id() . '/comment', $comment, t('Save')); // Start indexing backwards from node 3. \Drupal::state()->set('tracker.index_nid', 3); @@ -262,7 +258,6 @@ function testTrackerAdminUnpublish() { $this->drupalLogin($admin_user); $node = $this->drupalCreateNode(array( - 'comment' => 2, 'title' => $this->randomName(), )); diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/Views/TrackerTestBase.php b/core/modules/tracker/lib/Drupal/tracker/Tests/Views/TrackerTestBase.php index 1ae4ba6..00fb775 100644 --- a/core/modules/tracker/lib/Drupal/tracker/Tests/Views/TrackerTestBase.php +++ b/core/modules/tracker/lib/Drupal/tracker/Tests/Views/TrackerTestBase.php @@ -29,6 +29,8 @@ protected function setUp() { ViewTestData::importTestViews(get_class($this), array('tracker_test_views')); $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + // Add a comment field. + $this->container->get('comment.manager')->addDefaultField('node', 'page'); $permissions = array('access comments', 'create page content', 'post comments', 'skip comment approval'); $account = $this->drupalCreateUser($permissions); @@ -42,7 +44,9 @@ protected function setUp() { )); $this->comment = entity_create('comment', array( - 'nid' => $this->node->id(), + 'entity_id' => $this->node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', 'subject' => $this->randomName(), 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), )); diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module index 80b0d5a..65ba3a9 100644 --- a/core/modules/tracker/tracker.module +++ b/core/modules/tracker/tracker.module @@ -127,10 +127,12 @@ function tracker_cron() { // Force PostgreSQL to do an implicit cast by adding 0. $query->addExpression('0 + :changed', 'changed', array(':changed' => $changed)); $query->addField('c', 'status', 'published'); + $query->addField('c', 'entity_id', 'nid'); $query ->distinct() - ->fields('c', array('uid', 'nid')) - ->condition('c.nid', $row->nid) + ->fields('c', array('uid')) + ->condition('c.entity_id', $row->nid) + ->condition('c.entity_type', 'node') ->condition('c.uid', $row->uid, '<>') ->condition('c.status', COMMENT_PUBLISHED); @@ -233,8 +235,8 @@ function tracker_node_predelete(EntityInterface $node, $arg = 0) { function tracker_comment_update($comment) { // $comment->save() calls hook_comment_publish() for all published comments // so we need to handle all other values here. - if ($comment->status->value != COMMENT_PUBLISHED) { - _tracker_remove($comment->nid->target_id, $comment->uid->target_id, $comment->changed->value); + if ($comment->status->value != COMMENT_PUBLISHED && $comment->entity_type->value == 'node') { + _tracker_remove($comment->entity_id->target_id, $comment->uid->target_id, $comment->changed->value); } } @@ -245,21 +247,27 @@ function tracker_comment_update($comment) { * $comment->save() calls hook_comment_publish() for all published comments. */ function tracker_comment_publish($comment) { - _tracker_add($comment->nid->target_id, $comment->uid->target_id, $comment->changed->value); + if ($comment->entity_type->value == 'node') { + _tracker_add($comment->entity_id->target_id, $comment->uid->target_id, $comment->changed->value); + } } /** * Implements hook_comment_unpublish(). */ function tracker_comment_unpublish($comment) { - _tracker_remove($comment->nid->target_id, $comment->uid->target_id, $comment->changed->value); + if ($comment->entity_type->value == 'node') { + _tracker_remove($comment->entity_id->target_id, $comment->uid->target_id, $comment->changed->value); + } } /** * Implements hook_comment_delete(). */ function tracker_comment_delete($comment) { - _tracker_remove($comment->nid->target_id, $comment->uid->target_id, $comment->changed->value); + if ($comment->entity_type->value == 'node') { + _tracker_remove($comment->entity_id->target_id, $comment->uid->target_id, $comment->changed->value); + } } /** @@ -317,7 +325,7 @@ function _tracker_calculate_changed($nid) { // @todo This should be actually filtering on the desired language and just // fall back to the default language. $changed = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND default_langcode = 1 ORDER BY changed DESC', array(':nid' => $nid), array('target' => 'slave'))->fetchField(); - $latest_comment = db_query_range('SELECT cid, changed FROM {comment} WHERE nid = :nid AND status = :status ORDER BY changed DESC', 0, 1, array( + $latest_comment = db_query_range("SELECT cid, changed FROM {comment} WHERE entity_type = 'node' AND entity_id = :nid AND status = :status ORDER BY changed DESC", 0, 1, array( ':nid' => $nid, ':status' => COMMENT_PUBLISHED, ), array('target' => 'slave'))->fetchObject(); @@ -354,7 +362,7 @@ function _tracker_remove($nid, $uid = NULL, $changed = NULL) { // Comments are a second reason to keep the user's subscription. if (!$keep_subscription) { // Check if the user has commented at least once on the given nid. - $keep_subscription = db_query_range('SELECT COUNT(*) FROM {comment} WHERE nid = :nid AND uid = :uid AND status = :status', 0, 1, array( + $keep_subscription = db_query_range("SELECT COUNT(*) FROM {comment} WHERE entity_type = 'node' AND entity_id = :nid AND uid = :uid AND status = :status", 0, 1, array( ':nid' => $nid, ':uid' => $uid, ':status' => COMMENT_PUBLISHED, diff --git a/core/modules/tracker/tracker.pages.inc b/core/modules/tracker/tracker.pages.inc index 8e187ef..e6d2e5b 100644 --- a/core/modules/tracker/tracker.pages.inc +++ b/core/modules/tracker/tracker.pages.inc @@ -52,10 +52,9 @@ function tracker_page($account = NULL, $set_title = FALSE) { if (!empty($tracker_data)) { $nids = array_keys($tracker_data); $nodes = node_load_multiple($nids); - // Now, get the data and put into the placeholder array. // @todo This should be actually filtering on the desired language and just // fall back to the default language. - $result = db_query('SELECT n.nid, l.comment_count FROM {node_field_data} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.nid IN (:nids) AND n.default_langcode = 1 ORDER BY n.changed DESC', array(':nids' => $nids), array('target' => 'slave'))->fetchAllKeyed(); + $result = db_query("SELECT n.nid, SUM(l.comment_count) AS comment_count FROM {node_field_data} n INNER JOIN {comment_entity_statistics} l ON n.nid = l.entity_id AND l.entity_type = 'node' INNER JOIN {users} u ON n.uid = u.uid WHERE n.nid IN (:nids) AND n.default_langcode = 1 GROUP BY n.nid ORDER BY n.changed DESC", array(':nids' => array_keys($nodes)), array('target' => 'slave'))->fetchAllKeyed(); foreach ($result as $nid => $comment_count) { $nodes[$nid]->last_activity = $tracker_data[$nid]->changed; $nodes[$nid]->comment_count = $comment_count; @@ -68,7 +67,7 @@ function tracker_page($account = NULL, $set_title = FALSE) { if ($node->comment_count) { $comments = $node->comment_count; - if ($new = comment_num_new($node->id())) { + if ($new = comment_num_new($node->id(), 'node')) { $comments .= '
'; $comments .= l(format_plural($new, '1 new', '@count new'), 'node/' . $node->id(), array('fragment' => 'new')); } diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php index af8d8c9..441e483 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php @@ -278,6 +278,7 @@ function testUserDelete() { \Drupal::config('user.settings')->set('cancel_method', 'user_cancel_delete')->save(); module_enable(array('comment')); $this->resetAll(); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); // Create a user. $account = $this->drupalCreateUser(array('cancel account', 'post comments', 'skip comment approval')); @@ -294,7 +295,7 @@ function testUserDelete() { $edit['subject'] = $this->randomName(8); $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $this->drupalPost('comment/reply/' . $node->id(), $edit, t('Preview')); + $this->drupalPost('comment/reply/node/' . $node->id() . '/comment', $edit, t('Preview')); $this->drupalPost(NULL, array(), t('Save')); $this->assertText(t('Your comment has been posted.')); $comments = entity_load_multiple_by_properties('comment', array('subject' => $edit['subject'])); diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php index 4b79760..0df4cf2 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php @@ -43,6 +43,7 @@ function setUp() { 'skip comment approval', )); $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + $this->container->get('comment.manager')->addDefaultField('node', 'article'); // @see standard.install module_load_install('user'); @@ -113,7 +114,7 @@ function testPictureOnNodeComment() { $edit = array( 'comment_body[' . Language::LANGCODE_NOT_SPECIFIED . '][0][value]' => $this->randomString(), ); - $this->drupalPost('comment/reply/' . $node->id(), $edit, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->id() . '/comment', $edit, t('Save')); $this->assertRaw(file_uri_target($file->getFileUri()), 'User picture found on comment.'); // Disable user pictures on comments and nodes. diff --git a/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php index 5a8cd0e..e7b5312 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php @@ -38,6 +38,8 @@ function setUp() { // Create Basic page node type. $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + // Add a comment field with commenting enabled by default. + $this->container->get('comment.manager')->addDefaultField('node', 'page'); // Prefetch and create text formats. $this->filtered_html_format = entity_create('filter_format', array( @@ -82,8 +84,7 @@ function setUp() { * upon display. */ function testUserSignature() { - // Create a new node with comments on. - $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN)); + $node = $this->drupalCreateNode(); // Verify that user signature field is not displayed on registration form. $this->drupalGet('user/register'); @@ -105,7 +106,7 @@ function testUserSignature() { $edit = array(); $edit['subject'] = $this->randomName(8); $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $this->drupalPost('comment/reply/' . $node->id(), $edit, t('Preview')); + $this->drupalPost('comment/reply/node/' . $node->id() .'/comment', $edit, t('Preview')); $this->drupalPost(NULL, array(), t('Save')); // Get the comment ID. (This technique is the same one used in the Comment diff --git a/core/modules/views/config/views.view.comments_recent.yml b/core/modules/views/config/views.view.comments_recent.yml index 9b9db7d..6b0898b 100644 --- a/core/modules/views/config/views.view.comments_recent.yml +++ b/core/modules/views/config/views.view.comments_recent.yml @@ -42,10 +42,11 @@ display: items_per_page: '5' offset: '0' relationships: - nid: - id: nid + node: + field: node + id: node + required: '1' table: comment - field: nid plugin_id: standard relationship: none group_type: group @@ -178,7 +179,7 @@ display: id: status_extra table: node_field_data field: status_extra - relationship: nid + relationship: node group: '0' plugin_id: node_status provider: node @@ -224,7 +225,7 @@ display: id: title table: node_field_data field: title - relationship: nid + relationship: node label: 'Reply to' link_to_node: '1' plugin_id: node diff --git a/core/modules/views/config/views.view.tracker.yml b/core/modules/views/config/views.view.tracker.yml index 178421f..5926b45 100644 --- a/core/modules/views/config/views.view.tracker.yml +++ b/core/modules/views/config/views.view.tracker.yml @@ -224,7 +224,7 @@ display: provider: user comment_count: id: comment_count - table: node_comment_statistics + table: comment_entity_statistics field: comment_count label: Replies plugin_id: numeric @@ -283,7 +283,7 @@ display: provider: views last_comment_timestamp: id: last_comment_timestamp - table: node_comment_statistics + table: comment_entity_statistics field: last_comment_timestamp label: 'Last Post' plugin_id: comment_last_timestamp @@ -449,7 +449,7 @@ display: sorts: last_comment_timestamp: id: last_comment_timestamp - table: node_comment_statistics + table: comment_entity_statistics field: last_comment_timestamp plugin_id: date relationship: none diff --git a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php index 82fa1eb..1e61836 100644 --- a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php @@ -83,6 +83,10 @@ protected function setUp() { // Create a time in the past for the archive. $time = REQUEST_TIME - 3600; + $this->container->get('comment.manager')->addDefaultField('node', 'page'); + + $this->container->get('views.views_data')->clear(); + for ($i = 0; $i <= 10; $i++) { $user = $this->drupalCreateUser(); $term = $this->createTerm($this->vocabulary); @@ -102,8 +106,9 @@ protected function setUp() { $comment = array( 'uid' => $user->id(), - 'nid' => $node->id(), - 'node_type' => 'node_type_' . $node->bundle(), + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment' ); entity_create('comment', $comment)->save(); } diff --git a/core/modules/views/lib/Drupal/views/Tests/Entity/FieldEntityTest.php b/core/modules/views/lib/Drupal/views/Tests/Entity/FieldEntityTest.php index 0950180..310326c 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Entity/FieldEntityTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Entity/FieldEntityTest.php @@ -45,9 +45,18 @@ public function testGetEntity() { $account = entity_create('user', array('name' => $this->randomName(), 'bundle' => 'user')); $account->save(); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); + // Force a flush of the in-memory storage. + $this->container->get('views.views_data')->clear(); + $node = entity_create('node', array('uid' => $account->id(), 'type' => 'page')); $node->save(); - $comment = entity_create('comment', array('uid' => $account->id(), 'nid' => $node->id(), 'node_type' => 'comment_node_page')); + $comment = entity_create('comment', array( + 'uid' => $account->id(), + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment' + )); $comment->save(); $view = views_get_view('test_field_get_entity'); diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerAllTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerAllTest.php index 7ad88da..05d9887 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerAllTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerAllTest.php @@ -54,6 +54,8 @@ public static function getInfo() { * Tests most of the handlers. */ public function testHandlers() { + $this->container->get('comment.manager')->addDefaultField('node', 'article'); + $object_types = array_keys(ViewExecutable::viewsHandlerTypes()); foreach ($this->container->get('views.views_data')->get() as $base_table => $info) { if (!isset($info['table']['base'])) { diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php index 03a2b7f..fdf35f0 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php @@ -41,7 +41,7 @@ public static function getInfo() { protected function setUp() { parent::setUp(); - + $this->container->get('comment.manager')->addDefaultField('node', 'page'); $this->enableViewsTestModule(); } @@ -284,7 +284,7 @@ public function testSetRelationship() { // Setup a broken relationship. $view->addItem('default', 'relationship', $this->randomName(), $this->randomName(), array(), 'broken_relationship'); // Setup a valid relationship. - $view->addItem('default', 'relationship', 'comment', 'nid', array('relationship' => 'cid'), 'valid_relationship'); + $view->addItem('default', 'relationship', 'comment', 'node', array('relationship' => 'cid'), 'valid_relationship'); $view->initHandlers(); $field = $view->field['title']; diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php index 54ac900..06b8ac1 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php @@ -28,7 +28,7 @@ */ class ViewExecutableTest extends ViewUnitTestBase { - public static $modules = array('system', 'node', 'comment', 'user', 'filter'); + public static $modules = array('system', 'node', 'comment', 'user', 'filter', 'entity', 'field', 'field_sql_storage', 'text'); /** * Views used by this test. @@ -85,7 +85,9 @@ public static function getInfo() { protected function setUpFixtures() { $this->installSchema('user', array('users')); $this->installSchema('node', array('node', 'node_field_data')); - $this->installSchema('comment', array('comment', 'node_comment_statistics')); + $this->installSchema('comment', array('comment', 'comment_entity_statistics')); + $this->installConfig(array('field')); + $this->container->get('comment.manager')->addDefaultField('node', 'page'); parent::setUpFixtures(); $this->installConfig(array('filter')); diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml index a8fec7a..060b7e5 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml @@ -146,9 +146,9 @@ display: query: type: views_query relationships: - cid: - field: cid - id: cid + comment_cid: + field: comment_cid + id: comment_cid table: node plugin_id: standard provider: views @@ -156,27 +156,27 @@ display: field: pid id: pid table: comment - relationship: cid + relationship: comment_cid plugin_id: standard provider: views uid: field: uid id: uid table: comment - relationship: cid + relationship: comment_cid plugin_id: standard provider: views sorts: last_comment_name: field: last_comment_name id: last_comment_name - table: node_comment_statistics - plugin_id: comment_ncs_last_comment_name + table: comment_entity_statistics + plugin_id: comment_ces_last_comment_name provider: comment last_comment_timestamp: field: last_comment_timestamp id: last_comment_timestamp - table: node_comment_statistics + table: comment_entity_statistics plugin_id: date provider: views style: diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity.yml index 50c033c..cac8aad 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity.yml @@ -22,7 +22,7 @@ display: field: nid id: nid table: node - relationship: nid + relationship: node plugin_id: node provider: node uid: @@ -41,9 +41,9 @@ display: query: type: views_query relationships: - nid: - field: nid - id: nid + node: + field: node + id: node required: '1' table: comment plugin_id: standard @@ -54,7 +54,7 @@ display: group_type: group id: uid label: author - relationship: nid + relationship: node required: '0' table: node_field_data plugin_id: standard diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml index 71d1e17..4d9f7d8 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml @@ -13,17 +13,17 @@ display: plugin_id: node provider: node relationships: - cid: - id: cid + comment_cid: + id: comment_cid table: node - field: cid + field: comment_cid plugin_id: standard provider: views nid: id: nid table: comment - field: nid - relationship: cid + field: node + relationship: comment_cid plugin_id: standard provider: views display_plugin: default diff --git a/core/profiles/standard/config/rdf.mapping.comment.comment_node_article.yml b/core/profiles/standard/config/rdf.mapping.comment.comment_node_article.yml deleted file mode 100644 index 342da4f..0000000 --- a/core/profiles/standard/config/rdf.mapping.comment.comment_node_article.yml +++ /dev/null @@ -1,26 +0,0 @@ -id: comment.comment_node_article -targetEntityType: comment -bundle: comment_node_article -types: - - 'schema:Comment' -fieldMappings: - subject: - properties: - - 'schema:name' - created: - properties: - - 'schema:dateCreated' - datatype_callback: - callable: 'date_iso8601' - changed: - properties: - - 'schema:dateModified' - datatype_callback: - callable: 'date_iso8601' - comment_body: - properties: - - 'schema:text' - uid: - properties: - - 'schema:author' - mapping_type: 'rel' diff --git a/core/profiles/standard/config/rdf.mapping.comment.comment_node_page.yml b/core/profiles/standard/config/rdf.mapping.comment.node__comment.yml similarity index 89% rename from core/profiles/standard/config/rdf.mapping.comment.comment_node_page.yml rename to core/profiles/standard/config/rdf.mapping.comment.node__comment.yml index 63e2a0a..42d8f34 100644 --- a/core/profiles/standard/config/rdf.mapping.comment.comment_node_page.yml +++ b/core/profiles/standard/config/rdf.mapping.comment.node__comment.yml @@ -1,6 +1,6 @@ -id: comment.comment_node_page +id: comment.node__comment targetEntityType: comment -bundle: comment_node_page +bundle: node__comment types: - 'schema:Comment' fieldMappings: diff --git a/core/profiles/standard/config/rdf.mapping.node.article.yml b/core/profiles/standard/config/rdf.mapping.node.article.yml index 5c3dc2b..469f7b0 100644 --- a/core/profiles/standard/config/rdf.mapping.node.article.yml +++ b/core/profiles/standard/config/rdf.mapping.node.article.yml @@ -24,6 +24,10 @@ fieldMappings: properties: - 'schema:author' mapping_type: 'rel' + comment: + properties: + - 'schema:comment' + mapping_type: 'rel' comment_count: properties: - 'schema:interactionCount' diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index af30e20..10aa27f 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -24,8 +24,8 @@ function standard_install() { // Set front page to "node". Drupal::config('system.site')->set('page.front', 'node')->save(); - // Default "Basic page" to have comments disabled. - variable_set('comment_page', COMMENT_NODE_HIDDEN); + // Add comment field to article node type. + Drupal::service('comment.manager')->addDefaultField('node', 'article', 'comment', COMMENT_OPEN); // Allow visitor account creation with administrative approval. $user_settings = Drupal::config('user.settings'); diff --git a/core/themes/bartik/templates/comment.html.twig b/core/themes/bartik/templates/comment.html.twig index 2671268..bcf1db5 100644 --- a/core/themes/bartik/templates/comment.html.twig +++ b/core/themes/bartik/templates/comment.html.twig @@ -28,7 +28,8 @@ * through CSS. The default values can be one or more of the following: * - comment: The current template type; e.g., 'theming hook'. * - by-anonymous: Comment by an unregistered user. - * - by-node-author: Comment by the author of the parent node. + * - by-{entity-type}-author: Comment by the author of the parent entity, + * eg. by-node-author. * - preview: When previewing a new or edited comment. * The following applies only to viewers who are registered users: * - unpublished: An unpublished comment visible only to administrators. @@ -56,7 +57,7 @@ * * These two variables are provided for context: * - comment: Full comment object. - * - node: Node entity the comments are attached to. + * - entity: Entity the comments are attached to. * * @see template_preprocess_comment() * diff --git a/core/themes/bartik/templates/node.html.twig b/core/themes/bartik/templates/node.html.twig index d45cc55..6694211 100644 --- a/core/themes/bartik/templates/node.html.twig +++ b/core/themes/bartik/templates/node.html.twig @@ -15,13 +15,6 @@ * - sticky: Whether the node is 'sticky'. Sticky nodes are ordered above * other non-sticky nodes in teaser listings * - published: Whether the node is published. - * - comment: A value representing the comment status of the current node. May - * be one of the following: - * - 0: The comment form and any existing comments are hidden. - * - 1: Comments are closed. No new comments may be posted, but existing - * comments are displayed. - * - 2: Comments are open on this node. - * - comment_count: Number of comments attached to the node. * - label: The title of the node. * - content: All node items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use @@ -98,8 +91,7 @@
- {# We hide the comments and links now so that we can render them later. #} - {% hide(content.comments) %} + {# We hide links now so that we can render them later. #} {% hide(content.links) %} {{ content }}
@@ -110,6 +102,4 @@ {% endif %} - {{ content.comments }} -