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.admin.inc b/core/modules/comment/comment.admin.inc index 26e9c8b..0682d47 100644 --- a/core/modules/comment/comment.admin.inc +++ b/core/modules/comment/comment.admin.inc @@ -9,6 +9,58 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** + * Page callback: Displays the comment bundle admin overview page. + */ +function comment_overview_bundles() { + $header = array( + t('Field name'), + t('Used in'), + array( + 'data' => t('Operations'), + 'colspan' => (module_exists('field_ui') ? '4' : '2') + ) + ); + $rows = array(); + + foreach (comment_get_comment_fields() as $field_name => $field_info) { + foreach ($field_info['bundles'] as $entity_type => $field_bundles) { + $bundles = array_intersect_key(field_info_bundles($entity_type), array_flip($field_bundles)); + + foreach ($bundles as $bundle => $bundle_info) { + if (!isset($rows[$field_name])) { + $rows[$field_name]['class'] = $field_info['locked'] ? array('menu-disabled') : array(''); + $rows[$field_name]['data']['label'] = $field_info['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + } + + if (module_exists('field_ui') && $path = _field_ui_bundle_admin_path($entity_type, $bundle)) { + $rows[$field_name]['data']['usage'][] = l($bundle_info['label'], $path . '/fields'); + } + else { + $rows[$field_name]['data']['usage'][] = $bundle_info['label']; + } + + if (module_exists('field_ui')) { + $name = strtr($field_name, '_', '-'); + $rows[$field_name]['data']['fields'] = l(t('manage fields'), 'admin/structure/comments/' . $name . '/fields'); + $rows[$field_name]['data']['display'] = l(t('manage display'), 'admin/structure/comments/' . $name . '/display'); + } + } + } + + $rows[$field_name]['data']['usage'] = implode(', ', $rows[$field_name]['data']['usage']); + } + + $build['overview'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('No comment bundles available.'), + ); + + return $build; +} + +/** * Page callback: Presents an administrative comment listing. * * @param $type @@ -82,23 +134,30 @@ 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', 'n', 'n.nid = c.nid'); - $query->addField('n', 'title', 'node_title'); - $query->addTag('node_access'); + if (module_exists('node')) { + // Special case to ensure node access works. + $query->leftJoin('node', 'n', "n.nid = c.entity_id AND c.entity_type = 'node'"); + $query->addTag('node_access'); + } $result = $query - ->fields('c', array('cid', 'subject', 'name', 'changed')) + ->fields('c', array('cid', 'subject', 'name', 'changed', 'entity_id', 'entity_type', 'field_name')) ->condition('c.status', $status) ->limit(50) ->orderByHeader($header) ->execute(); $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) { + $entity_ids[$row->entity_type][] = $row->entity_id; $cids[] = $row->cid; - $node_titles[] = $row->node_title; + } + foreach ($entity_ids as $entity_type => $ids) { + $entities[$entity_type] = entity_load_multiple($entity_type, $ids); } $comments = comment_load_multiple($cids); @@ -107,9 +166,9 @@ function comment_admin_overview($form, &$form_state, $arg) { $destination = drupal_get_destination(); foreach ($comments as $comment) { - // Remove the first node title from the node_titles array and attach to - // the comment. - $comment->node_title = array_shift($node_titles); + // Use the first entity label. + $comment->entity_title = $entities[$comment->entity_type][$comment->entity_id]->label(); + $comment->entity_uri = $entities[$comment->entity_type][$comment->entity_id]->uri(); $comment_body = field_get_items('comment', $comment, 'comment_body'); $options[$comment->cid] = array( 'subject' => array( @@ -124,8 +183,9 @@ function comment_admin_overview($form, &$form_state, $arg) { 'posted_in' => array( 'data' => array( '#type' => 'link', - '#title' => $comment->node_title, - '#href' => 'node/' . $comment->nid, + '#title' => $comment->entity_title, + '#href' => $comment->entity_uri['path'], + '#options' => $comment->entity_uri['options'] ), ), 'changed' => format_date($comment->changed, 'short'), @@ -284,10 +344,12 @@ function comment_confirm_delete($form, &$form_state, Comment $comment) { $form_state['comment'] = $comment; // Always provide entity id in the same form key as in the entity edit form. $form['cid'] = array('#type' => 'value', '#value' => $comment->cid); + $entity = entity_load($comment->entity_type, $comment->entity_id); + $uri = $entity->uri(); return confirm_form( $form, t('Are you sure you want to delete the comment %title?', array('%title' => $comment->subject)), - 'node/' . $comment->nid, + $uri['path'], t('Any replies to this comment will be lost. This action cannot be undone.'), t('Delete'), t('Cancel'), @@ -306,5 +368,7 @@ function comment_confirm_delete_submit($form, &$form_state) { // Clear the cache so an anonymous user sees that his comment was deleted. cache_invalidate(array('content' => TRUE)); - $form_state['redirect'] = "node/$comment->nid"; + $entity = entity_load($comment->entity_type, $comment->entity_id); + $uri = $entity->uri(); + $form_state['redirect'] = $uri['path']; } diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php index 95c3e1d..7792f23 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. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** @@ -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. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** @@ -66,9 +70,9 @@ function hook_comment_load(Drupal\comment\Comment $comments) { * * @param Drupal\comment\Comment $comment * Passes in the comment the action is being performed on. - * @param $view_mode + * @param string $view_mode * View mode, e.g. 'full', 'teaser'... - * @param $langcode + * @param string $langcode * The language code used for rendering. * * @see hook_entity_view() @@ -91,7 +95,7 @@ function hook_comment_view(Drupal\comment\Comment $comment, $view_mode, $langcod * comment.tpl.php. See drupal_render() and theme() documentation respectively * for details. * - * @param $build + * @param array $build * A renderable array representing the comment. * @param Drupal\comment\Comment $comment * The comment being rendered. @@ -99,7 +103,7 @@ function hook_comment_view(Drupal\comment\Comment $comment, $view_mode, $langcod * @see comment_view() * @see hook_entity_view_alter() */ -function hook_comment_view_alter(&$build, Drupal\comment\Comment $comment) { +function hook_comment_view_alter(array &$build, Drupal\comment\Comment $comment) { // Check for the existence of a field added by another module. if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) { // Change its weight. @@ -170,5 +174,38 @@ function hook_comment_delete(Drupal\comment\Comment $comment) { } /** + * Controls access to entity comment forms. + * + * Used to control access to commenting on an entity where no + * {%entity_type}_access function exists for the given entity. + * + * Modules may implement this hook if they want to have a say in whether or not + * the logged in user has access to view an entity in order to reply to a + * comment. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to which the comment field is attached. + * + * @return mixed + * - COMMENT_ACCESS_DENY: if the operation is to be denied. + * - FALSE: to not affect this operation at all. + * + * @todo replace this with entity access controls once generic access controller + * lands. + * + * @see http://drupal.org/node/1696660 + */ +function hook_comment_access(\Drupal\Core\Entity\EntityInterface $entity) { + $type = $entity->entityType(); + + if ($type == 'comment') { + return COMMENT_ACCESS_DENY; + } + + // Returning nothing from this function would have the same effect. + return FALSE; +} + +/** * @} End of "addtogroup hooks". */ diff --git a/core/modules/comment/comment.field.inc b/core/modules/comment/comment.field.inc new file mode 100644 index 0000000..2b5febf --- /dev/null +++ b/core/modules/comment/comment.field.inc @@ -0,0 +1,292 @@ + array( + 'label' => t('Comments'), + 'default_widget' => 'comment_default', + 'settings' => array(), + 'instance_settings' => array( + 'comment' => array( + 'comment' => COMMENT_OPEN, + 'comment_default_mode' => COMMENT_MODE_THREADED, + 'comment_default_per_page' => 50, + 'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT, + 'comment_subject_field' => 1, + 'comment_form_location' => COMMENT_FORM_BELOW, + 'comment_preview' => DRUPAL_OPTIONAL + ) + ), + 'description' => t('This field manages configuration and presentation of comments on an entity'), + 'default_formatter' => 'comment_default' + ) + ); +} + +/** + * Implements hook_field_instance_settings_form(). + */ +function comment_field_instance_settings_form($field, $instance) { + $settings = $instance['settings']; + $form['comment'] = array( + '#type' => 'fieldset', + '#title' => t('Comment settings'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#field_name' => $field['field_name'], + '#process' => array('comment_instance_settings_translation_entity_process'), + '#attributes' => array( + 'class' => array('comment-instance-settings-form'), + ), + '#attached' => array( + 'library' => array(array('comment', 'drupal.comment')), + ), + ); + $form['comment']['comment'] = array( + '#type' => 'select', + '#title' => t('Default comment setting for new content'), + '#default_value' => empty($settings['comment']) ? COMMENT_OPEN : $settings['comment'], + '#options' => array( + COMMENT_OPEN => t('Open'), + COMMENT_CLOSED => t('Closed'), + COMMENT_ENTITY_HIDDEN => t('Hidden'), + ), + ); + $form['comment']['comment_default_mode'] = array( + '#type' => 'checkbox', + '#title' => t('Threading'), + '#default_value' => empty($settings['comment_default_mode']) ? COMMENT_MODE_THREADED : $settings['comment_default_mode'], + '#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' => empty($settings['comment_default_per_page']) ? 50 : $settings['comment_default_per_page'], + '#options' => _comment_per_page(), + ); + $form['comment']['comment_anonymous'] = array( + '#type' => 'select', + '#title' => t('Anonymous commenting'), + '#default_value' => empty($settings['comment_anonymous']) ? COMMENT_ANONYMOUS_MAYNOT_CONTACT : $settings['comment_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' => user_access('post comments', drupal_anonymous_user()), + ); + $form['comment']['comment_subject_field'] = array( + '#type' => 'checkbox', + '#title' => t('Allow comment title'), + '#default_value' => empty($settings['comment_subject_field']) ? 1 : $settings['comment_subject_field'], + ); + $form['comment']['comment_form_location'] = array( + '#type' => 'checkbox', + '#title' => t('Show reply form on the same page as comments'), + '#default_value' => empty($settings['comment_form_location']) ? COMMENT_FORM_BELOW : $settings['comment_form_location'], + ); + $form['comment']['comment_preview'] = array( + '#type' => 'radios', + '#title' => t('Preview comment'), + '#default_value' => empty($settings['comment_preview']) ? DRUPAL_OPTIONAL : $settings['comment_preview'], + '#options' => array( + DRUPAL_DISABLED => t('Disabled'), + DRUPAL_OPTIONAL => t('Optional'), + DRUPAL_REQUIRED => t('Required'), + ), + ); + return $form; +} + +/** + * 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. + */ +function comment_instance_settings_translation_entity_process($element, $form_state) { + if (module_exists('translation_entity')) { + $comment_form = $element; + $comment_form_state['translation_entity']['key'] = 'language_configuration'; + $element += translation_entity_enable_widget('comment', $element['#field_name'], $comment_form, $comment_form_state); + $element['translation_entity']['#parents'] = $element['translation_entity']['#array_parents'] = array( + 'translation_entity' + ); + } + return $element; +} + +/** + * Implements hook_field_formatter_info(). + */ +function comment_field_formatter_info() { + return array( + 'comment_default' => array( + 'label' => t('Comments'), + 'field types' => array('comment'), + ), + ); +} + +/** + * Implements hook_field_widget_info(). + * + * @todo convert this to a WidgetPlugin. + */ +function comment_field_widget_info() { + return array( + 'comment_default' => array( + 'label' => t('Comment'), + 'field types' => array('comment'), + 'settings' => array(), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + ), + ); +} + +/** + * Implements hook_field_widget_form(). + * + * @todo convert this to a WidgetPlugin. + */ +function comment_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + $entity = $element['#entity']; + $settings = $instance['settings']['comment']; + $element += array( + '#type' => 'fieldset', + '#access' => user_access('administer comments'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#group' => 'additional_settings', + '#attributes' => array( + 'class' => array('comment-node-settings-form'), + ), + '#attached' => array( + 'library' => array('comment', 'drupal.comment'), + ), + '#weight' => 30, + ); + $values = reset($items); + if (empty($values)) { + $values = array( + // Default taken from instance settings. + 'comment' => $settings['comment'], + ); + } + $entity_id = $entity->id(); + $comment_count = empty($entity->comment_statistics[$field['field_name']]->comment_count) ? 0 : $entity->comment_statistics[$field['field_name']]->comment_count; + $comment_settings = ($values['comment'] == COMMENT_ENTITY_HIDDEN && empty($comment_count)) ? COMMENT_CLOSED : $values['comment']; + $element['comment'] = array( + '#type' => 'radios', + '#title' => t('Comments'), + '#title_display' => 'invisible', + '#default_value' => $comment_settings, + '#options' => array( + COMMENT_OPEN => t('Open'), + COMMENT_CLOSED => t('Closed'), + COMMENT_ENTITY_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_ENTITY_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)) { + $element['comment'][COMMENT_ENTITY_HIDDEN]['#access'] = FALSE; + // Also adjust the description of the "closed" option. + $element['comment'][COMMENT_CLOSED]['#description'] = t('Users cannot post comments.'); + } + return array($element); +} + +/** + * Implements hook_field_formatter_view(). + */ +function comment_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + + switch ($display['type']) { + case 'comment_default': + // We only ever have one value. + $values = reset($items); + if ($values['comment'] != COMMENT_ENTITY_HIDDEN) { + $additions = FALSE; + if ($values['comment'] && empty($entity->in_preview)) { + // 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. + $comment_settings = $instance['settings']['comment']; + if (((!empty($entity->comment_statistics[$field['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['comment_default_mode']; + $comments_per_page = $comment_settings['comment_default_per_page']; + if ($cids = comment_get_thread($entity, $field['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 needed. + if ($values['comment'] == COMMENT_OPEN && $comment_settings['comment_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['field_name']); + } + } + if ($additions) { + $additions += array( + '#theme' => 'comment_wrapper__' . $entity->entityType() . '__' . $entity->bundle() . '__' . $field['field_name'], + '#entity' => $entity, + '#display_mode' => $instance['settings']['comment']['comment_default_mode'], + 'comments' => array(), + 'comment_form' => array(), + ); + } + if (!empty($additions)) { + $element[] = $additions; + } + } + } + break; + + } + + return $element; +} + +/** + * Implements hook_field_is_empty(). + */ +function comment_field_is_empty($item, $field) { + // We always want the values saved so we can rely on them. + return FALSE; +} diff --git a/core/modules/comment/comment.info b/core/modules/comment/comment.info index 6028bda..72597df 100644 --- a/core/modules/comment/comment.info +++ b/core/modules/comment/comment.info @@ -3,7 +3,6 @@ description = Allows users to comment on and discuss published content. package = Core version = VERSION core = 8.x -dependencies[] = node dependencies[] = text configure = admin/content/comment stylesheets[all][] = comment.theme.css diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index 48bb5a4..6230d70 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -1,9 +1,12 @@ $field) { + field_attach_delete_bundle('comment', $field_name); } // Remove states. @@ -36,50 +36,52 @@ 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', 'n'); - $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid'); - $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'); + $comment_fields = comment_get_comment_fields(); + $entity_info = entity_get_info(); + 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("'$field_name'", 'field_name'); + $query->addExpression('0', 'comment_count'); + $query->addExpression('NULL', 'last_comment_name'); + $query->isNull('ces.comment_count'); - db_insert('node_comment_statistics') - ->from($query) - ->execute(); -} - -/** - * Implements hook_modules_enabled(). - * - * 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(). - * - * 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() - */ -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)) { - // Ensure that the list of node types reflects newly enabled modules. - node_types_rebuild(); - - // Create comment body fields for each node type, if needed. - foreach (node_type_get_types() as $type => $info) { - _comment_body_field_create($info); + db_insert('comment_entity_statistics') + ->from($query) + ->execute(); + } + } } } + // Set default value of comment_maintain_entity_statistics. + state()->set('comment_maintain_entity_statistics', TRUE); } /** @@ -106,12 +108,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_name' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'comment', + 'length' => 255, + 'description' => 'The field_name through which this comment was added.', ), 'uid' => array( 'type' => 'int', @@ -188,9 +204,14 @@ 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', 'entity_type', 'field_name', 'status', 'created', 'cid', 'thread'), 'comment_uid' => array('uid'), - 'comment_nid_langcode' => array('nid', 'langcode'), + 'comment_entity_langcode' => array( + 'entity_id', + 'entity_type', + 'field_name', + 'langcode' + ), 'comment_created' => array('created'), ), 'primary key' => array('cid'), @@ -198,10 +219,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'), @@ -209,15 +226,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_name' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'comment', + 'length' => 255, + 'description' => 'The field_name through which this comment was added.', ), 'cid' => array( 'type' => 'int', @@ -252,17 +283,13 @@ function comment_schema() { 'description' => 'The total number of comments on this node.', ), ), - 'primary key' => array('nid'), + 'primary key' => array('entity_id', 'entity_type', 'field_name'), 'indexes' => array( 'node_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( @@ -276,6 +303,26 @@ function comment_schema() { } /** + * Implements hook_field_schema(). + */ +function comment_field_schema($field) { + $columns = array(); + if ($field['type'] == 'comment') { + $columns += array( + 'comment' => 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, + ), + ); + } + return array( + 'columns' => $columns, + ); +} + +/** * @addtogroup updates-7.x-to-8.x * @{ */ @@ -394,6 +441,278 @@ function comment_update_8004() { } /** + * Updates the comment_node_statistics and comment tables to new structure. + */ +function comment_update_8005(&$sandbox) { + // Remove the comment_node foreign key. + db_drop_index('comment', 'comment_node'); + // Remove the comment_nid_langcode index. + db_drop_index('comment', 'comment_nid_langcode'); + // Add the entity_type and field_name 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_name', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'comment', + 'length' => 255, + 'description' => 'The field_name 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.', + )); + // Add the comment_entity_langcode index. + db_add_index('comment', 'comment_entity_langcode', array( + 'entity_id', + 'entity_type', + 'field_name', + 'langcode' + )); + // Rename node_comment_statistics to comment_entity_statistics. + db_rename_table('node_comment_statistics', 'comment_entity_statistics'); + // Remove the statistics_node foreign key from entity_comment_statistics. + // Add the entity_type and field_name 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_name', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'comment', + 'length' => 255, + 'description' => 'The field_name 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.', + )); + $t = get_t(); + return $t('Updated database to reflect new comment structure'); +} + +/** + * Adds new field_api fields. + */ +function comment_update_8006(&$sandbox) { + // Loop over defined node_types. + $node_types = array_keys(_update_7000_node_get_types()); + foreach ($node_types as $node_type) { + // Add a default comment field for existing node comments. + $field = array( + 'cardinality' => '1', + // We need one per node type to match the existing bundles. + 'field_name' => 'comment_node_' . $node_type, + 'module' => 'comment', + 'settings' => array(), + 'translatable' => '0', + 'type' => 'comment', + ); + // Make sure field doesn't already exist. + if (!_update_7000_field_read_fields(array('field_name' => 'comment_node_' . $node_type))) { + // Create the field. + _update_7000_field_create_field($field); + } + // Add the comment field, setting the instance settings to match those for the + // give node_type. + $instance = array( + 'bundle' => $node_type, + 'default_value' => array( + 0 => array( + 'comment' => variable_get('comment_' . $node_type, 0), + ) + ), + 'deleted' => '0', + 'description' => '', + 'display' => array( + 'default' => array( + 'label' => 'above', + 'module' => 'comment', + 'settings' => array(), + 'type' => 'comment_default', + 'weight' => '1', + ), + 'rss' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + 'teaser' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + 'search_index' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + 'search_result' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + ), + 'entity_type' => 'node', + 'field_name' => 'comment_node_' . $node_type, + 'label' => 'Comment settings', + 'required' => 1, + 'settings' => array( + 'comment' => array( + 'comment' => variable_get('comment_' . $node_type, 0), + 'comment_default_mode' => variable_get('comment_default_mode_' . $node_type, 1), + 'comment_default_per_page' => variable_get('comment_default_per_page_' . $node_type, 50), + 'comment_anonymous' => variable_get('comment_anonymous_' . $node_type, 0), + 'comment_subject_field' => variable_get('comment_subject_field_' . $node_type, 1), + 'comment_form_location' => variable_get('comment_form_location_' . $node_type, 1), + 'comment_preview' => variable_get('comment_preview_' . $node_type, 1) + ) + ), + 'widget' => array( + 'active' => 0, + 'module' => 'comment', + 'settings' => array(), + 'type' => 'comment_default', + 'weight' => '50', + ), + ); + _update_7000_field_create_instance($field, $instance); + // Rename the comment bundle for this node type. + // Clean up old variables. + variable_del('comment_' . $node_type); + variable_del('comment_default_mode_' . $node_type); + variable_del('comment_default_per_page_' . $node_type); + variable_del('comment_anonymous_' . $node_type); + variable_del('comment_subject_field_' . $node_type); + variable_del('comment_form_location_' . $node_type); + variable_del('comment_preview_' . $node_type); + } + $t = get_t(); + return $t('Created required fields for each comment bundle'); +} + +/** + * 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(DISTINCT 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('field_data_comment_node_' . $sandbox['node_type'])-> + fields(array( + 'entity_type', + 'bundle', + 'entity_id', + 'revision_id', + 'langcode', + 'delta', + 'comment_node_' . $sandbox['node_type'] . '_comment', + )); + $revision = db_insert('field_revision_comment_node_' . $sandbox['node_type'])-> + fields(array( + 'entity_type', + 'bundle', + 'entity_id', + 'revision_id', + 'langcode', + 'delta', + 'comment_node_' . $sandbox['node_type'] . '_comment', + )); + + // Update the field name to match the node type. + db_update('comment')->fields( + array('field_name' => 'comment_node_' . $sandbox['node_type']) + )->condition('entity_id', array_keys($nodes)) + ->execute(); + foreach ($nodes as $nid => $node) { + $insert->values(array( + 'entity_type' => 'node', + 'bundle' => $sandbox['node_type'], + 'entity_id' => $nid, + 'revision_id' => $node->vid, + 'langcode' => $node->langcode, + 'delta' => 0, + 'comment_node_' . $sandbox['node_type'] . '_comment' => $node->comment, + )); + $revision->values(array( + 'entity_type' => 'node', + 'bundle' => $sandbox['node_type'], + 'entity_id' => $nid, + 'revision_id' => $node->vid, + 'langcode' => $node->langcode, + 'delta' => 0, + 'comment_node_' . $sandbox['node_type'] . '_comment' => $node->comment, + )); + $sandbox['progress']++; + $sandbox['current_nid'] = $nid; + } + $insert->execute(); + $revision->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']); + + $t = get_t(); + return $t('Migrated existing node comment settings to field api.'); +} + +/** + * 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'); + $t = get_t(); + return $t('Removed old fields.'); +} + + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index e700723..325fa5e 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -4,9 +4,10 @@ * @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\Plugin\Core\Entity\Node; @@ -16,6 +17,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; +// Load all Field module hooks for Comment. +require_once DRUPAL_ROOT . '/core/modules/comment/comment.field.inc'; + /** * Comment is awaiting approval. */ @@ -62,19 +66,37 @@ const COMMENT_FORM_BELOW = 1; /** - * Comments for this node are hidden. + * Comments for this entity are hidden. */ -const COMMENT_NODE_HIDDEN = 0; +const COMMENT_ENTITY_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_OPEN = 2; + +/** + * 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'; + +/** + * Denotes the time cutoff for comments marked as read. + * + * 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. */ -const COMMENT_NODE_OPEN = 2; +define('COMMENT_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60); use Drupal\comment\Plugin\Core\Entity\Comment; @@ -89,7 +111,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 .= '
'; @@ -98,48 +120,150 @@ function comment_help($path, $arg) { } /** - * Implements hook_entity_info(). + * Implements hook_entity_view(). */ -function comment_entity_info(&$info) { - foreach (node_type_get_names() as $type => $name) { - $info['comment']['bundles']['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, +function comment_entity_view($entity, $view_mode, $langcode) { + $fields = field_info_instances($entity->entityType(), $entity->bundle()); + foreach ($fields as $field_name => $instance) { + $links = array(); + $field = field_info_field($instance['field_name']); + if ($field['type'] != 'comment') { + continue; + } + $values = field_get_items($entity->entityType(), $entity, $field_name); + if ($values && is_array($values) && ($value = reset($values)) && + !empty($value['comment']) && + $value['comment'] != COMMENT_ENTITY_HIDDEN) { + $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']) + ); + } + 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->comment_statistics[$field_name]->comment_count)) { + $links['comment-comments'] = array( + 'title' => format_plural($entity->comment_statistics[$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->comment_statistics[$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, + ); + } + } + } + $values = field_get_items($entity->entityType(), $entity, $field_name); + $value = reset($values); + if (!empty($value['comment']) && $value['comment'] == COMMENT_OPEN) { + $comment_form_location = $instance['settings']['comment']['comment_form_location']; + if (user_access('post comments')) { + $links['comment-add'] = array( + 'title' => t('Add new comment'), + '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/'. $entity->entityType() . '/' . $entity->id() .'/' . $field_name; + } + } + else { + $links['comment-forbidden'] = array( + 'title' => theme('comment_post_forbidden', array('entity' => $entity, 'field_name' => $field_name)), + '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. + $values = field_get_items($entity->entityType(), $entity, $field_name); + if (is_array($values) && ($value = reset($values)) && + !empty($value['comment']) && $value['comment'] == COMMENT_OPEN) { + $comment_form_location = $instance['settings']['comment']['comment_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->comment_statistics[$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 { + $links['comment-forbidden'] = array( + 'title' => theme('comment_post_forbidden', array('entity' => $entity, 'field_name' => $field_name)), + 'html' => TRUE, + ); + } + } + } + } + + $entity->content['links']['comment__' . $field_name] = array( + '#theme' => 'links__entity__comment__' . $field_name, + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } +} + +/** + * Implements hook_entity_info_alter(). + */ +function comment_entity_info_alter(&$info) { + foreach (comment_get_comment_fields() as $field_name => $field_info) { + $info['comment']['bundles'][$field_name] = array( + 'label' => $field_name, 'admin' => array( - // Place the Field UI paths for comments one level below the - // corresponding paths for nodes, so that they appear in the same set - // of local tasks. Note that the paths use a different placeholder name - // and thus a different menu loader callback, so that Field UI page - // callbacks get a comment bundle name from the node type in the URL. - // See comment_node_type_load() and comment_menu_alter(). - 'path' => 'admin/structure/types/manage/%comment_node_type/comment', - 'bundle argument' => 4, - 'real path' => 'admin/structure/types/manage/' . $type . '/comment', - 'access arguments' => array('administer content types'), + 'path' => 'admin/structure/comments/%comment_field_name', + 'bundle argument' => 3, + 'real path' => 'admin/structure/comments/' . strtr($field_name, '_', '-'), + 'access arguments' => array('administer comments'), ), ); } } /** - * Loads the comment bundle name corresponding a given content type. - * - * This function is used as a menu loader callback in comment_menu(). - * - * @param $name - * The machine name of the node type whose comment fields are to be edited. - * - * @return - * The comment bundle name corresponding to the node type. - * - * @see comment_menu_alter() + * Menu callback for loading the actual name of a comment field. */ -function comment_node_type_load($name) { - if ($type = node_type_load($name)) { - return 'comment_node_' . $type->type; +function comment_field_name_load($arg) { + $field_name = strtr($arg, array('-' => '_')); + if (($field = field_info_field($field_name)) && $field['type'] == 'comment') { + return $field_name; } + + return FALSE; } /** @@ -157,24 +281,21 @@ 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( - 'form' => array( - 'author' => array( - 'label' => t('Author'), - 'description' => t('Author textfield'), - 'weight' => -2, - ), - 'subject' => array( - 'label' => t('Subject'), - 'description' => t('Subject textfield'), - 'weight' => -1, - ), + foreach (comment_get_comment_fields() as $field_name => $field_info) { + $return['comment'][$field_name] = array( + 'form' => array( + 'author' => array( + 'label' => t('Author'), + 'description' => t('Author textfield'), + 'weight' => -2, ), - ); - } + 'subject' => array( + 'label' => t('Subject'), + 'description' => t('Subject textfield'), + 'weight' => -1, + ), + ), + ); } return $return; @@ -196,7 +317,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', @@ -209,6 +330,13 @@ function comment_theme() { * Implements hook_menu(). */ function comment_menu() { + $items['admin/structure/comments'] = array( + 'title' => 'Comment bundles', + 'description' => 'Manage fields and displays settings for comment bundles.', + 'page callback' => 'comment_overview_bundles', + 'access arguments' => array('administer comments'), + 'file' => 'comment.admin.inc', + ); $items['admin/content/comment'] = array( 'title' => 'Comments', 'description' => 'List and edit site comments and the comment approval queue.', @@ -269,93 +397,106 @@ function comment_menu() { 'file' => 'comment.admin.inc', 'weight' => 2, ); - $items['comment/reply/%node'] = array( + $items['comment/reply/%/%comment_entity_reply/%'] = array( 'title' => 'Add new comment', 'page callback' => 'comment_reply', - 'page arguments' => array(2), - 'access callback' => 'node_access', - 'access arguments' => array('view', 2), + 'page arguments' => array(3, 4), + 'load arguments' => array('%map'), + 'access callback' => 'comment_reply_access', + 'access arguments' => array(3), 'file' => 'comment.pages.inc', ); + // Legacy redirect handler for links of form comment/reply/%nid + if (module_exists('node')) { + $items['comment/reply/%node'] = array( + 'title' => 'Add new comment', + 'page callback' => 'comment_node_redirect', + 'page arguments' => array(2), + 'access callback' => 'node_access', + 'access arguments' => array('view', 2), + 'file' => 'comment.pages.inc', + ); + } return $items; } /** - * Implements hook_menu_alter(). - */ -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_info(). - $items['admin/structure/types/manage/%comment_node_type/comment/fields']['title'] = 'Comment fields'; - $items['admin/structure/types/manage/%comment_node_type/comment/fields']['weight'] = 3; - $items['admin/structure/types/manage/%comment_node_type/comment/display']['title'] = 'Comment display'; - $items['admin/structure/types/manage/%comment_node_type/comment/display']['weight'] = 4; -} - -/** - * Returns a menu title which includes the number of unapproved comments. + * Dynamic menu loader callback for comment/reply/%/%commenty_entity/%. + * + * Loads the entity given and entity id and entity type + * + * @param array $args + * The menu args passed from the %map load argument + * + * @return Drupal\entity\Entity or FALSE if not found. */ -function comment_count_unpublished() { - $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE status = :status', array( - ':status' => COMMENT_NOT_PUBLISHED, - ))->fetchField(); - return t('Unapproved comments (@count)', array('@count' => $count)); +function comment_entity_reply_load($entity_id, $args) { + list(, ,$entity_type, $entity_id, $field_name) = $args; + return entity_load($entity_type, $entity_id); } /** - * Implements hook_node_type_insert(). + * Access callback for testing user has access to view the subject of comments. * - * 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. + * Checks the user has view access to the entity which the comment reply is + * against. + + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity which the comment field is attached to. * - * @see comment_modules_enabled() + * @return bool + * TRUE or FALSE if the user has access */ -function comment_node_type_insert($info) { - _comment_body_field_create($info); -} +function comment_reply_access(\Drupal\Core\Entity\EntityInterface $entity) { + $function = $entity->entityType() . '_access'; + // @todo replace this with entity access controls once generic access + // controller lands. + // @see http://drupal.org/node/1696660 + if (function_exists($function)) { + switch ($function) { + case 'user_access': + return user_view_access($entity); + break; -/** - * Implements hook_node_type_update(). - */ -function comment_node_type_update($info) { - if (!empty($info->old_type) && $info->type != $info->old_type) { - field_attach_rename_bundle('comment', 'comment_node_' . $info->old_type, 'comment_node_' . $info->type); + case 'taxonomy_term_access': + return user_access('access content'); + break; + + default: + return $function('view', $entity); + } } + + // We can't know how to control access to this entity, invoke + // hook_comment_access and if no other modules object, grant access. + $access = module_invoke_all('comment_access', $entity); + return !in_array(COMMENT_ACCESS_DENY, $access, TRUE); } /** - * Implements hook_node_type_delete(). + * Returns a menu title which includes the number of unapproved comments. */ -function comment_node_type_delete($info) { - field_attach_delete_bundle('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); - } +function comment_count_unpublished() { + $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE status = :status', array( + ':status' => COMMENT_NOT_PUBLISHED, + ))->fetchField(); + return t('Unapproved comments (@count)', array('@count' => $count)); } /** - * Creates a comment_body field instance for a given node type. + * Creates a comment_body field instance for a given entity type, bundle and + * field name. + * + * @param string $entity_type + * Entity type to which the comment field is attached. + * @param string $bundle + * Bundle of entity type to which comment field is attached + * @param string $field_name + * Name of the comment field attached to the entity type and bundle. * - * @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) { +function _comment_body_field_create($entity_type, $bundle, $field_name) { // Create the field if needed. if (!field_read_field('comment_body', array('include_inactive' => TRUE))) { $field = array( @@ -365,15 +506,15 @@ function _comment_body_field_create($info) { ); field_create_field($field); } - // Create the instance if needed. - if (!field_read_instance('comment', 'comment_body', 'comment_node_' . $info->type, array('include_inactive' => TRUE))) { - field_attach_create_bundle('comment', 'comment_node_' . $info->type); + // Create the instance if needed, field name defaults to 'comment'. + if (!field_read_instance('comment', 'comment_body', $field_name, array('include_inactive' => TRUE))) { + field_attach_create_bundle('comment', $bundle); // Attaches the body field by default. $instance = array( 'field_name' => 'comment_body', 'label' => 'Comment', 'entity_type' => 'comment', - 'bundle' => 'comment_node_' . $info->type, + 'bundle' => $field_name, 'settings' => array('text_processing' => 1), 'required' => TRUE, 'display' => array( @@ -474,14 +615,16 @@ function comment_block_view($delta = '') { * The comment listing set to the page on which the comment appears. */ function comment_permalink($cid) { - if (($comment = comment_load($cid)) && ($node = node_load($comment->nid))) { + if (($comment = comment_load($cid)) && ($entity = entity_load($comment->entity_type, $comment->entity_id))) { + $instance = field_info_instance($entity->entityType(), $comment->field_name, $entity->bundle()); // Find the current display page for this comment. - $page = comment_get_display_page($comment->cid, $node->type); + $page = comment_get_display_page($comment->cid, $instance); // @todo: Cleaner sub request handling. $request = drupal_container()->get('request'); - $subrequest = Request::create('/node/' . $node->nid, 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all()); + $uri = $entity->uri(); + $subrequest = Request::create($uri['path'], 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all()); $subrequest->query->set('page', $page); // @todo: Convert the pager to use the request object. $_GET['page'] = $page; @@ -499,17 +642,24 @@ function comment_permalink($cid) { * @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', '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) - ->orderBy('c.created', 'DESC') + $query->fields('c') + ->condition('c.status', COMMENT_PUBLISHED); + if (module_exists('node')) { + // Special case to filter by published content. + $query->leftJoin('node', 'n', "n.nid = c.entity_id AND c.entity_type = 'node'"); + $query->addTag('node_access'); + $query->condition(db_or() + ->condition('n.status', NODE_PUBLISHED) + ->isNull('n.status') + ); + } + $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. ->orderBy('c.cid', 'DESC') @@ -527,15 +677,18 @@ function comment_get_recent($number = 10) { * Number of comments. * @param $new_replies * Number of new replies. - * @param Drupal\node\Node $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, Node $node) { - $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50); +function comment_new_page_count($num_comments, $new_replies, \Drupal\Core\Entity\EntityInterface $entity, $field_name = 'comment') { + $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()); + $mode = $instance['settings']['comment']['comment_default_mode']; + $comments_per_page = $instance['settings']['comment']['comment_default_per_page']; $pagenum = NULL; $flat = $mode == COMMENT_MODE_FLAT ? TRUE : FALSE; if ($num_comments <= $comments_per_page) { @@ -554,7 +707,9 @@ function comment_new_page_count($num_comments, $new_replies, Node $node) { // 1. Find all the threads with a new comment. $unread_threads_query = db_select('comment') ->fields('comment', array('thread')) - ->condition('nid', $node->nid) + ->condition('entity_id', $entity->id()) + ->condition('entity_type', $entity->entityType()) + ->condition('field_name', $field_name) ->condition('status', COMMENT_PUBLISHED) ->orderBy('created', 'DESC') ->orderBy('cid', 'DESC') @@ -572,9 +727,14 @@ function comment_new_page_count($num_comments, $new_replies, Node $node) { $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_name = :field_name + AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array( ':status' => COMMENT_PUBLISHED, - ':nid' => $node->nid, + ':entity_id' => $entity->id(), + ':field_name' => $field_name, + ':entity_type' => $entity->entityType(), ':thread' => $first_thread, ))->fetchField(); @@ -609,163 +769,8 @@ function theme_comment_block() { } /** - * Implements hook_node_view(). - */ -function comment_node_view(Node $node, $view_mode) { - $links = array(); - - if ($node->comment != 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->nid, array('fragment' => 'comments', 'absolute' => TRUE)) - ); - } - 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->nid", - '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->nid)) { - $links['comment-new-comments'] = array( - 'title' => format_plural($new, '1 new comment', '@count new comments'), - 'href' => "node/$node->nid", - '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, - ); - } - } - } - if ($node->comment == COMMENT_NODE_OPEN) { - $comment_form_location = variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW); - if (user_access('post comments')) { - $links['comment-add'] = array( - 'title' => t('Add new comment'), - 'href' => "node/$node->nid", - '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->nid"; - } - } - else { - $links['comment-forbidden'] = array( - 'title' => theme('comment_post_forbidden', array('node' => $node)), - '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 == COMMENT_NODE_OPEN) { - $comment_form_location = variable_get('comment_form_location_' . $node->type, 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'))) { - $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->nid", - 'fragment' => 'comment-form', - ); - if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { - $links['comment-add']['href'] = "comment/reply/$node->nid"; - } - } - } - else { - $links['comment-forbidden'] = array( - 'title' => theme('comment_post_forbidden', array('node' => $node)), - 'html' => TRUE, - ); - } - } - } - - $node->content['links']['comment'] = array( - '#theme' => 'links__node__comment', - '#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. + * Returns a rendered form to comment the given entity. * - * @param Drupal\node\Node $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(Node $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->type, COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 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 == COMMENT_NODE_OPEN && (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_BELOW)) { - $additions['comment_form'] = comment_add($node); - } - - if ($additions) { - $additions += array( - '#theme' => 'comment_wrapper__node_' . $node->type, - '#node' => $node, - 'comments' => array(), - 'comment_form' => array(), - ); - } - - return $additions; -} - -/** - * Returns a rendered form to comment the given node. - * - * @param Drupal\node\Node $node - * The node entity to be commented. * @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. @@ -773,8 +778,8 @@ function comment_node_page_additions(Node $node) { * @return array * The renderable array for the comment addition form. */ -function comment_add(Node $node, $pid = NULL) { - $values = array('nid' => $node->nid, 'pid' => $pid, 'node_type' => 'comment_node_' . $node->type); +function comment_add(\Drupal\Core\Entity\EntityInterface $entity, $field_name = 'comment', $pid = NULL) { + $values = array('entity_id' => $entity->id(), 'pid' => $pid, 'entity_type' => $entity->entityType(), 'field_name' => $field_name); $comment = entity_create('comment', $values); return entity_get_form($comment); } @@ -782,8 +787,10 @@ function comment_add(Node $node, $pid = NULL) { /** * Retrieves comments for a thread. * - * @param Drupal\node\Node $node - * The node whose comment(s) needs rendering. + * @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 $mode * The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED. * @param $comments_per_page @@ -846,26 +853,32 @@ function comment_add(Node $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(Node $node, $mode, $comments_per_page) { +function comment_get_thread(\Drupal\Core\Entity\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->nid) - ->addTag('node_access') + ->condition('c.entity_id', $entity->id()) + ->condition('c.entity_type', $entity->entityType()) + ->condition('c.field_name', $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->nid) - ->addTag('node_access') + ->condition('c.entity_id', $entity->id()) + ->condition('c.entity_type', $entity->entityType()) + ->condition('c.field_name', $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); @@ -936,10 +949,12 @@ function comment_prepare_thread(&$comments) { /** * Generates an array for rendering a comment. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * The comment object. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity the comment is attached to. * @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. @@ -954,17 +969,21 @@ function comment_view(Comment $comment, $view_mode = 'full', $langcode = NULL) { /** * Adds reply, edit, delete, etc. links, depending on user permissions. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * The comment object. - * @param Drupal\node\Node $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, Node $node) { +function comment_links(Comment $comment, \Drupal\Core\Entity\EntityInterface $entity, $field_name) { $links = array(); - if ($node->comment == COMMENT_NODE_OPEN) { + $items = field_get_items($entity->entityType(), $entity, $field_name); + $status = reset($items); + if ($status['comment'] == COMMENT_OPEN) { if (user_access('administer comments') && user_access('post comments')) { $links['comment-delete'] = array( 'title' => t('delete'), @@ -978,7 +997,7 @@ function comment_links(Comment $comment, Node $node) { ); $links['comment-reply'] = array( 'title' => t('reply'), - 'href' => "comment/reply/$comment->nid/$comment->cid", + 'href' => "comment/reply/$comment->entity_type/$comment->entity_id/$comment->field_name/$comment->cid", 'html' => TRUE, ); if ($comment->status == COMMENT_NOT_PUBLISHED) { @@ -1000,12 +1019,15 @@ function comment_links(Comment $comment, Node $node) { } $links['comment-reply'] = array( 'title' => t('reply'), - 'href' => "comment/reply/$comment->nid/$comment->cid", + 'href' => "comment/reply/$comment->entity_type/$comment->entity_id/$comment->field_name/$comment->cid", 'html' => TRUE, ); } else { - $links['comment-forbidden']['title'] = theme('comment_post_forbidden', array('node' => $node)); + $links['comment-forbidden']['title'] = theme('comment_post_forbidden', array( + 'entity' => $entity, + 'field_name' => $field_name + )); $links['comment-forbidden']['html'] = TRUE; } } @@ -1030,8 +1052,9 @@ function comment_links(Comment $comment, Node $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(). @@ -1043,235 +1066,125 @@ 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_edit_form(). */ -function comment_form_node_type_form_alter(&$form, $form_state) { - if (isset($form['type'])) { - $form['comment'] = array( - '#type' => 'fieldset', - '#title' => t('Comment settings'), - '#collapsible' => TRUE, - '#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_' . $form['#node_type']->type, 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_' . $form['#node_type']->type, 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_' . $form['#node_type']->type, 50), - '#options' => _comment_per_page(), - ); - $form['comment']['comment_anonymous'] = array( - '#type' => 'select', - '#title' => t('Anonymous commenting'), - '#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, 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_' . $form['#node_type']->type, 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_' . $form['#node_type']->type, COMMENT_FORM_BELOW), - ); - $form['comment']['comment_preview'] = array( - '#type' => 'radios', - '#title' => t('Preview comment'), - '#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, 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. +function comment_form_field_ui_field_edit_form_alter(&$form, $form_state) { + if ($form['#field']['type'] == 'comment') { + // Collect translation settings. if (module_exists('translation_entity')) { - $comment_form = $form; - $comment_form_state['translation_entity']['key'] = 'language_configuration'; - $form['comment'] += translation_entity_enable_widget('comment', 'comment_node_' . $form['#node_type']->type, $comment_form, $comment_form_state); array_unshift($form['#submit'], 'comment_translation_configuration_element_submit'); } + + // Hide required checkbox. + $form['instance']['required']['#access'] = FALSE; + + // Force cardinality to one. + $form['field']['cardinality']['#options'] = drupal_map_assoc(array(1)); } } /** - * 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_instance_settings_translation_entity_process(). * - * @see comment_form_node_type_form_alter() + * @see comment_instance_settings_translation_entity_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( 'translation_entity' => array('key' => $key), - 'language' => array($key => array('entity_type' => 'comment', 'bundle' => 'comment_node_' . $form['#node_type']->type)), + 'language' => array($key => array('entity_type' => 'comment', 'bundle' => $form['#field']['field_name'])), 'values' => array($key => array('translation_entity' => $form_state['values']['translation_entity'])), ); translation_entity_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_state); - $form['comment_settings'] = array( - '#type' => 'fieldset', - '#access' => user_access('administer comments'), - '#title' => t('Comment settings'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#group' => 'additional_settings', - '#attributes' => array( - 'class' => array('comment-node-settings-form'), - ), - '#attached' => array( - 'library' => array(array('comment', 'drupal.comment')), - ), - '#weight' => 30, - ); - $comment_count = isset($node->nid) ? db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() : 0; - $comment_settings = ($node->comment == COMMENT_NODE_HIDDEN && empty($comment_count)) ? COMMENT_NODE_CLOSED : $node->comment; - $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(). */ -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 != COMMENT_NODE_HIDDEN) { - $comments_enabled[] = $node->nid; - } - else { - $node->cid = 0; - $node->last_comment_timestamp = $node->created; - $node->last_comment_name = ''; - $node->last_comment_uid = $node->uid; - $node->comment_count = 0; +function comment_entity_load($entities, $entity_type) { + // Load comment information from the database and add it to the entity's + // comment_statistics property, which is an array keyed by field_name. + $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) { + if (empty($entities[$record->entity_id]->comment_statistics)) { + $entities[$record->entity_id]->comment_statistics = array(); } - } - - // 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; + if (empty($entities[$record->entity_id]->comment_statistics[$record->field_name])) { + $entities[$record->entity_id]->comment_statistics[$record->field_name] = new \StdClass(); } + $comment_statistics = &$entities[$record->entity_id]->comment_statistics[$record->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_prepare(). + * Implements hook_entity_insert(). */ -function comment_node_prepare(Node $node) { - if (!isset($node->comment)) { - $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN); - } -} - -/** - * Implements hook_node_insert(). - */ -function comment_node_insert(Node $node) { +function comment_entity_insert($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->nid, + // maintenance of the {comment_entity_statistics} table. + $fields = comment_get_comment_fields($entity->entityType()); + if (state()->get('comment_maintain_entity_statistics', TRUE)) { + $query = db_insert('comment_entity_statistics') + ->fields(array( + 'entity_id', + 'entity_type', + 'field_name', + 'cid', + 'last_comment_timestamp', + 'last_comment_name', + 'last_comment_uid', + 'comment_count' + )); + foreach ($fields as $field_name => $detail) { + // There is at least one comment field, the query needs to be executed. + $query->values(array( + 'entity_id' => $entity->id(), + 'entity_type' => $entity->entityType(), + 'field_name' => $field_name, 'cid' => 0, - 'last_comment_timestamp' => $node->changed, + // Default to REQUEST_TIME when entity does not have a changed property. + 'last_comment_timestamp' => isset($entity->changed) ? $entity->changed : REQUEST_TIME, 'last_comment_name' => NULL, - 'last_comment_uid' => $node->uid, + // Default to current user when entity does not have a uid property. + 'last_comment_uid' => isset($entity->uid) ? $entity->uid : $user->uid, 'comment_count' => 0, - )) - ->execute(); + )); + } + $query->execute(); } } /** - * Implements hook_node_predelete(). + * Implements hook_entity_predelete(). */ -function comment_node_predelete(Node $node) { - $cids = db_query('SELECT cid FROM {comment} WHERE nid = :nid', array(':nid' => $node->nid))->fetchCol(); +function comment_entity_predelete($entity) { + $cids = db_select('comment', 'c') + ->fields('c', array('cid')) + ->condition('entity_id', $entity->id()) + ->condition('entity_type', $entity->entityType()) + ->execute() + ->fetchCol(); comment_delete_multiple($cids); - db_delete('node_comment_statistics') - ->condition('nid', $node->nid) + db_delete('comment_entity_statistics') + ->condition('entity_id', $entity->id()) + ->condition('entity_type', $entity->entityType()) ->execute(); } @@ -1283,7 +1196,10 @@ function comment_node_update_index(Node $node, $langcode) { if ($index_comments === NULL) { // Find and save roles that can 'access comments' or 'search content'. - $perms = array('access comments' => array(), 'search content' => array()); + $perms = array( + 'access comments' => array(), + 'search content' => array(), + ); $result = db_query("SELECT rid, permission FROM {role_permission} WHERE permission IN ('access comments', 'search content')"); foreach ($result as $record) { $perms[$record->permission][$record->rid] = $record->rid; @@ -1300,17 +1216,24 @@ function comment_node_update_index(Node $node, $langcode) { } } + $return = ''; + if ($index_comments) { - $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50); - if ($node->comment && $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 (comment_get_comment_fields('node') as $field_name => $info) { + $instance = field_info_instance('node', $field_name, $node->type); + $mode = $instance['settings']['comment']['comment_default_mode']; + $comments_per_page = $instance['settings']['comment']['comment_default_per_page']; + if (($items = field_get_items('node', $node, $field_name)) && + ($item = reset($items)) && + $item['comment'] && $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; } /** @@ -1318,7 +1241,7 @@ function comment_node_update_index(Node $node, $langcode) { */ function comment_update_index() { // Store the maximum possible comments per thread (used for ranking by reply count) - state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}')->fetchField())); + state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField())); } /** @@ -1327,16 +1250,30 @@ function comment_update_index() { * Formats a comment count string and returns it, for display with search * results. */ -function comment_node_search_result(Node $node) { - // Do not make a string if comments are hidden. - if (user_access('access comments') && $node->comment != COMMENT_NODE_HIDDEN) { - $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField(); - // Do not make a string if comments are closed and there are currently - // zero comments. - if ($node->comment != COMMENT_NODE_CLOSED || $comments > 0) { - return array('comment' => format_plural($comments, '1 comment', '@count comments')); +function comment_node_search_result($node) { + $comment_fields = comment_get_comment_fields('node'); + $return = FALSE; + $comments = 0; + $open = FALSE; + foreach ($comment_fields as $field_name => $info) { + // Do not make a string if comments are hidden. + if (user_access('access comments') && ($items = field_get_items('node', $node, $field_name)) && + ($item = reset($items)) && $item['comment'] != COMMENT_ENTITY_HIDDEN) { + if ($item['comment'] == 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_name = :field_name", array( + 'nid' => $node->nid, + 'field_name' => $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')); + } } /** @@ -1380,7 +1317,7 @@ function comment_user_predelete($account) { * @param $op * The operation that is to be performed on the comment. Only 'edit' is * recognized now. - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * The comment object. * * @return @@ -1397,7 +1334,7 @@ function comment_access($op, Comment $comment) { /** * Accepts a submission of new or changed comment content. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * A comment object. */ function comment_save(Comment $comment) { @@ -1465,8 +1402,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 integer $entity_id + * Entity ID to count comments for. + * @param string $entity_type + * The entity type to count comments for. + * @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). @@ -1474,22 +1415,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->uid && module_exists('history')) { - // Retrieve the timestamp at which the current user last viewed this node. + // Retrieve the timestamp at which the current user last viewed this entity. 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_name', $field_name); + } + + return $query->execute() + ->fetchField(); } else { return FALSE; @@ -1503,27 +1465,28 @@ 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 integer $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_name = c1.field_name'); $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); + $mode = $instance['settings']['comment']['comment_default_mode']; if ($mode == COMMENT_MODE_FLAT) { // For flat comments, cid is used for ordering comments due to @@ -1549,22 +1512,22 @@ 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['settings']['comment']['comment_default_per_page']; return floor($ordinal / $comments_per_page); } /** * Page callback: Displays the comment editing form. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * The comment object representing the comment to be edited. * * @see comment_menu() @@ -1577,11 +1540,12 @@ function comment_edit_page(Comment $comment) { /** * Generates a comment preview. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment */ function comment_preview(Comment $comment) { global $user; $preview_build = array(); + $entity = entity_load($comment->entity_type, $comment->entity_id); if (!form_get_errors()) { $comment_body = field_get_items('comment', $comment, 'comment_body'); @@ -1616,13 +1580,18 @@ function comment_preview(Comment $comment) { if ($comment->pid) { $build = array(); - $comment = comment_load($comment->pid); - if ($comment && $comment->status == COMMENT_PUBLISHED) { - $build = comment_view($comment); + $parent = comment_load($comment->pid); + if ($parent && $parent->status == COMMENT_PUBLISHED) { + $build = comment_view($parent); } } else { - $build = node_view(node_load($comment->nid)); + // We temporarily disable rendering of this field to prevent infinite + // loop. + $values = $entity->{$comment->field_name}; + unset($entity->{$comment->field_name}); + $build = entity_view($entity, 'full'); + $entity->{$comment->field_name} = $values; } $preview_build['comment_output_below'] = $build; @@ -1647,9 +1616,9 @@ function comment_preprocess_block(&$variables) { */ function template_preprocess_comment(&$variables) { $comment = $variables['elements']['#comment']; - $node = $variables['elements']['#node']; + $comment_entity = entity_load($comment->entity_type, $comment->entity_id); $variables['comment'] = $comment; - $variables['node'] = $node; + $variables['comment_entity'] = $comment_entity; $variables['author'] = theme('username', array('account' => $comment)); $variables['created'] = format_date($comment->created); $variables['changed'] = format_date($comment->changed); @@ -1717,8 +1686,8 @@ function template_preprocess_comment(&$variables) { $variables['attributes']['class'][] = 'by-anonymous'; } else { - if ($comment->uid == $variables['node']->uid) { - $variables['attributes']['class'][] = 'by-node-author'; + if (!empty($comment_entity->uid) && $comment->uid == $comment_entity->uid) { + $variables['attributes']['class'][] = 'by-' . $comment_entity->entityType() . '-author'; } if ($comment->uid == $variables['user']->uid) { $variables['attributes']['class'][] = 'by-viewer'; @@ -1731,12 +1700,14 @@ function template_preprocess_comment(&$variables) { * * @param $variables * An associative array containing: - * - node: The comment node. + * - entity: The comment entity. * * @ingroup themeable */ function theme_comment_post_forbidden($variables) { - $node = $variables['node']; + $entity = $variables['entity']; + $field_name = $variables['field_name']; + $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()); global $user; // Since this is expensive to compute, we cache it so that a page with many @@ -1754,11 +1725,12 @@ function theme_comment_post_forbidden($variables) { if ($authenticated_post_comments) { // We cannot use drupal_get_destination() because these links // sometimes appear on /node and taxonomy listing pages. - if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) { - $destination = array('destination' => "comment/reply/$node->nid#comment-form"); + if ($instance['settings']['comment']['comment_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->nid#comment-form"); + $uri = $entity->uri(); + $destination = array('destination' => $uri['path'] . "#comment-form"); } if (config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) { @@ -1780,8 +1752,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']->type, 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()); } @@ -1873,7 +1845,7 @@ function comment_action_info() { /** * Publishes a comment. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * (optional) A comment object to publish. * @param array $context * Array with components: @@ -1900,7 +1872,7 @@ function comment_publish_action(Comment $comment = NULL, $context = array()) { /** * Unpublishes a comment. * - * @param Drupal\comment\Comment|null $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment|null $comment * (optional) A comment object to unpublish. * @param array $context * Array with components: @@ -1927,7 +1899,7 @@ function comment_unpublish_action(Comment $comment = NULL, $context = array()) { /** * Unpublishes a comment if it contains certain keywords. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * Comment object to modify. * @param array $context * Array with components: @@ -1979,7 +1951,7 @@ function comment_unpublish_by_keyword_action_submit($form, $form_state) { /** * Saves a comment. * - * @param Drupal\comment\Comment $comment + * @param \Drupal\comment\Plugin\Core\Entity\comment $comment * * @ingroup actions */ @@ -1998,12 +1970,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_name = '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' => state()->get('comment.node_comment_statistics_scale') ?: 0), ), ); @@ -2057,8 +2031,16 @@ function comment_rdf_mapping() { function comment_file_download_access($field, EntityInterface $entity, File $file) { if ($entity->entityType() == 'comment') { if (user_access('access comments') && $entity->status == COMMENT_PUBLISHED || user_access('administer comments')) { - $node = node_load($entity->nid); - return node_access('view', $node); + // @todo replace this with entity access controls once generic access controller lands, + // @see http://drupal.org/node/1696660 + $function = $entity->entity_type . '_access'; + if (function_exists($function)) { + $entity = entity_load($entity->entity_type, $entity->entity_id); + return $function('view', $entity); + } + // Return NULL as there is no entity access callback for this entity + // type, we rely on other modules intervening. + return; } return FALSE; } @@ -2072,7 +2054,7 @@ function comment_library_info() { 'title' => 'Comment', 'version' => 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'), @@ -2083,3 +2065,176 @@ function comment_library_info() { return $libraries; } + +/** + * Utility function to return an array of comment fields. + * + * @param string $entity_type + * (optional) Specify a entity type if you want to just return fields which + * are attached on a certain entity type. + * + * @return array + * An array of comment field definitions, keyed by field name. Each field has + * an additional property, 'bundles', which is an array of all the bundles to + * which this field belongs, keyed by entity type. + * + * @see field_info_fields(). + */ +function comment_get_comment_fields($entity_type = NULL) { + return array_filter(field_info_fields(), function ($value) use($entity_type) { + if ($value['type'] == 'comment') { + if (isset($entity_type)) { + return in_array($entity_type, array_keys($value['bundles'])); + } + return TRUE; + } + }); +} + +/** + * Decide on the type of marker to be shown for a comment. + */ +function comment_mark(Comment $comment) { + global $user; + $cache = &drupal_static(__FUNCTION__, array()); + $cid = $comment->entity_id . '__' . $comment->entity_type; + + if (!$user->uid) { + return MARK_READ; + } + if (!isset($cache[$cid])) { + if ($comment->entity_type == 'node' && module_exists('history')) { + $cache[$cid] = history_read($comment->entity_id); + } + 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 > COMMENT_NEW_LIMIT) { + return MARK_NEW; + } + elseif ($comment->changed > $cache[$cid] && $comment->changed > COMMENT_NEW_LIMIT) { + return MARK_UPDATED; + } + return MARK_READ; +} + +/** + * 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 string $default_value + * (optional) Default value, one of COMMENT_ENTITY_HIDDEN, + * COMMENT_OPEN, COMMENT_CLOSED. Defaults to + * COMMENT_ENTITY_HIDDEN. + */ +function comment_add_default_comment_field($entity_type, $bundle, $field_name = 'comment', $default_value = COMMENT_ENTITY_HIDDEN) { + // Make sure field doesn't already exist. + if (!field_info_field($field_name)) { + // Add a default comment field for existing node comments. + $field = array( + 'field_name' => $field_name, + 'settings' => array(), + 'type' => 'comment', + ); + // Create the field. + field_create_field($field); + } + // Make sure the instance doesn't already exist. + if (!field_info_instance($entity_type, $field_name, $bundle)) { + $instance = array( + 'bundle' => $bundle, + 'default_value' => array(array('comment' => $default_value)), + 'deleted' => '0', + 'description' => '', + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'module' => 'comment', + 'settings' => array(), + 'type' => 'comment_default', + 'weight' => '1', + ), + 'rss' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + 'teaser' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + 'search_index' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ), + 'search_result' => array( + 'type' => 'hidden', + 'label' => 'hidden', + ) + ), + 'entity_type' => $entity_type, + 'field_name' => $field_name, + 'label' => 'Comment settings', + 'required' => 1, + 'settings' => array( + 'comment' => array( + 'comment' => COMMENT_OPEN, + 'comment_default_mode' => COMMENT_MODE_THREADED, + 'comment_default_per_page' => 50, + 'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT, + 'comment_subject_field' => 1, + 'comment_form_location' => COMMENT_FORM_BELOW, + 'comment_preview' => DRUPAL_OPTIONAL + ) + ), + 'widget' => array( + 'active' => 0, + 'module' => 'comment', + 'settings' => array(), + 'type' => 'comment_default', + 'weight' => '50', + ), + ); + field_create_instance($instance); + } + _comment_body_field_create($entity_type, $bundle, $field_name); +} + +/** + * Implements hook_field_create_instance(). + */ +function comment_field_create_instance($instance) { + $field = field_info_field($instance['field_name']); + if ($field['type'] == 'comment') { + _comment_body_field_create($instance['entity_type'], $instance['bundle'], $instance['field_name']); + cache()->delete('comment_entity_info'); + if (module_exists('views')) { + // Refresh views data. + views_fetch_data('comment', TRUE); + } + } +} + +/** + * Implements hook_field_delete_instance(). + */ +function comment_field_delete_instance($instance) { + $field = field_info_field($instance['field_name']); + if ($field['type'] == 'comment') { + cache()->delete('comment_entity_info'); + if (module_exists('views')) { + // Refresh views data. + views_fetch_data('comment'); + } + } +} diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc index 6263660..6995192 100644 --- a/core/modules/comment/comment.pages.inc +++ b/core/modules/comment/comment.pages.inc @@ -5,6 +5,7 @@ * User page callbacks for the Comment module. */ +use Drupal\core\Entity\Entity; use Drupal\node\Plugin\Core\Entity\Node; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -14,40 +15,49 @@ * * There are several cases that have to be handled, including: * - replies to comments - * - replies to nodes - * - attempts to reply to nodes that can no longer accept comments + * - replies to entities + * - attempts to reply to entities that can no longer accept comments * - respecting access permissions ('access comments', 'post comments', etc.) * - * The node or comment that is being replied to must appear above the comment + * The entity or comment that is being replied to must appear above the comment * form to provide the user context while authoring the comment. * - * @param Drupal\node\Node $node - * Every comment belongs to a node. This is that node. + * @param \Drupal\Core\Entity\EntityInterface $entity + * Every comment belongs to an entity. This is that entity. + * @param string $field_name + * The field_name to which the commment belongs. * @param $pid * (optional) Some comments are replies to other comments. In those cases, * $pid is the parent comment's comment ID. Defaults to NULL. * * @return 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. */ -function comment_reply(Node $node, $pid = NULL) { +function comment_reply(Drupal\Core\Entity\EntityInterface $entity, $field_name, $pid = NULL) { + $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()); // Set the breadcrumb trail. - drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->label(), 'node/' . $node->nid))); + // @todo - test behaviour when entities don't have uris. + $uri = $entity->uri(); + drupal_set_breadcrumb(array( + l(t('Home'), NULL), + l($entity->label(), $uri['path'], $uri['options'])) + ); $op = isset($_POST['op']) ? $_POST['op'] : ''; $build = array(); // The user is previewing a comment prior to submitting it. if ($op == t('Preview')) { if (user_access('post comments')) { - $build['comment_form'] = comment_add($node, $pid); + $build['comment_form'] = comment_add($entity, $field_name, $pid); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); - drupal_goto("node/$node->nid"); + $uri = $entity->uri(); + drupal_goto($uri['path']); } } else { @@ -59,43 +69,54 @@ function comment_reply(Node $node, $pid = NULL) { if ($comment->status == COMMENT_PUBLISHED) { // If that comment exists, make sure that the current comment and the // parent comment both belong to the same parent node. - if ($comment->nid != $node->nid) { - // Attempting to reply to a comment not belonging to the current nid. + if ($comment->entity_id != $entity->id() || + $comment->field_name != $field_name || + $comment->entity_type != $entity->entityType()) { + // Attempting to reply to a comment not belonging to the current entity. drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto($uri['path']); } - // Display the parent comment - $comment->node_type = 'comment_node_' . $node->type; + // Display the parent comment. + $comment->entity_type = $entity->entityType(); + $comment->field_name = $field_name; field_attach_load('comment', array($comment->cid => $comment)); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $build['comment_parent'] = comment_view($comment); } else { drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto($uri['path']); } } else { drupal_set_message(t('You are not authorized to view comments.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto($uri['path']); } } - // This is the case where the comment is in response to a node. Display the node. + // This is the case where the comment is in response to a entity. Display the entity. + // @todo replace this with entity access controls once generic access controller lands, + // @see http://drupal.org/node/1696660 elseif (user_access('access content')) { - $build['comment_node'] = node_view($node); + // We make sure the field value isn't set so we don't end up with a redirect loop. + $original = $entity->{$field_name}; + unset($entity->{$field_name}); + $build['comment_entity'] = entity_view($entity, 'full'); + $entity->{$field_name} = $original; } // Should we show the reply box? - if ($node->comment != COMMENT_NODE_OPEN) { + $items = field_get_items($entity->entityType(), $entity, $field_name); + $status = reset($items); + if (isset($status['comment']) && $status['comment'] != COMMENT_OPEN) { drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto($uri['path']); } elseif (user_access('post comments')) { - $build['comment_form'] = comment_add($node, $pid); + $build['comment_form'] = comment_add($entity, $field_name, $pid); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto($uri['path']); } } @@ -124,7 +145,31 @@ function comment_approve($cid) { comment_save($comment); drupal_set_message(t('Comment approved.')); - drupal_goto('node/' . $comment->nid); + $entity = entity_load($comment->entity_type, $comment->entity_id); + if ($entity) { + $uri = $entity->uri(); + drupal_goto($uri['path']); + } + } + throw new NotFoundHttpException(); +} + +/** + * Page callback: Legacy handler to redirect old comment reply urls to new url. + * + * @param \Drupal\node\Plugin\Core\Entity\Node $node + * The node to which the comments are attached. + * @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. + */ +function comment_node_redirect(\Drupal\node\Plugin\Core\Entity\Node $node, $pid = NULL) { + $fields = comment_get_comment_fields('node'); + foreach ($fields as $field_name => $detail) { + // Pick the first comment field found on the node. + if (in_array($node->bundle(), $detail['bundles']['node'], TRUE)) { + drupal_goto('comment/reply/node/' . $node->id() . '/' . $field_name); + } } throw new NotFoundHttpException(); } diff --git a/core/modules/comment/comment.tokens.inc b/core/modules/comment/comment.tokens.inc index c77cb67..594f0b0 100644 --- a/core/modules/comment/comment.tokens.inc +++ b/core/modules/comment/comment.tokens.inc @@ -16,13 +16,14 @@ function comment_token_info() { ); // Comment-related tokens for nodes - $node['comment-count'] = array( + // @todo make this work per field. + $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 +80,15 @@ 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', + ); + // Provide for backwards compatability as no upgrade path available. $comment['node'] = array( 'name' => t("Node"), - 'description' => t("The node the comment was posted to."), + 'description' => t("The Node the comment was posted to."), '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, ), ); @@ -193,17 +200,33 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = $replacements[$original] = format_date($comment->changed, 'medium', '', NULL, $langcode); break; - case 'node': - $node = node_load($comment->nid); - $title = $node->label(); + case 'entity': + $entity = entity_load($comment->entity_type, $comment->entity_id); + $title = $entity->label(); $replacements[$original] = $sanitize ? filter_xss($title) : $title; break; + + case 'node': + if ($comment->entity_type== 'node') { + $entity = entity_load($comment->entity_type, $comment->entity_id); + $title = $entity->label(); + $replacements[$original] = $sanitize ? filter_xss($title) : $title; + } + else { + $replacements[$original] = NULL; + } + break; } } // Chained token relationships. - if ($node_tokens = token_find_with_prefix($tokens, 'node')) { - $node = node_load($comment->nid); + if ($entity_tokens = token_find_with_prefix($tokens, 'entity')) { + $entity = entity_load($comment->entity_type, $comment->entity_id); + $replacements += token_generate($comment->entity_type, $entity_tokens, array($comment->entity_type => $entity), $options); + } + + if (($node_tokens = token_find_with_prefix($tokens, 'node')) && $comment->entity_type == 'node') { + $node = node_load($comment->entity_id); $replacements += token_generate('node', $node_tokens, array('node' => $node), $options); } @@ -223,17 +246,21 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = $replacements += token_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; + foreach ($entity->comment_statistics as $field_name => $statistics) { + $count += $statistics->comment_count; + } + $replacements[$original] = $count; break; case 'comment-count-new': - $replacements[$original] = comment_num_new($node->nid); + $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 79c3bc3..a2babe7 100644 --- a/core/modules/comment/comment.views.inc +++ b/core/modules/comment/comment.views.inc @@ -325,17 +325,9 @@ 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', - 'id' => 'standard', - 'label' => t('Content'), - ), + $data['comment']['entity_id'] = array( + 'title' => t('Entity id'), + 'help' => t('The Entity ID to which the comment is a reply to.'), 'filter' => array( 'id' => 'numeric', ), @@ -347,6 +339,61 @@ function comment_views_data() { ), ); + $data['comment']['entity_type'] = array( + 'title' => t('Entity type'), + 'help' => t('The Entity type to which the comment is a reply to.'), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'string', + ), + ); + + // Provide a relationship for each entity type except comment. + foreach (entity_get_info() as $type => $entity_info) { + if ($type == 'comment') { + continue; + } + if (($fields = comment_get_comment_fields($type)) && isset($entity_info['base_table'])) { + $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' => 'node', + 'table' => 'comment' + ), + ), + ), + ); + } + } + + $data['comment']['field_name'] = array( + 'title' => t('Comment field name'), + 'help' => t('The Field name from which the comment originated.'), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'string', + ), + ); + $data['comment']['uid'] = array( 'title' => t('Author uid'), 'help' => t('If you need more fields than the uid add the comment: author relationship'), @@ -385,23 +432,31 @@ function comment_views_data() { ), ); - // node_comment_statistics table - - // define the group - $data['node_comment_statistics']['table']['group'] = t('Content'); - - // joins - $data['node_comment_statistics']['table']['join'] = array( - //...to the node table - '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 (entity_get_info() as $type => $entity_info) { + if ($type == 'comment') { + continue; + } + // @todo As you can't have multiple joins this join doesn't use the field_name yet. + if (comment_get_comment_fields($type) && isset($entity_info['base_table'])) { + $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, + ), + ), + ); + } + } // last_comment_timestamp - $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( @@ -417,22 +472,22 @@ function comment_views_data() { ); // last_comment_name (author's name) - $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', 'click sortable' => TRUE, 'no group by' => TRUE, ), 'sort' => array( - 'id' => 'comment_ncs_last_comment_name', + 'id' => 'comment_ces_last_comment_name', 'no group by' => TRUE, ), ); // comment_count - $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.'), 'field' => array( @@ -451,29 +506,29 @@ function comment_views_data() { ); // last_comment_timestamp - $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.'), 'field' => array( - 'id' => 'comment_ncs_last_updated', + 'id' => 'comment_ces_last_updated', 'click sortable' => TRUE, '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', @@ -483,7 +538,7 @@ function comment_views_data() { ); // last_comment_uid - $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.'), 'relationship' => array( @@ -504,6 +559,20 @@ function comment_views_data() { ), ); + $data['comment_entity_statistics']['field_name'] = array( + 'title' => t('Comment field name'), + 'help' => t('The Field name from which the comment originated.'), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'string', + ), + ); + return $data; } @@ -512,6 +581,8 @@ function comment_views_data() { * relevant to comments. */ function comment_views_data_alter(&$data) { + // new comments are only supported for node table because it requires the + // history table. // new comments $data['node']['new_comments'] = array( 'title' => t('New comments'), @@ -522,59 +593,70 @@ function comment_views_data_alter(&$data) { ), ); - $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', - ), - ); - - // Comment status of the node - $data['node']['comment'] = array( - 'title' => t('Comment status'), - 'help' => t('Whether comments are enabled or disabled on the node.'), - 'field' => array( - 'id' => 'node_comment', - 'click sortable' => TRUE, - ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'node_comment', - ), - ); - - $data['node']['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, - ), - 'filter' => array( - 'field' => 'uid', - 'name table' => 'users', - 'name field' => 'name', - 'id' => 'comment_user_uid', - ), - ); - - $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', - ), - ); - + // Provide a integration for each entity type. + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!isset($entity_info['base_table'])) { + continue; + } + $fields = comment_get_comment_fields($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', + ), + ); + + $data[$base_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 (comment_get_comment_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_name', + 'value' => $field_name, + ), + ), + ), + ); + } + } } diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index f1f910e..502f288 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -22,14 +22,15 @@ class CommentFormController extends EntityFormController { public function form(array $form, array &$form_state, EntityInterface $comment) { global $user; - $node = node_load($comment->nid); - $form_state['comment']['node'] = $node; + $entity = entity_load($comment->entity_type, $comment->entity_id); + $instance = field_info_instance($comment->entity_type, $comment->field_name, $entity->bundle()); + $form_state['comment']['entity'] = $entity; - // Use #comment-form as unique jump target, regardless of node type. + // 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->type, 'comment_form'); + $form['#theme'] = array('comment_form__' . $comment->entity_type . '__' . $entity->bundle() . '__' . $comment->field_name, 'comment_form'); - $anonymous_contact = variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT); + $anonymous_contact = $instance['settings']['comment']['comment_anonymous']; $is_admin = (!empty($comment->cid) && user_access('administer comments')); if (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) { @@ -38,9 +39,9 @@ public function form(array $form, array &$form_state, EntityInterface $comment) } // If not replying to a comment, use our dedicated page callback for new - // comments on nodes. + // comments on entities. if (empty($comment->cid) && empty($comment->pid)) { - $form['#action'] = url('comment/reply/' . $comment->nid); + $form['#action'] = url('comment/reply/' . $comment->entity_type . '/' . $comment->entity_id . '/' . $comment->field_name); } if (isset($form_state['comment_preview'])) { @@ -159,7 +160,7 @@ public function form(array $form, array &$form_state, EntityInterface $comment) '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => $comment->subject, - '#access' => variable_get('comment_subject_field_' . $node->type, 1) == 1, + '#access' => $instance['settings']['comment']['comment_subject_field'], ); // Used for conditional validation of author fields. @@ -169,13 +170,12 @@ public function form(array $form, array &$form_state, EntityInterface $comment) ); // Add internal comment properties. - foreach (array('cid', 'pid', 'nid', 'uid') as $key) { + foreach (array('cid', 'pid', 'entity_id', 'entity_type', 'field_name', 'uid') as $key) { $form[$key] = array('#type' => 'value', '#value' => $comment->$key); } - $form['node_type'] = array('#type' => 'value', '#value' => 'comment_node_' . $node->type); - // Make the comment inherit the current content language unless specifically - // set. + // Make the comment inherit the entity language unless specifically set. + $comment_langcode = $comment->langcode; if ($comment->isNew()) { $language_content = language(LANGUAGE_TYPE_CONTENT); $comment->langcode = $language_content->langcode; @@ -186,9 +186,6 @@ public function form(array $form, array &$form_state, EntityInterface $comment) '#value' => $comment->langcode, ); - // Attach fields. - $comment->node_type = 'comment_node_' . $node->type; - return parent::form($form, $form_state, $comment); } @@ -198,8 +195,9 @@ public function form(array $form, array &$form_state, EntityInterface $comment) protected function actions(array $form, array &$form_state) { $element = parent::actions($form, $form_state); $comment = $this->getEntity($form_state); - $node = $form_state['comment']['node']; - $preview_mode = variable_get('comment_preview_' . $node->type, DRUPAL_OPTIONAL); + $entity = $form_state['comment']['entity']; + $instance = field_info_instance($comment->entity_type, $comment->field_name, $entity->bundle()); + $preview_mode = $instance['settings']['comment']['comment_preview']; // No delete action on the comment form. unset($element['delete']); @@ -207,7 +205,6 @@ protected function actions(array $form, array &$form_state) { // Only show the save button if comment previews are optional or if we are // already previewing the submission. $element['submit']['#access'] = ($comment->cid && user_access('administer comments')) || $preview_mode != DRUPAL_REQUIRED || isset($form_state['comment_preview']); - $element['preview'] = array( '#type' => 'submit', '#value' => t('Preview'), @@ -333,10 +330,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->getEntity($form_state); + $field_name = $form_state['values']['field_name']; + $instance = field_info_instance($entity->entityType(), $field_name, $entity->bundle()); + $items = field_get_items($entity->entityType(), $entity, $field_name); + $status = reset($items); + $uri = $entity->uri(); - if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) { + if (user_access('post comments') && (user_access('administer comments') || $status['comment'] == 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')))); @@ -359,18 +361,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->cid, $node->type); + $page = comment_get_display_page($comment->cid, $instance); if ($page > 0) { $query['page'] = $page; } - // Redirect to the newly posted comment. - $redirect = array('node/' . $node->nid, array('query' => $query, 'fragment' => 'comment-' . $comment->cid)); + // Redirect to the newly posted comment. @todo up to here. + $redirect = array($uri['path'], array('query' => $query, 'fragment' => 'comment-' . $comment->cid) + $uri['options']); } else { watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject), WATCHDOG_WARNING); drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject)), 'error'); - // Redirect the user to the node they are commenting on. - $redirect = 'node/' . $node->nid; + // 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/CommentRenderController.php b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php index c614f8e..b7c7765 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php @@ -19,7 +19,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(), $view_mode = 'full', $langcode = NULL) { $return = array(); @@ -30,12 +30,12 @@ public function buildContent(array $entities = array(), $view_mode = 'full', $la parent::buildContent($entities, $view_mode, $langcode); foreach ($entities as $entity) { - $node = node_load($entity->nid); - if (!$node) { - throw new \InvalidArgumentException(t('Invalid node for comment.')); + $comment_entity = entity_load($entity->entity_type, $entity->entity_id); + if (!$comment_entity) { + 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->entity_type . '__' . $comment_entity->bundle() . '__' . $entity->field_name; $entity->content['links'] = array( '#theme' => 'links__comment', '#pre_render' => array('drupal_pre_render_links'), @@ -44,8 +44,8 @@ public function buildContent(array $entities = array(), $view_mode = 'full', $la 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), '#attributes' => array('class' => array('links', 'inline')), ); } @@ -59,8 +59,10 @@ protected function alterBuild(array &$build, EntityInterface $comment, $view_mod parent::alterBuild($build, $comment, $view_mode, $langcode); if (empty($comment->in_preview)) { $prefix = ''; + $comment_entity = entity_load($comment->entity_type, $comment->entity_id); + $instance = field_info_instance($comment_entity->entityType(), $comment->field_name, $comment_entity->bundle()); $is_threaded = isset($comment->divs) - && variable_get('comment_default_mode_' . $comment->bundle(), COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED; + && $instance['settings']['comment']['comment_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 4d447a5..4ab6db4 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -29,9 +29,7 @@ class CommentStorageController extends DatabaseStorageController { */ 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'); $query->addField('u', 'name', 'registered_name'); $query->fields('u', array('uid', 'signature', 'signature_format', 'picture')); @@ -45,8 +43,7 @@ protected function attachLoad(&$comments, $load_revision = FALSE) { // Set up standard comment properties. foreach ($comments as $key => $comment) { $comment->name = $comment->uid ? $comment->registered_name : $comment->name; - $comment->new = node_mark($comment->nid, $comment->changed); - $comment->node_type = 'comment_node_' . $comment->node_type; + $comment->new = comment_mark($comment); $comments[$key] = $comment; } parent::attachLoad($comments, $load_revision); @@ -64,11 +61,6 @@ protected function preSave(EntityInterface $comment) { if (!isset($comment->status)) { $comment->status = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED; } - // Make sure we have a proper bundle name. - if (!isset($comment->node_type)) { - $node = node_load($comment->nid); - $comment->node_type = 'comment_node_' . $node->type; - } if (!$comment->cid) { // Add the comment to database. This next section builds the thread field. // Also see the documentation for comment_view(). @@ -85,7 +77,13 @@ protected function preSave(EntityInterface $comment) { if ($comment->pid == 0) { // This is a comment with no parent comment (depth 0): we start // by retrieving the maximum thread level. - $max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid))->fetchField(); + $query = db_select('comment', 'c') + ->condition('entity_id', $comment->entity_id) + ->condition('field_name', $comment->field_name) + ->condition('entity_type', $comment->entity_type); + $query->addExpression('MAX(thread)', 'thread'); + $max = $query->execute() + ->fetchField(); // Strip the "/" from the end of the thread. $max = rtrim($max, '/'); // We need to get the value at the correct depth. @@ -103,10 +101,14 @@ protected function preSave(EntityInterface $comment) { $parent->thread = (string) rtrim((string) $parent->thread, '/'); $prefix = $parent->thread . '.'; // Get the max value in *this* thread. - $max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array( - ':thread' => $parent->thread . '.%', - ':nid' => $comment->nid, - ))->fetchField(); + $query = db_select('comment', 'c') + ->condition('entity_id', $comment->entity_id) + ->condition('field_name', $comment->field_name) + ->condition('entity_type', $comment->entity_type) + ->condition('thread', $parent->thread . '.%', 'LIKE'); + $query->addExpression('MAX(thread)', 'thread'); + $max = $query->execute() + ->fetchField(); if ($max == '') { // First child of this parent. As the other two cases do an @@ -128,7 +130,7 @@ protected function preSave(EntityInterface $comment) { // has the lock, just move to the next integer. do { $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/'; - } while (!lock()->acquire("comment:$comment->nid:$thread")); + } while (!lock()->acquire("comment:$comment->entity_id:$comment->entity_type:$thread")); $this->threadLock = $thread; } if (empty($comment->created)) { @@ -153,8 +155,8 @@ protected function preSave(EntityInterface $comment) { */ protected function postSave(EntityInterface $comment, $update) { $this->releaseThreadLock(); - // Update the {node_comment_statistics} table prior to executing the hook. - $this->updateNodeStatistics($comment->nid); + // Update the {comment_entity_statistics} table prior to executing the hook. + $this->updateEntityStatistics($comment); if ($comment->status == COMMENT_PUBLISHED) { module_invoke_all('comment_publish', $comment); } @@ -172,45 +174,56 @@ protected function postDelete($comments) { comment_delete_multiple($child_cids); foreach ($comments as $comment) { - $this->updateNodeStatistics($comment->nid); + $this->updateEntityStatistics($comment); } } /** - * Updates the comment statistics for a given node. + * Updates the comment statistics for a given entity. * - * 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 this 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\Comment $comment + * The comment being saved. */ - protected function updateNodeStatistics($nid) { + protected function updateEntityStatistics($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 (!state()->get('comment_maintain_entity_statistics', TRUE)) { return; } - $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND status = :status', array( - ':nid' => $nid, - ':status' => COMMENT_PUBLISHED, - ))->fetchField(); + $query = db_select('comment', 'c'); + $query->addExpression('COUNT(cid)'); + $count = $query->condition('c.entity_id', $comment->entity_id) + ->condition('c.entity_type', $comment->entity_type) + ->condition('c.field_name', $comment->field_name) + ->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 = db_select('comment', 'c') + ->fields('c', array('cid', 'name', 'changed', 'uid')) + ->condition('c.entity_id', $comment->entity_id) + ->condition('c.entity_type', $comment->entity_type) + ->condition('c.field_name', $comment->field_name) + ->condition('c.status', COMMENT_PUBLISHED) + ->orderBy('c.created', 'DESC') + ->range(0, 1) + ->execute() + ->fetchObject(); + db_update('comment_entity_statistics') ->fields(array( 'cid' => $last_reply->cid, 'comment_count' => $count, @@ -218,21 +231,29 @@ protected 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) + ->condition('entity_type', $comment->entity_type) + ->condition('field_name', $comment->field_name) ->execute(); } else { // Comments do not exist. - $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject(); - db_update('node_comment_statistics') + $entity = entity_load($comment->entity_type, $comment->entity_id); + db_update('comment_entity_statistics') ->fields(array( 'cid' => 0, 'comment_count' => 0, - 'last_comment_timestamp' => $node->created, + // Use created date of entity or default to REQUEST_TIME if none + // exists. + 'last_comment_timestamp' => isset($entity->created) ? $entity->created : REQUEST_TIME, 'last_comment_name' => '', - 'last_comment_uid' => $node->uid, + // @todo refactor when http://drupal.org/node/585838 lands. + // Get uid from entity or default to logged in user if none exists. + 'last_comment_uid' => isset($entity->uid) ? $entity->uid : $user->uid, )) - ->condition('nid', $nid) + ->condition('entity_id', $comment->entity_id) + ->condition('entity_type', $comment->entity_type) + ->condition('field_name', $comment->field_name) ->execute(); } } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php index e0bbfd9..d58708d 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php @@ -60,6 +60,27 @@ class Comment extends Entity implements ContentEntityInterface { public $uuid; /** + * The entity ID to which this comment is attached. + * + * @var integer + */ + public $entity_id; + + /** + * The entity type to which this comment is attached. + * + * @var string + */ + public $entity_type; + + /** + * The field to which this comment is attached. + * + * @var string + */ + public $field_name = 'comment'; + + /** * The parent comment ID if this is a reply to a comment. * * @var integer @@ -121,6 +142,13 @@ class Comment extends Entity implements ContentEntityInterface { public $homepage; /** + * The entity which this comment is attached. + * + * @var Drupal\Core\Entity\EntityInterface + */ + protected $entity; + + /** * Implements Drupal\Core\Entity\EntityInterface::id(). */ public function id() { @@ -131,6 +159,6 @@ public function id() { * Implements Drupal\Core\Entity\EntityInterface::bundle(). */ public function bundle() { - return $this->node_type; + return $this->field_name; } } 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 521e45a..a4db641 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 @@ -57,16 +57,28 @@ function default_actions($which = NULL) { public function query($group_by = FALSE) { $this->ensureMyTable(); - $subselect = db_select('comment', 'c'); - $subselect->addField('c', 'cid'); - $subselect->condition('c.uid', $this->argument); - $subselect->where("c.nid = $this->tableAlias.nid"); + // 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') { + $table_info = views_fetch_data($this->table); + $entity_info = entity_get_info($table_info['table']['entity type']); - $condition = db_or() - ->condition("$this->tableAlias.uid", $this->argument, '=') - ->exists($subselect); + $subselect = db_select('comment', 'c'); + $subselect->addField('c', 'cid'); + $subselect->condition('c.uid', $this->argument); - $this->query->add_where(0, $condition); + $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->add_where(0, $condition); + } } function get_sort_name() { 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 eadc0b5..58dd068 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 @@ -30,14 +30,15 @@ public function init(ViewExecutable $view, &$options) { parent::init($view, $options); 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; } @@ -52,10 +53,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); } @@ -63,15 +64,18 @@ public function buildOptionsForm(&$form, &$form_state) { function render_link($data, $values) { if (!empty($this->options['link_to_comment'])) { $this->options['alter']['make_link'] = TRUE; - $nid = $this->get_value($values, 'nid'); $cid = $this->get_value($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->get_value($values, 'entity_id'); + $entity_type = $this->get_value($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/EntityLink.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/EntityLink.php new file mode 100644 index 0000000..682d2b8 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/EntityLink.php @@ -0,0 +1,76 @@ + FALSE, 'bool' => TRUE); + return $options; + } + + public function buildOptionsForm(&$form, &$form_state) { + $form['teaser'] = array( + '#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 entity teasers, rather than the full entity form.'), + ); + + parent::buildOptionsForm($form, $form_state); + } + + public function query() {} + + /** + * Implements \Drupal\views\Plugin\views\field\FieldPluginBase::pre_render(). + */ + function pre_render(&$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'); + } + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::render(). + */ + function render($values) { + $entity = $this->get_entity($values); + + // 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 d11e7a5..77b4c20 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 @@ -25,7 +25,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; } @@ -35,10 +35,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); } @@ -53,7 +53,6 @@ function render($values) { function render_link($data, $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; @@ -65,7 +64,11 @@ function render_link($data, $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 5eb2195..ad47aad 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 @@ -35,11 +35,11 @@ function render_link($data, $values) { } $text = !empty($this->options['text']) ? $this->options['text'] : t('approve'); - $cid = $this->get_value($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 fbe0815..d90f760 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 @@ -28,10 +28,10 @@ public function access() { function render_link($data, $values) { $text = !empty($this->options['text']) ? $this->options['text'] : t('delete'); - $cid = $this->get_value($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/LinkEdit.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkEdit.php index 4a17fe7..70a65c8 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkEdit.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/LinkEdit.php @@ -43,7 +43,7 @@ public function buildOptionsForm(&$form, &$form_state) { function render_link($data, $values) { parent::render_link($data, $values); // ensure user has access to edit this comment. - $comment = $this->get_value($values); + $comment = $this->get_entity($values); if (!comment_access('edit', $comment)) { return; } 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 bee231c..f4f8ead 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 @@ -28,11 +28,10 @@ public function access() { function render_link($data, $values) { $text = !empty($this->options['text']) ? $this->options['text'] : t('reply'); - $nid = $this->get_value($values, 'nid'); - $cid = $this->get_value($values, 'cid'); + $comment = $this->get_entity($values); $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "comment/reply/" . $nid . '/' . $cid; + $this->options['alter']['path'] = "comment/reply/{$comment->entity_type}/{$comment->entity_id}/{$comment->field_name}/{$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 e0b3f90..f9a92f1 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 @@ -25,12 +25,12 @@ class NodeComment extends FieldPluginBase { function render($values) { $value = $this->get_value($values); switch ($value) { - case COMMENT_NODE_HIDDEN: + case COMMENT_ENTITY_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 fe24f52..cd632bf 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 @@ -31,7 +31,7 @@ public function init(ViewExecutable $view, &$options) { $this->additional_fields['nid'] = '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() { @@ -80,7 +80,7 @@ function pre_render(&$values) { if ($nids) { $query = db_select('node', 'n'); $query->addField('n', 'nid'); - $query->innerJoin('comment', 'c', 'n.nid = c.nid'); + $query->innerJoin('comment', 'c', "n.nid = c.entity_id AND c.entity_type = 'node'"); $query->addExpression('COUNT(c.cid)', 'num_comments'); $query->leftJoin('history', 'h', 'h.nid = n.nid'); $query->condition('n.nid', $nids); diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastCommentName.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastCommentName.php new file mode 100644 index 0000000..ba812ab --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastCommentName.php @@ -0,0 +1,77 @@ +ensureMyTable(); + // join 'users' to this table via vid + $definition = array( + 'table' => 'users', + 'field' => 'uid', + 'left_table' => $this->tableAlias, + 'left_field' => 'last_comment_uid', + 'extra' => array( + array( + 'field' => 'uid', + 'operator' => '!=', + 'value' => '0' + ) + ) + ); + $join = drupal_container()->get('plugin.manager.views.join')->createInstance('standard', $definition); + + // nes_user alias so this can work with the sort handler, below. +// $this->user_table = $this->query->add_relationship(NULL, $join, 'users', $this->relationship); + $this->user_table = $this->query->ensure_table('ces_users', $this->relationship, $join); + + $this->field_alias = $this->query->add_field(NULL, "COALESCE($this->user_table.name, $this->tableAlias.$this->field)", $this->tableAlias . '_' . $this->field); + + $this->user_field = $this->query->add_field($this->user_table, 'name'); + $this->uid = $this->query->add_field($this->tableAlias, 'last_comment_uid'); + } + + protected function defineOptions() { + $options = parent::defineOptions(); + + $options['link_to_user'] = array('default' => TRUE, 'bool' => TRUE); + + return $options; + } + + function render($values) { + if (!empty($this->options['link_to_user'])) { + $account = entity_create('user', array()); + $account->name = $this->get_value($values); + $account->uid = $values->{$this->uid}; + return theme('username', array( + 'account' => $account + )); + } + else { + return $this->sanitizeValue($this->get_value($values)); + } + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastUpdated.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastUpdated.php new file mode 100644 index 0000000..914cb58 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/StatisticsLastUpdated.php @@ -0,0 +1,31 @@ +ensureMyTable(); + $this->node_table = $this->query->ensure_table('node', $this->relationship); + $this->field_alias = $this->query->add_field(NULL, "GREATEST(" . $this->node_table . ".changed, " . $this->tableAlias . ".last_comment_timestamp)", $this->tableAlias . '_' . $this->field); + } + +} 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 1eae8c9..44f6e46 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 @@ -24,9 +24,9 @@ class NodeComment extends InOperator { function get_value_options() { $this->value_options = array( - COMMENT_NODE_HIDDEN => t('Hidden'), - COMMENT_NODE_CLOSED => t('Closed'), - COMMENT_NODE_OPEN => t('Open'), + COMMENT_ENTITY_HIDDEN => t('Hidden'), + COMMENT_CLOSED => t('Closed'), + COMMENT_OPEN => t('Open'), ); } diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/StatisticsLastUpdated.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/StatisticsLastUpdated.php new file mode 100644 index 0000000..c653f81 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/filter/StatisticsLastUpdated.php @@ -0,0 +1,37 @@ +ensureMyTable(); + $this->node_table = $this->query->ensure_table('node', $this->relationship); + + $field = "GREATEST(" . $this->node_table . ".changed, " . $this->tableAlias . ".last_comment_timestamp)"; + + $info = $this->operators(); + if (!empty($info[$this->operator]['method'])) { + $this->{$info[$this->operator]['method']}($field); + } + } + +} 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 32eae9c..eba7f12 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 @@ -29,7 +29,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/row/Rss.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php index cb39cd2..3df32b5 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php @@ -151,7 +151,7 @@ function render($row) { $item_text .= drupal_render($build); } - $item = new stdClass(); + $item = new \stdClass(); $item->description = $item_text; $item->title = $comment->label(); $item->link = $comment->link; diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastCommentName.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastCommentName.php new file mode 100644 index 0000000..caab87f --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastCommentName.php @@ -0,0 +1,47 @@ +ensureMyTable(); + $definition = array( + 'table' => 'users', + 'field' => 'uid', + 'left_table' => $this->tableAlias, + 'left_field' => 'last_comment_uid', + ); + $join = drupal_container()->get('plugin.manager.views.join')->createInstance('standard', $definition); + + // @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->add_relationship(NULL, $join, 'users', $this->relationship); + $this->user_table = $this->query->ensure_table('ncs_users', $this->relationship, $join); + $this->user_field = $this->query->add_field($this->user_table, 'name'); + + // Add the field. + $this->query->add_orderby(NULL, "LOWER(COALESCE($this->user_table.name, $this->tableAlias.$this->field))", $this->options['order'], $this->tableAlias . '_' . $this->field); + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastUpdated.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastUpdated.php new file mode 100644 index 0000000..8db2acc --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/sort/StatisticsLastUpdated.php @@ -0,0 +1,31 @@ +ensureMyTable(); + $this->node_table = $this->query->ensure_table('node', $this->relationship); + $this->field_alias = $this->query->add_orderby(NULL, "GREATEST(" . $this->node_table . ".changed, " . $this->tableAlias . ".last_comment_timestamp)", $this->options['order'], $this->tableAlias . '_' . $this->field); + } + +} 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 d079af4..f5683a2 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 @@ -60,7 +60,7 @@ class Comment extends WizardPluginBase { 'value' => TRUE, 'table' => 'node', 'field' => 'status', - 'relationship' => 'nid' + 'relationship' => 'node' ) ); @@ -143,10 +143,10 @@ protected function default_display_options() { $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/Tests/CommentAnonymousTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php index 5e46be4..6d10043 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php @@ -60,7 +60,7 @@ function testAnonymous() { $this->drupalLogout(); // Post anonymous comment with contact info (optional). - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment'); $this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.'); $anonymous_comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); @@ -74,7 +74,7 @@ function testAnonymous() { 'subject' => $this->randomName(), "comment_body[$langcode][0][value]" => $this->randomName(), ); - $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); + $this->drupalPost('comment/reply/node/' . $this->node->nid . '/comment', $edit, t('Save')); $this->assertText(t('The name you used belongs to a registered user.')); // Require contact info. @@ -83,7 +83,7 @@ function testAnonymous() { $this->drupalLogout(); // Try to post comment with contact info (required). - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment'); $this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.'); $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); @@ -137,7 +137,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->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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.'); @@ -162,7 +162,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->nid . '/' . $anonymous_comment3->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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 5cf0720..e5ca92f 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php @@ -45,11 +45,13 @@ function testCommentClasses() { foreach ($permutations as $case) { // Create a new node. - $node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $case['node_uid'])); + $node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $case['node_uid'], 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); // Add a comment. $comment = entity_create('comment', array( - 'nid' => $node->nid, + 'entity_id' => $node->nid, + '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 ab03d41..4122861 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php @@ -34,15 +34,14 @@ 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')); + comment_add_default_comment_field('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 = field_info_instance('comment', 'comment_body', 'comment'); + $this->assertTrue(!empty($instance), 'The comment_body field is added when a comment bundle is created'); - // Delete the instance along the way. - field_delete_instance($instances['comment_node_' . $type_name]['comment_body']); - } + // Delete the instance. + field_delete_instance($instance); // Check that the 'comment_body' field is deleted. $field = field_info_field('comment_body'); @@ -51,56 +50,14 @@ function testCommentDefaultFields() { // Create a new content type. $type_name = 'test_node_type_2'; $this->drupalCreateContentType(array('type' => $type_name)); + comment_add_default_comment_field('node', $type_name); // Check that the 'comment_body' field exists and has an instance on the // new comment bundle. $field = field_info_field('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))); - } - - /** - * Tests that comment module works when enabled after a content module. - */ - function testCommentEnable() { - // Create a user to do module administration. - $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules')); - $this->drupalLogin($this->admin_user); - - // Disable the comment module. - $edit = array(); - $edit['modules[Core][comment][enable]'] = FALSE; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->resetAll(); - $this->assertFalse(module_exists('comment'), 'Comment module disabled.'); - - // Enable core content type modules (book, and poll). - $edit = array(); - $edit['modules[Core][book][enable]'] = 'book'; - $edit['modules[Core][poll][enable]'] = 'poll'; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - - // Now enable the comment module. - $edit = array(); - $edit['modules[Core][comment][enable]'] = 'comment'; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->resetAll(); - $this->assertTrue(module_exists('comment'), 'Comment module enabled.'); - - // Create nodes of each type. - $book_node = $this->drupalCreateNode(array('type' => 'book')); - $poll_node = $this->drupalCreateNode(array('type' => 'poll', 'active' => 1, 'runtime' => 0, 'choice' => array(array('chtext' => '')))); - - $this->drupalLogout(); - - // Try to post a comment on each node. A failure will be triggered if the - // comment body is missing on one of these forms, due to postComment() - // asserting that the body is actually posted correctly. - $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'post comments', 'skip comment approval')); - $this->drupalLogin($this->web_user); - $this->postComment($book_node, $this->randomName(), $this->randomName()); - $this->postComment($poll_node, $this->randomName(), $this->randomName()); + $this->assertTrue(isset($instances['comment']['comment_body']), format_string('The comment_body field is present for comments on type @type', array('@type' => $type_name))); } /** @@ -109,8 +66,9 @@ function testCommentEnable() { 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_body', $edit, t('Save settings')); + $instance = field_info_instance('comment', 'comment_body', 'comment'); + $instance['settings']['text_processing'] = 0; + field_update_instance($instance); // 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 1070107..76d8301 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php @@ -90,7 +90,7 @@ 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->nid . '/' . $comment->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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); @@ -100,7 +100,7 @@ function testCommentInterface() { $this->assertEqual(rtrim($comment_loaded->thread, '/') . '.00/', $reply_loaded->thread, 'Thread of reply grows correctly.'); // Second reply to comment #3 creating comment #4. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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(), $this->randomName(), TRUE); @@ -128,27 +128,27 @@ function testCommentInterface() { // Attempt to reply to an unpublished comment. $reply_loaded->status = COMMENT_NOT_PUBLISHED; $reply_loaded->save(); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply_loaded->cid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $reply_loaded->cid); $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(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_ENTITY_HIDDEN))))); $this->assertTrue($this->node, 'Article node created.'); - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_CLOSED))))); $this->assertTrue($this->node, 'Article node created.'); - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); $this->assertTrue($this->node, 'Article node created.'); - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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.'); @@ -178,5 +178,4 @@ function testCommentInterface() { $this->drupalLogin($this->admin_user); $this->setCommentForm(FALSE); } - } diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php index a704fb7..b20c361 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentLanguageTest.php @@ -102,6 +102,7 @@ function testCommentLanguage() { "title" => $title, "body[$langcode_not_specified][0][value]" => $this->randomName(), "langcode" => $node_langcode, + "comment[$langcode_not_specified][0][comment]" => COMMENT_OPEN, ); $this->drupalPost("node/add/article", $edit, t('Save')); $node = $this->drupalGetNodeByTitle($title); @@ -121,7 +122,9 @@ function testCommentLanguage() { // Check that comment language matches the current content language. $cid = db_select('comment', 'c') ->fields('c', array('cid')) - ->condition('nid', $node->nid) + ->condition('entity_id', $node->nid) + ->condition('entity_type', 'node') + ->condition('field_name', '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 d049aa3..8798a93 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php @@ -53,7 +53,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_ENTITY_HIDDEN), //// COMMENT_ANONYMOUS_MUST_CONTACT is irrelevant for this test. //'contact ' => array(COMMENT_ANONYMOUS_MAY_CONTACT, COMMENT_ANONYMOUS_MAYNOT_CONTACT), ); @@ -80,8 +80,8 @@ 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_ENTITY_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 @@ -102,7 +102,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. @@ -128,8 +128,9 @@ function setEnvironment(array $info) { // $this->postComment() relies on actual user permissions. $comment = entity_create('comment', array( 'cid' => NULL, - 'nid' => $this->node->nid, - 'node_type' => $this->node->type, + 'entity_id' => $this->node->nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'pid' => 0, 'uid' => 0, 'status' => COMMENT_PUBLISHED, @@ -153,10 +154,10 @@ function setEnvironment(array $info) { } // Change comment settings. - variable_set('comment_form_location_' . $this->node->type, $info['form']); - variable_set('comment_anonymous_' . $this->node->type, $info['contact']); - if ($this->node->comment != $info['comments']) { - $this->node->comment = $info['comments']; + $this->setCommentSettings('comment_form_location', $info['form'], 'Set comment form location'); + $this->setCommentAnonymous($info['contact']); + if ($this->node->comment[LANGUAGE_NOT_SPECIFIED][0]['comment'] != $info['comments']) { + $this->node->comment[LANGUAGE_NOT_SPECIFIED][0]['comment'] = $info['comments']; node_save($this->node); } @@ -180,9 +181,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_ENTITY_HIDDEN => 'hidden', ); $verbose = $info; $verbose['form'] = $t_form[$info['form']]; @@ -281,12 +282,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 324012e..9cf58f5 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php @@ -27,7 +27,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))); @@ -37,8 +36,9 @@ public function testCommentNewCommentsIndicator() { // comment settings so use comment_save() to avoid complex setup. $comment = entity_create('comment', array( 'cid' => NULL, - 'nid' => $this->node->nid, - 'node_type' => $this->node->type, + 'entity_id' => $this->node->nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'pid' => 0, 'uid' => $this->loggedInUser->uid, '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 37809c6..93c64f5 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php @@ -75,7 +75,7 @@ function testThreadedCommentView() { $this->assertText($comment_text, 'Individual comment body found.'); // Reply to comment, creating second comment. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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/CommentPagerTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php index 52f1e32..af94601 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php @@ -31,7 +31,7 @@ function testCommentPaging() { $this->setCommentPreview(DRUPAL_DISABLED); // Create a node and three comments. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); $comments = array(); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); @@ -66,7 +66,7 @@ function testCommentPaging() { // Post a reply to the oldest comment and test again. $replies = array(); $oldest_comment = reset($comments); - $this->drupalGet('comment/reply/' . $node->nid . '/' . $oldest_comment->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/comment/' . $oldest_comment->id); $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); $this->setCommentsPerPage(2); @@ -107,26 +107,26 @@ function testCommentOrderingThreading() { $this->setCommentsPerPage(1000); // Create a node and three comments. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); $comments = array(); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[1]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/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->nid . '/' . $comments[0]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/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->nid . '/' . $comments[2]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/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->nid . '/' . $comments[3]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/comment/' . $comments[3]->id); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // At this point, the comment tree is: @@ -208,22 +208,22 @@ function testCommentNewPageIndicator() { $this->setCommentsPerPage(1); // Create a node and three comments. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); $comments = array(); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[1]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/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->nid . '/' . $comments[0]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/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->nid . '/' . $comments[2]->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/comment/' . $comments[2]->id); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // At this point, the comment tree is: @@ -247,7 +247,7 @@ function testCommentNewPageIndicator() { $node = node_load($node->nid); 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->comment_statistics['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))); } @@ -265,7 +265,7 @@ function testCommentNewPageIndicator() { $node = node_load($node->nid); 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->comment_statistics['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/CommentRssTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php index 28e199e..55bee30 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php @@ -31,7 +31,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->comment = array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_ENTITY_HIDDEN))); node_save($this->node); $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 afdcba4..5e67c3e 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentStatisticsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentStatisticsTest.php @@ -50,10 +50,10 @@ function testCommentNodeCommentStatistics() { // Checks the initial values of node comment statistics with no comment. $node = node_load($this->node->nid); - $this->assertEqual($node->last_comment_timestamp, $this->node->created, '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->uid, '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->comment_statistics['comment']->last_comment_timestamp, $this->node->created, 'The initial value of node last_comment_timestamp is the node created date.'); + $this->assertEqual($node->comment_statistics['comment']->last_comment_name, NULL, 'The initial value of node last_comment_name is NULL.'); + $this->assertEqual($node->comment_statistics['comment']->last_comment_uid, $this->web_user->uid, 'The initial value of node last_comment_uid is the node uid.'); + $this->assertEqual($node->comment_statistics['comment']->comment_count, 0, 'The initial value of node comment_count is zero.'); // Post comment #1 as web_user2. $this->drupalLogin($this->web_user2); @@ -64,9 +64,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->nid, 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->uid, '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->comment_statistics['comment']->last_comment_name, NULL, 'The value of node last_comment_name is NULL.'); + $this->assertEqual($node->comment_statistics['comment']->last_comment_uid, $this->web_user2->uid, 'The value of node last_comment_uid is the comment #1 uid.'); + $this->assertEqual($node->comment_statistics['comment']->comment_count, 1, 'The value of node comment_count is 1.'); // Prepare for anonymous comment submission (comment approval enabled). config('user.settings')->set('register', USER_REGISTER_VISITORS)->save(); @@ -81,7 +81,7 @@ function testCommentNodeCommentStatistics() { $this->drupalLogout(); // Post comment #2 as anonymous (comment approval enabled). - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment'); $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', TRUE); $comment_unpublished_loaded = comment_load($anonymous_comment->id); @@ -89,9 +89,9 @@ function testCommentNodeCommentStatistics() { // 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->nid, 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->uid, '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->comment_statistics['comment']->last_comment_name, NULL, 'The value of node last_comment_name is still NULL.'); + $this->assertEqual($node->comment_statistics['comment']->last_comment_uid, $this->web_user2->uid, 'The value of node last_comment_uid is still the comment #1 uid.'); + $this->assertEqual($node->comment_statistics['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); @@ -103,16 +103,16 @@ function testCommentNodeCommentStatistics() { $this->drupalLogout(); // Post comment #3 as anonymous. - $this->drupalGet('comment/reply/' . $this->node->nid); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/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->nid, TRUE); - $this->assertEqual($node->last_comment_name, $comment_loaded->name, '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->comment_statistics['comment']->last_comment_name, $comment_loaded->name, 'The value of node last_comment_name is the name of the anonymous user.'); + $this->assertEqual($node->comment_statistics['comment']->last_comment_uid, 0, 'The value of node last_comment_uid is zero.'); + $this->assertEqual($node->comment_statistics['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 902dbdd..e7cc15b 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php @@ -72,8 +72,11 @@ function setUp() { 'access content', )); + // Create comment field on article. + comment_add_default_comment_field('node', 'article'); + // Create a test node authored by the web user. - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); } /** @@ -94,12 +97,13 @@ function postComment($node, $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); + $instance = field_info_instance('node', 'comment', 'article'); + $preview_mode = $instance['settings']['comment']['comment_preview']; + $subject_mode = $instance['settings']['comment']['comment_subject_field']; // Must get the page before we test for fields. if ($node !== NULL) { - $this->drupalGet('comment/reply/' . $node->nid); + $this->drupalGet('comment/reply/node/' . $node->nid . '/comment'); } if ($subject_mode == TRUE) { @@ -264,7 +268,9 @@ function setCommentsPerPage($number) { * Status message to display. */ function setCommentSettings($name, $value, $message) { - variable_set($name . '_article', $value); + $instance = field_info_instance('node', 'comment', 'article'); + $instance['settings']['comment'][$name] = $value; + field_update_instance($instance); // 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 ba03951..7c59f37 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php @@ -34,7 +34,7 @@ function testCommentThreading() { // Create a node. $this->drupalLogin($this->web_user); - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); // Post comment #1. $this->drupalLogin($this->web_user); @@ -50,7 +50,7 @@ function testCommentThreading() { // Reply to comment #1 creating comment #2. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment1->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $comment1->id); $comment2 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $comment2_loaded = comment_load($comment2->id); @@ -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->nid . '/' . $comment2->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $comment2->id); $comment3 = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Confirm that the comment was created and has the correct threading. $comment3_loaded = comment_load($comment3->id); @@ -71,7 +71,7 @@ function testCommentThreading() { // Reply to comment #1 creating comment #4. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment1->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $comment1->id); $comment4 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $comment4_loaded = comment_load($comment4->id); @@ -94,7 +94,7 @@ function testCommentThreading() { // Reply to comment #5 creating comment #6. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment5->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $comment5->id); $comment6 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $comment6_loaded = comment_load($comment6->id); @@ -104,7 +104,7 @@ function testCommentThreading() { $this->assertParentLink($comment6->id, $comment5->id); // Reply to comment #6 creating comment #7. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment6->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $comment6->id); $comment7 = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Confirm that the comment was created and has the correct threading. $comment7_loaded = comment_load($comment7->id); @@ -115,7 +115,7 @@ function testCommentThreading() { // Reply to comment #5 creating comment #8. $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment5->id); + $this->drupalGet('comment/reply/node/' . $this->node->nid . '/comment/' . $comment5->id); $comment8 = $this->postComment(NULL, $this->randomName(), '', TRUE); // Confirm that the comment was created and has the correct threading. $comment8_loaded = comment_load($comment8->id); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php index 34674e8..6cb7d57 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php @@ -35,11 +35,11 @@ function testCommentTokenReplacement() { $this->setCommentSubject(TRUE); // Create a node and a comment. - $node = $this->drupalCreateNode(array('type' => 'article')); + $node = $this->drupalCreateNode(array('type' => 'article', 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); $parent_comment = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); // Post a reply to the comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $parent_comment->id); + $this->drupalGet('comment/reply/node/' . $node->nid . '/comment/' . $parent_comment->id); $child_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); $comment = comment_load($child_comment->id); $comment->homepage = 'http://example.org/'; @@ -63,7 +63,7 @@ function testCommentTokenReplacement() { $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed, 2, $language_interface->langcode); $tests['[comment:parent:cid]'] = $comment->pid; $tests['[comment:parent:title]'] = check_plain($parent_comment->subject); - $tests['[comment:node:nid]'] = $comment->nid; + $tests['[comment:node:nid]'] = $comment->entity_id; $tests['[comment:node:title]'] = check_plain($node->title); $tests['[comment:author:uid]'] = $comment->uid; $tests['[comment:author:name]'] = check_plain($this->admin_user->name); @@ -97,11 +97,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_replace($input, array('node' => $node), array('langcode' => $language_interface->langcode)); + $output = token_replace($input, array('entity' => $node), array('langcode' => $language_interface->langcode)); $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 75ca1d8..ec11211 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php @@ -40,7 +40,7 @@ public static function getInfo() { function setUp() { $this->entityType = 'comment'; $this->nodeBundle = 'article'; - $this->bundle = 'comment_node_' . $this->nodeBundle; + $this->bundle = 'comment'; $this->testLanguageSelector = FALSE; $this->subject = $this->randomName(); parent::setUp(); @@ -52,6 +52,14 @@ function setUp() { function setupBundle() { parent::setupBundle(); $this->drupalCreateContentType(array('type' => $this->nodeBundle, 'name' => $this->nodeBundle)); + // Add a comment field to the article content type. + comment_add_default_comment_field('node', 'article'); + // Mark this bundle as translatable. + translation_entity_set_config('comment', 'comment', 'enabled', TRUE); + // Refresh entity info. + entity_info_cache_clear(); + // Flush the permissions after adding the translatable comment bundle. + $this->checkPermissions(array(), TRUE); } /** @@ -75,8 +83,15 @@ function setupTestFields() { * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::createEntity(). */ protected function createEntity($values, $langcode) { - $node = $this->drupalCreateNode(array('type' => $this->nodeBundle)); - $values['nid'] = $node->nid; + $node = $this->drupalCreateNode(array( + 'type' => $this->nodeBundle, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_OPEN) + )) + )); + $values['entity_id'] = $node->nid; + $values['entity_type'] = 'node'; + $values['field_name'] = 'comment'; $values['uid'] = $node->uid; return parent::createEntity($values, $langcode); } 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..6a8a584 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentUserTest.php @@ -0,0 +1,342 @@ + 'Comment user tests', + 'description' => 'Test commenting on users.', + 'group' => 'Comment', + ); + } + + function setUp() { + parent::setUp(); + + // Create comment field on user bundle. + comment_add_default_comment_field('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', + )); + + $this->web_user->comment = array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))); + $this->web_user->save(); + } + + /** + * Posts a comment. + * + * @param Drupal\user\User|null $account + * User to post comment on or NULL to post to the previusly 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($account, $comment, $subject = '', $contact = NULL) { + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit = array(); + $edit['comment_body[' . $langcode . '][0][value]'] = $comment; + + $instance = field_info_instance('user', 'comment', 'user'); + $preview_mode = $instance['settings']['comment']['comment_preview']; + $subject_mode = $instance['settings']['comment']['comment_subject_field']; + + // Must get the page before we test for fields. + if ($account !== NULL) { + $this->drupalGet('comment/reply/user/' . $account->uid . '/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 entity_create('comment', array('id' => $match[1], 'subject' => $subject, 'comment' => $comment)); + } + } + + /** + * Checks current page for specified comment. + * + * @param Drupal\comment\Plugin\Core\Entity\comment $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(Comment $comment = NULL, $reply = FALSE) { + if ($comment) { + $regex = '/' . ($reply ? '
(.*?)' : ''); + $regex .= 'subject . '(.*?)'; // Match subject. + $regex .= $comment->comment . '(.*?)'; // Match comment. + $regex .= '/s'; + + return (boolean)preg_match($regex, $this->drupalGetContent()); + } + else { + return FALSE; + } + } + + /** + * Deletes a comment. + * + * @param Drupal\comment\Comment $comment + * Comment to delete. + */ + function deleteComment(Comment $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() { + $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.'); + + // 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.'); + + $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->uid); + $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->uid . '/comment'); + $this->assertText('You are not authorized to post comments', 'Error attempting to post comment.'); + $this->assertNoFieldByName('subject', '', 'Subject field not found.'); + $langcode = LANGUAGE_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->uid); + $this->assertPattern('@]*>Comments@', 'Comments were displayed.'); + $this->assertLink('Log in', 1, 'Link to log in was found.'); + $this->assertLink('register', 1, '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->uid); + $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->uid . '/comment/' . $comment1->id); + $this->assertText('You are not authorized to view comments', 'Error attempting to post reply.'); + $this->assertNoText($comment1->subject, 'Comment not displayed.'); + } + +} diff --git a/core/modules/comment/templates/comment-wrapper.tpl.php b/core/modules/comment/templates/comment-wrapper.tpl.php index ac1c27f..5b72140 100644 --- a/core/modules/comment/templates/comment-wrapper.tpl.php +++ b/core/modules/comment/templates/comment-wrapper.tpl.php @@ -5,7 +5,7 @@ * Provides an HTML container for comments. * * Available variables: - * - $content: The array of content-related elements for the node. Use + * - $content: The array of content-related elements for the entity. Use * render($content) to print them all, or * print a subset such as render($content['comment_form']). * - $attributes: An instance of Attributes class that can be manipulated as an @@ -20,7 +20,7 @@ * the template. * * The following variables are provided for contextual information. - * - $node: Node entity the comments are attached to. + * - $entity: Entity the comments are attached to. * The constants below the variables show the possible values and should be * used for comparison. * - $display_mode @@ -33,7 +33,7 @@ */ ?>
> - type != 'forum'): ?> + entityType() != 'node' || $entity->bundle() != 'forum')): ?>

diff --git a/core/modules/comment/templates/comment.tpl.php b/core/modules/comment/templates/comment.tpl.php index 890e457..3922580 100644 --- a/core/modules/comment/templates/comment.tpl.php +++ b/core/modules/comment/templates/comment.tpl.php @@ -30,7 +30,8 @@ * It includes the 'class' information, which includes: * - 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. @@ -59,7 +60,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() * @see template_preprocess_comment() diff --git a/core/modules/field/modules/field_sql_storage/field_sql_storage.install b/core/modules/field/modules/field_sql_storage/field_sql_storage.install index 4d4fe57..3eff987 100644 --- a/core/modules/field/modules/field_sql_storage/field_sql_storage.install +++ b/core/modules/field/modules/field_sql_storage/field_sql_storage.install @@ -118,11 +118,16 @@ function field_sql_storage_update_8000(&$sandbox) { $table_info = array($data_table => $primary_key_data, $revision_table => $primary_key_revision); foreach ($table_info as $table => $primary_key) { - db_drop_primary_key($table); - db_drop_index($table, 'language'); - db_change_field($table, 'language', 'langcode', $field_langcode); - db_add_primary_key($table, $primary_key); - db_add_index($table, 'langcode', $langcode_index); + // Some fields are created as part of the upgrade process, these have the + // correct field name already. Check that language field exists before + // attempting to rename. + if (db_field_exists($table, 'language')) { + db_drop_primary_key($table); + db_drop_index($table, 'language'); + db_change_field($table, 'language', 'langcode', $field_langcode); + db_add_primary_key($table, $primary_key); + db_add_index($table, 'langcode', $langcode_index); + } } } } diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php index e291bec..67c661b 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php @@ -19,7 +19,7 @@ * * @var array */ - public static $modules = array('file', 'file_module_test'); + public static $modules = array('file', 'file_module_test', 'field_test'); protected $profile = 'standard'; @@ -27,7 +27,7 @@ function setUp() { parent::setUp(); - $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'bypass node access')); + $this->admin_user = $this->drupalCreateUser(array('administer comments', 'access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'bypass node access')); $this->drupalLogin($this->admin_user); } diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php index 0bb8866..acc5d08 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php @@ -258,15 +258,18 @@ function testPrivateFileComment() { 'fields[_add_new_field][type]' => 'file', 'fields[_add_new_field][widget_type]' => 'file_generic', ); - $this->drupalPost('admin/structure/types/manage/article/comment/fields', $edit, t('Save')); + comment_add_default_comment_field('node', 'article'); + $this->drupalPost('admin/structure/comments/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')); // Create node. $text_file = $this->getTestFile('text'); + $langcode = LANGUAGE_NOT_SPECIFIED; $edit = array( 'title' => $this->randomName(), + "comment[$langcode][0][comment]" => COMMENT_OPEN ); $this->drupalPost('node/add/article', $edit, t('Save')); $node = $this->drupalGetNodeByTitle($edit['title']); diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php index a7e38ac..39ef4fd 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterHtmlImageSecureTest.php @@ -67,7 +67,9 @@ function setUp() { // Setup a node to comment and test on. $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - $this->node = $this->drupalCreateNode(); + // Add a comment field. + comment_add_default_comment_field('node', 'page'); + $this->node = $this->drupalCreateNode(array('comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); } /** diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index 56f8402..9fdbe07 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -16,16 +16,19 @@ function forum_install() { // @todo Convert to default module configuration, once Node module's content // types are converted. variable_set('node_options_forum', array('status')); + // Make sure the comment module is loaded. + drupal_load('module', 'comment'); } /** * Implements hook_enable(). */ function forum_enable() { - // If we enable forum at the same time as taxonomy we need to call + // If we enable forum/comment at the same time as taxonomy we need to call // field_associate_fields() as otherwise the field won't be enabled until // hook modules_enabled is called which takes place after hook_enable events. field_associate_fields('taxonomy'); + field_associate_fields('comment'); // Create the forum vocabulary if it does not exist. // @todo Change Forum module so forum.settings can contain the vocabulary's @@ -108,6 +111,8 @@ function forum_enable() { node_types_rebuild(); $types = node_type_get_types(); node_add_body_field($types['forum']); + // Add the comment field. + comment_add_default_comment_field('node', 'forum', 'comment_node_forum', COMMENT_OPEN); } /** @@ -120,9 +125,20 @@ function forum_uninstall() { variable_del('node_options_forum'); field_delete_field('taxonomy_forums'); - // Purge field data now to allow taxonomy module to be uninstalled - // if this is the only field remaining. - field_purge_batch(10); + + // Delete comment field, load comment in case it has been disabled. + drupal_load('module', 'comment'); + // Remove the forum comment field. + field_associate_fields('comment'); + field_delete_field('comment_node_forum'); + + // Purge field data now to allow taxonomy and comment modules to be + // uninstalled if these were the only fields remaining. We need to call + // field_purge_batch at least twice as the instances won't be removed until + // all data is gone, both cannot be removed in the same call. + foreach (array('data', 'instances', 'fields') as $step) { + field_purge_batch(10); + } } /** diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 05960a0..fb38f73 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -491,7 +491,9 @@ function forum_taxonomy_term_delete(Term $term) { * comment_save() calls hook_comment_publish() for all published comments. */ function forum_comment_publish($comment) { - _forum_update_forum_index($comment->nid); + if ($comment->entity_type == 'node') { + _forum_update_forum_index($comment->entity_id); + } } /** @@ -503,8 +505,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) { - _forum_update_forum_index($comment->nid); + if (!$comment->status && $comment->entity_type == 'node') { + _forum_update_forum_index($comment->entity_id); } } @@ -512,14 +514,18 @@ function forum_comment_update($comment) { * Implements hook_comment_unpublish(). */ function forum_comment_unpublish($comment) { - _forum_update_forum_index($comment->nid); + if ($comment->entity_type == 'node') { + _forum_update_forum_index($comment->entity_id); + } } /** * Implements hook_comment_delete(). */ function forum_comment_delete($comment) { - _forum_update_forum_index($comment->nid); + if ($comment->entity_type == 'node') { + _forum_update_forum_index($comment->entity_id); + } } /** @@ -807,10 +813,10 @@ function forum_forum_load($tid = NULL) { if (count($_forums)) { $query = db_select('node', 'n'); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); + $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.entity_type = 'node' AND ces.field_name = 'comment_node_forum'"); $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'); $counts = $query ->fields('f', array('tid')) ->condition('n.status', 1) @@ -839,12 +845,12 @@ function forum_forum_load($tid = NULL) { // Query "Last Post" information for this forum. $query = db_select('node', 'n'); $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->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.entity_type = 'node' AND ces.field_name = 'comment_node_forum'"); + $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) @@ -962,18 +968,18 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { ->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('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count')); + $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.entity_type = 'node' AND ces.field_name = 'comment_node_forum'"); + $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 = ces.entity_id'); $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') @@ -983,7 +989,7 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { $result = array(); foreach ($query->execute() as $row) { $topic = $nodes[$row->nid]; - $topic->comment_mode = $topic->comment; + $topic->comment_mode = !empty($topic->comment_node_forum[LANGUAGE_NOT_SPECIFIED][0]['comment']) ? $topic->comment_node_forum[LANGUAGE_NOT_SPECIFIED][0]['comment'] : COMMENT_ENTITY_HIDDEN; foreach ($row as $key => $value) { $topic->{$key} = $value; @@ -1006,7 +1012,7 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { } else { $history = _forum_user_last_visit($topic->nid); - $topic->new_replies = comment_num_new($topic->nid, $history); + $topic->new_replies = comment_num_new('node', $topic->nid, 'comment_node_forum', $history); $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history); } } @@ -1232,7 +1238,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' => $original_title)); - $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->comment_count, $topic->new_replies, $topic), 'fragment' => 'new')); + $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->comment_count, $topic->new_replies, $topic, 'comment_node_forum'), 'fragment' => 'new')); } } @@ -1274,7 +1280,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_ENTITY_HIDDEN) { $variables['icon_class'] = 'closed'; $variables['icon_title'] = t('Closed topic'); } @@ -1365,14 +1371,14 @@ function _forum_get_topic_order($sortby) { * The ID of the node to update. */ function _forum_update_forum_index($nid) { - $count = db_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 = db_query("SELECT COUNT(cid) FROM {comment} c INNER JOIN {forum_index} i ON c.entity_id = i.nid AND c.entity_type = 'node' AND c.field_name = 'comment_node_forum' WHERE c.entity_id = :nid AND c.status = :status", array( ':nid' => $nid, ':status' => COMMENT_PUBLISHED, ))->fetchField(); if ($count > 0) { // Comments exist. - $last_reply = db_query_range('SELECT cid, name, created, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array( + $last_reply = db_query_range("SELECT cid, name, created, uid FROM {comment} WHERE entity_type = 'node' AND field_name = 'comment_node_forum' AND entity_id = :nid 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 e121cb8..0b886a5 100644 --- a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php +++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php @@ -109,7 +109,9 @@ function testActiveForumTopicsBlock() { // Get the node from the topic title. $node = $this->drupalGetNodeByTitle($topics[$index]); $comment = entity_create('comment', array( - 'nid' => $node->nid, + 'entity_id' => $node->nid, + 'field_name' => 'comment_node_forum', + 'entity_type' => 'node', 'subject' => $this->randomString(20), 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => $this->randomString(256)), 'created' => $timestamp + $index, diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/field/HistoryUserTimestamp.php b/core/modules/node/lib/Drupal/node/Plugin/views/field/HistoryUserTimestamp.php index 3617bc6..feccfda 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/views/field/HistoryUserTimestamp.php +++ b/core/modules/node/lib/Drupal/node/Plugin/views/field/HistoryUserTimestamp.php @@ -33,7 +33,7 @@ public function init(ViewExecutable $view, &$options) { $this->additional_fields['created'] = array('table' => 'node', 'field' => 'created'); $this->additional_fields['changed'] = array('table' => 'node', '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/node/lib/Drupal/node/Plugin/views/filter/HistoryUserTimestamp.php b/core/modules/node/lib/Drupal/node/Plugin/views/filter/HistoryUserTimestamp.php index 57cb781..aa124df 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/views/filter/HistoryUserTimestamp.php +++ b/core/modules/node/lib/Drupal/node/Plugin/views/filter/HistoryUserTimestamp.php @@ -79,9 +79,9 @@ public function query() { $clause = ''; $clause2 = ''; if (module_exists('comment')) { - $ncs = $this->query->ensure_table('node_comment_statistics', $this->relationship); - $clause = ("OR $ncs.last_comment_timestamp > (***CURRENT_TIME*** - $limit)"); - $clause2 = "OR $field < $ncs.last_comment_timestamp"; + $ces = $this->query->ensure_table('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/Tests/NodeAccessPagerTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php index bf8f845..e5c8833 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php @@ -33,6 +33,7 @@ public function setUp() { parent::setUp(); node_access_rebuild(); + comment_add_default_comment_field('node', 'page'); $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'node test view')); } @@ -41,12 +42,18 @@ public function setUp() { */ public function testCommentPager() { // Create a node. - $node = $this->drupalCreateNode(); + $node = $this->drupalCreateNode( + array('comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + )) + ); // Create 60 comments. for ($i = 0; $i < 60; $i++) { $comment = entity_create('comment', array( - 'nid' => $node->nid, + 'entity_id' => $node->nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'subject' => $this->randomName(), 'comment_body' => array( LANGUAGE_NOT_SPECIFIED => array( diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php index e657d76..662c042 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); + comment_add_default_comment_field('node', 'page'); } /** @@ -45,6 +46,9 @@ function testNodeTitle() { $settings = array( 'title' => $this->randomName(8), 'promote' => 1, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ) ); $node = $this->drupalCreateNode($settings); @@ -54,7 +58,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->nid"); + $this->drupalGet("comment/reply/node/" . $node->nid . "/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/node.install b/core/modules/node/node.install index c6fd7e6..0d8083d 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -79,12 +79,6 @@ function node_schema() { 'not null' => TRUE, 'default' => 0, ), - 'comment' => array( - 'description' => 'Whether comments are allowed on this node: 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 should be displayed on the front page.', 'type' => 'int', @@ -245,12 +239,6 @@ function node_schema() { 'not null' => TRUE, 'default' => 1, ), - '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', diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php index fc883cb..289a7b2 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php @@ -50,6 +50,10 @@ public function setUp() { 'post comments' => TRUE, 'skip comment approval' => TRUE, )); + + // Create comment field on article. + comment_add_default_comment_field('node', 'article'); + // Allows anonymous to leave their contact information. $this->setCommentAnonymous(COMMENT_ANONYMOUS_MAY_CONTACT); $this->setCommentPreview(DRUPAL_OPTIONAL); @@ -59,8 +63,8 @@ public function setUp() { // Creates the nodes on which the test comments will be posted. $this->drupalLogin($this->web_user); - $this->node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $this->node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $this->node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); + $this->node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); $this->drupalLogout(); } @@ -156,7 +160,7 @@ public function testCommentReplyOfRdfaMarkup() { $this->assertFalse($result, 'No RDFa markup referring to the comment itself is present.'); // Posts a reply to the first comment. - $this->drupalGet('comment/reply/' . $this->node1->nid . '/' . $comments[0]->id); + $this->drupalGet('comment/reply/node/' . $this->node1->nid . '/comment/' . $comments[0]->id); $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); // Tests the reply_of relationship of a second level comment. diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php index b7771c6..65460fa 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/TrackerAttributesTest.php @@ -42,6 +42,8 @@ function setUp() { 'post comments' => TRUE, 'skip comment approval' => TRUE, )); + // Create comment field on article. + comment_add_default_comment_field('node', 'article'); } /** @@ -52,9 +54,9 @@ function setUp() { */ function testAttributesInTracker() { // Create node as anonymous user. - $node_anon = $this->drupalCreateNode(array('type' => 'article', 'uid' => 0)); + $node_anon = $this->drupalCreateNode(array('type' => 'article', 'uid' => 0, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); // Create node as admin user. - $node_admin = $this->drupalCreateNode(array('type' => 'article', 'uid' => 1)); + $node_admin = $this->drupalCreateNode(array('type' => 'article', 'uid' => 1, 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); // Pass both the anonymously posted node and the administrator posted node // through to test for the RDF attributes. @@ -119,7 +121,7 @@ function _testBasicTrackerRdfaMarkup(Node $node) { 'subject' => $this->randomName(), 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(), ); - $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->nid .'/comment', $comment, t('Save')); $this->drupalGet('tracker'); // Tests whether the property has been set for number of comments. diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 2e04773..a6aac55 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -429,7 +429,9 @@ function rdf_comment_load($comments) { // isn't needed until rdf_preprocess_comment() is called, but set it here // to optimize performance for websites that implement an entity cache. $comment->rdf_data['date'] = rdf_rdfa_attributes($comment->rdf_mapping['created'], $comment->created); - $comment->rdf_data['nid_uri'] = url('node/' . $comment->nid); + $entity = entity_load($comment->entity_type, $comment->entity_id); + $uri = $entity->uri(); + $comment->rdf_data['entity_uri'] = url($uri['path'], $uri['options']); if ($comment->pid) { $comment->rdf_data['pid_uri'] = url('comment/' . $comment->pid, array('fragment' => 'comment-' . $comment->pid)); } @@ -554,19 +556,23 @@ function rdf_preprocess_node(&$variables) { } // Adds RDFa markup annotating the number of comments a node has. - if (isset($variables['node']->comment_count) && !empty($variables['node']->rdf_mapping['comment_count']['predicates'])) { - // Annotates the 'x comments' link in teaser view. - if (isset($variables['content']['links']['comment']['#links']['comment-comments'])) { - $comment_count_attributes['property'] = $variables['node']->rdf_mapping['comment_count']['predicates']; - $comment_count_attributes['content'] = $variables['node']->comment_count; - $comment_count_attributes['datatype'] = $variables['node']->rdf_mapping['comment_count']['datatype']; - // 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; + if (isset($variables['node']->comment_statistics) && !empty($variables['node']->rdf_mapping['comment_count']['predicates'])) { + $count = 0; + foreach ($variables['node']->comment_statistics as $field_name => $statistics) { + $count += $statistics->comment_count; + // Annotates the 'x comments' link in teaser view. + if (isset($variables['content']['links']['comment__' . $field_name]['#links']['comment-comments'])) { + $comment_count_attributes['property'] = $variables['node']->rdf_mapping['comment_count']['predicates']; + $comment_count_attributes['content'] = $statistics->comment_count; + $comment_count_attributes['datatype'] = $variables['node']->rdf_mapping['comment_count']['datatype']; + // 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.tpl.php so it is expressed in RDFa in the tag of the HTML @@ -577,7 +583,7 @@ function rdf_preprocess_node(&$variables) { '#attributes' => array( 'about' => $variables['node_url'], 'property' => $variables['node']->rdf_mapping['comment_count']['predicates'], - 'content' => $variables['node']->comment_count, + 'content' => $count, 'datatype' => $variables['node']->rdf_mapping['comment_count']['datatype'], ), ); @@ -743,15 +749,15 @@ function rdf_preprocess_comment(&$variables) { $variables['title_attributes']['datatype'] = ''; } - // Annotates the parent relationship between the current comment and the node - // it belongs to. If available, the parent comment is also annotated. + // Annotates the parent relationship between the current comment and the + // entity it belongs to. If available, the parent comment is also annotated. if (!empty($comment->rdf_mapping['pid'])) { - // Adds the relation to the parent node. - $parent_node_attributes['rel'] = $comment->rdf_mapping['pid']['predicates']; - // The parent node URI is precomputed as part of the rdf_data so that it can + // Adds the relation to the parent entity. + $parent_entity_attributes['rel'] = $comment->rdf_mapping['pid']['predicates']; + // The parent entity 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; + $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 != 0) { diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php index 338174d..9f3da36 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php @@ -46,8 +46,14 @@ function setUp() { // Create searching user. $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'skip comment approval')); + // Add a comment field. + comment_add_default_comment_field('node', 'article'); // Create initial nodes. - $node_params = array('type' => 'article', 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'SearchCommentToggleTestCase')))); + $node_params = array( + 'type' => 'article', + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'SearchCommentToggleTestCase'))), + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))) + ); $this->searchable_nodes['1 comment'] = $this->drupalCreateNode($node_params); $this->searchable_nodes['0 comments'] = $this->drupalCreateNode($node_params); @@ -63,7 +69,7 @@ function setUp() { $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][format]'] = $filtered_html_format_id; // Post comment to the test node with comment - $this->drupalPost('comment/reply/' . $this->searchable_nodes['1 comment']->nid, $edit_comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $this->searchable_nodes['1 comment']->nid . '/comment', $edit_comment, t('Save')); // First update the index. This does the initial processing. node_update_index(); @@ -89,9 +95,13 @@ 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']->comment = array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_CLOSED)) + ); node_save($this->searchable_nodes['0 comments']); - $this->searchable_nodes['1 comment']->comment = COMMENT_NODE_CLOSED; + $this->searchable_nodes['1 comment']->comment = array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_CLOSED)) + ); node_save($this->searchable_nodes['1 comment']); $this->drupalPost('', $edit, t('Search')); @@ -99,9 +109,13 @@ 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']->comment = array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_ENTITY_HIDDEN)) + );; node_save($this->searchable_nodes['0 comments']); - $this->searchable_nodes['1 comment']->comment = COMMENT_NODE_HIDDEN; + $this->searchable_nodes['1 comment']->comment = array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_ENTITY_HIDDEN)) + );; node_save($this->searchable_nodes['1 comment']); $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 d6c25b1..655adad 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php @@ -47,6 +47,8 @@ function setUp() { ); $this->admin_user = $this->drupalCreateUser($permissions); $this->drupalLogin($this->admin_user); + // Add a comment field. + comment_add_default_comment_field('node', 'article'); } /** @@ -55,7 +57,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']['comment']['comment_preview'] = DRUPAL_OPTIONAL; + field_update_instance($instance); // Enable check_plain() for 'Filtered HTML' text format. $filtered_html_format_id = 'filtered_html'; $edit = array( @@ -71,14 +76,21 @@ function testSearchResultsComment() { $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); // Create a node. - $node = $this->drupalCreateNode(array('type' => 'article')); + $node = $this->drupalCreateNode(array( + 'type' => 'article', + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_OPEN) + ) + ) + )); // Post a comment using 'Full HTML' text format. $edit_comment = array(); $edit_comment['subject'] = 'Test comment subject'; $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]'] = '

' . $comment_body . '

'; $full_html_format_id = 'full_html'; $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][format]'] = $full_html_format_id; - $this->drupalPost('comment/reply/' . $node->nid, $edit_comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->nid .'/comment', $edit_comment, t('Save')); // Invoke search index update. $this->drupalLogout(); @@ -130,14 +142,24 @@ function testSearchResultsCommentAccess() { $this->admin_role = key($this->admin_role); // Create a node. - variable_set('comment_preview_article', DRUPAL_OPTIONAL); - $this->node = $this->drupalCreateNode(array('type' => 'article')); + // Make preview optional. + $instance = field_info_instance('node', 'comment', 'article'); + $instance['settings']['comment']['comment_preview'] = DRUPAL_OPTIONAL; + field_update_instance($instance); + $this->node = $this->drupalCreateNode(array( + 'type' => 'article', + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_OPEN) + ) + ) + )); // Post a comment using 'Full HTML' text format. $edit_comment = array(); $edit_comment['subject'] = $this->comment_subject; $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]'] = '

' . $comment_body . '

'; - $this->drupalPost('comment/reply/' . $this->node->nid, $edit_comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $this->node->nid . '/comment', $edit_comment, t('Save')); $this->drupalLogout(); $this->setRolePermissions(DRUPAL_ANONYMOUS_RID); @@ -155,6 +177,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->nid); $this->setRolePermissions($this->admin_role, TRUE); $this->assertCommentAccess(TRUE, 'Admin user has search permission and access comments permission, comments should be indexed'); @@ -222,6 +245,11 @@ function testAddNewComment() { 'type' => 'article', 'title' => 'short title', 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'short body text'))), + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_OPEN) + ) + ) ); $user = $this->drupalCreateUser(array('search content', 'create article content', 'access content')); diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php index a57e483..f9b913f 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php @@ -27,6 +27,8 @@ public static function getInfo() { function testRankings() { // Login with sufficient privileges. $this->drupalLogin($this->drupalCreateUser(array('post comments', 'skip comment approval', 'create page content'))); + // Add a comment field. + comment_add_default_comment_field('node', 'page'); // Build a list of the rankings to test. $node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views'); @@ -35,6 +37,9 @@ function testRankings() { foreach ($node_ranks as $node_rank) { $settings = array( 'type' => 'page', + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array( + 'comment' => COMMENT_ENTITY_HIDDEN + ))), 'title' => 'Drupal rocks', 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => "Drupal's search rocks"))), ); @@ -52,7 +57,7 @@ function testRankings() { $settings['created'] = REQUEST_TIME + 3600; break; case 'comments': - $settings['comment'] = 2; + $settings['comment'][LANGUAGE_NOT_SPECIFIED][0]['comment'] = COMMENT_OPEN; break; } } @@ -71,7 +76,7 @@ function testRankings() { $edit = array(); $edit['subject'] = 'my comment title'; $edit['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]'] = 'some random comment'; - $this->drupalGet('comment/reply/' . $nodes['comments'][1]->nid); + $this->drupalGet('comment/reply/node/' . $nodes['comments'][1]->nid . '/comment'); $this->drupalPost(NULL, $edit, t('Preview')); $this->drupalPost(NULL, $edit, t('Save')); @@ -122,6 +127,7 @@ function testHTMLRankings() { // Shuffle tags to ensure HTML tags are ranked properly. shuffle($shuffled_tags); + $settings = array( 'type' => 'page', 'title' => 'Simple node', diff --git a/core/modules/search/search.module b/core/modules/search/search.module index a32b5c3..177d4a4 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -278,7 +278,12 @@ function search_get_info($all = FALSE) { } // Return only modules that are set to active in search settings. - return array_intersect_key($search_hooks, array_flip(config('search.settings')->get('active_modules'))); + $active = config('search.settings')->get('active_modules'); + if (empty($active)) { + // No active search modules. + return array(); + } + return array_intersect_key($search_hooks, array_flip($active)); } /** @@ -832,7 +837,9 @@ function search_node_update(Node $node) { */ function search_comment_insert($comment) { // Reindex the node when comments are added. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** @@ -840,7 +847,9 @@ function search_comment_insert($comment) { */ function search_comment_update($comment) { // Reindex the node when comments are changed. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** @@ -848,7 +857,9 @@ function search_comment_update($comment) { */ function search_comment_delete($comment) { // Reindex the node when comments are deleted. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** @@ -856,7 +867,9 @@ function search_comment_delete($comment) { */ function search_comment_publish($comment) { // Reindex the node when comments are published. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** @@ -864,7 +877,9 @@ function search_comment_publish($comment) { */ function search_comment_unpublish($comment) { // Reindex the node when comments are unpublished. - search_touch_node($comment->nid); + if ($comment->entity_type == 'node') { + search_touch_node($comment->entity_id); + } } /** diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 7d14ca0..49c4d0f 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -205,7 +205,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. @@ -241,7 +241,9 @@ protected function drupalCreateNode(array $settings = array()) { // Add in comment settings for nodes. if (module_exists('comment')) { $settings += array( - 'comment' => COMMENT_NODE_OPEN, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_OPEN) + )), ); } 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 4afb574..8198785 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php @@ -69,12 +69,19 @@ protected function assertHookMessageOrder($messages) { * Tests hook invocations for CRUD operations on comments. */ public function testCommentHooks() { + comment_add_default_comment_field('node', 'article', 'comment', COMMENT_OPEN); $node = entity_create('node', array( 'uid' => 1, 'type' => 'article', 'title' => 'Test node', 'status' => 1, - 'comment' => 2, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array( + array( + 'comment' => COMMENT_OPEN + ) + ) + ), 'promote' => 0, 'sticky' => 0, 'langcode' => LANGUAGE_NOT_SPECIFIED, @@ -87,7 +94,9 @@ public function testCommentHooks() { $comment = entity_create('comment', array( 'cid' => NULL, 'pid' => 0, - 'nid' => $nid, + 'entity_id' => $nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'uid' => 1, 'subject' => 'Test comment', 'created' => REQUEST_TIME, 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 a77218b..4f3078d 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php @@ -139,8 +139,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 cb56a90..b5da813 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php @@ -170,28 +170,39 @@ 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, 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 disabled. + $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, and check that taxonomy and comment now can + // also be uninstalled. $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 comment, should now be installed but disabled. + $edit = array('modules[Core][comment][enable]' => FALSE); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('comment'), FALSE); + // Now uninstall it. $edit = array('uninstall[comment]' => 'comment'); $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 taxonomy, should now be installed but disabled. + $edit = array('modules[Core][taxonomy][enable]' => FALSE); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('taxonomy'), FALSE); + // Now uninstall it. + $edit = array('uninstall[taxonomy]' => 'comment'); + $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.'); } } 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 27f3e6a..ebec2ef 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() { )); taxonomy_term_save($this->term); + // Add a comment field. + comment_add_default_comment_field('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->nid, - 'node_type' => $this->node->type, + 'entity_id' => $this->node->nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'status' => COMMENT_PUBLISHED, 'subject' => $this->xss_label, 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => 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..2e8e3c9 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/CommentUpgradePathTest.php @@ -0,0 +1,58 @@ + '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'); + $node = node_load(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.'); + + // Check one instance exists for each node type. + $types = node_type_get_types(); + foreach (array_keys($types) as $type) { + $instance = field_info_instance('node', 'comment_node_' . $type, $type); + $this->assertTrue($instance, format_string('Comment field found for the %type node type', array( + '%type' => $type + ))); + } + + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php index 73f563b..cfc542b 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php @@ -47,6 +47,7 @@ public function testLanguageUpgrade() { // Check that both comments display on the node. $this->drupalGet('node/50'); + $node = node_load(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.'); diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php index 1aa144e..5d444a6 100644 --- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php +++ b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerNodeAccessTest.php @@ -35,6 +35,7 @@ public static function getInfo() { public function setUp() { parent::setUp(); node_access_rebuild(); + comment_add_default_comment_field('node', 'page', 'comment', COMMENT_OPEN); 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 189b927..395633f 100644 --- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php +++ b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php @@ -51,9 +51,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); + comment_add_default_comment_field('node', 'page'); } /** @@ -65,10 +63,16 @@ function testTrackerAll() { $unpublished = $this->drupalCreateNode(array( 'title' => $this->randomName(8), 'status' => 0, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), )); $published = $this->drupalCreateNode(array( 'title' => $this->randomName(8), 'status' => 1, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), )); $this->drupalGet('tracker'); @@ -92,11 +96,17 @@ function testTrackerUser() { 'title' => $this->randomName(8), 'uid' => $this->user->uid, 'status' => 0, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), )); $my_published = $this->drupalCreateNode(array( 'title' => $this->randomName(8), 'uid' => $this->user->uid, 'status' => 1, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), )); $other_published_no_comment = $this->drupalCreateNode(array( 'title' => $this->randomName(8), @@ -107,12 +117,15 @@ function testTrackerUser() { 'title' => $this->randomName(8), 'uid' => $this->other_user->uid, 'status' => 1, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), )); $comment = array( 'subject' => $this->randomName(), 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), ); - $this->drupalPost('comment/reply/' . $other_published_my_comment->nid, $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $other_published_my_comment->nid . '/comment', $comment, t('Save')); $this->drupalGet('user/' . $this->user->uid . '/track'); $this->assertNoText($unpublished->label(), "Unpublished nodes do not show up in the users's tracker listing."); @@ -163,7 +176,9 @@ function testTrackerNewComments() { $this->drupalLogin($this->user); $node = $this->drupalCreateNode(array( - 'comment' => 2, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array( + 'comment' => COMMENT_OPEN + ))), 'title' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(8)))), )); @@ -173,7 +188,7 @@ function testTrackerNewComments() { 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), ); // The new comment is automatically viewed by the current user. - $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->nid . '/comment', $comment, t('Save')); $this->drupalLogin($this->other_user); $this->drupalGet('tracker'); @@ -188,7 +203,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->nid, $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $node->nid . '/comment', $comment, t('Save')); $this->drupalLogin($this->user); $this->drupalGet('tracker'); @@ -206,7 +221,9 @@ function testTrackerCronIndexing() { $nodes = array(); for ($i = 1; $i <= 3; $i++) { $edits[$i] = array( - 'comment' => 2, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array( + 'comment' => COMMENT_OPEN + ))), 'title' => $this->randomName(), ); $nodes[$i] = $this->drupalCreateNode($edits[$i]); @@ -218,7 +235,7 @@ function testTrackerCronIndexing() { 'subject' => $this->randomName(), 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $this->randomName(20), ); - $this->drupalPost('comment/reply/' . $nodes[3]->nid, $comment, t('Save')); + $this->drupalPost('comment/reply/node/' . $nodes[3]->nid . '/comment', $comment, t('Save')); // Start indexing backwards from node 3. variable_set('tracker_index_nid', 3); diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module index 92f5701..9a01b6d 100644 --- a/core/modules/tracker/tracker.module +++ b/core/modules/tracker/tracker.module @@ -122,10 +122,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); @@ -228,8 +230,8 @@ function tracker_node_predelete(Node $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 != COMMENT_PUBLISHED) { - _tracker_remove($comment->nid, $comment->uid, $comment->changed); + if ($comment->status != COMMENT_PUBLISHED && $comment->entity_type == 'node') { + _tracker_remove($comment->entity_id, $comment->uid, $comment->changed); } } @@ -240,21 +242,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, $comment->uid, $comment->changed); + if ($comment->entity_type == 'node') { + _tracker_add($comment->entity_id, $comment->uid, $comment->changed); + } } /** * Implements hook_comment_unpublish(). */ function tracker_comment_unpublish($comment) { - _tracker_remove($comment->nid, $comment->uid, $comment->changed); + if ($comment->entity_type == 'node') { + _tracker_remove($comment->entity_id, $comment->uid, $comment->changed); + } } /** * Implements hook_comment_delete(). */ function tracker_comment_delete($comment) { - _tracker_remove($comment->nid, $comment->uid, $comment->changed); + if ($comment->entity_type == 'node') { + _tracker_remove($comment->entity_id, $comment->uid, $comment->changed); + } } /** @@ -308,7 +316,7 @@ function _tracker_add($nid, $uid, $changed) { */ function _tracker_calculate_changed($nid) { $changed = db_query('SELECT changed FROM {node} WHERE nid = :nid', 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(); @@ -343,7 +351,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 853f50d..9b8a577 100644 --- a/core/modules/tracker/tracker.pages.inc +++ b/core/modules/tracker/tracker.pages.inc @@ -50,7 +50,7 @@ function tracker_page($account = NULL, $set_title = FALSE) { $rows = array(); if (!empty($nodes)) { // Now, get the data and put into the placeholder array. - $result = db_query('SELECT n.nid, n.title, n.type, n.changed, n.uid, u.name, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.nid IN (:nids)', array(':nids' => array_keys($nodes)), array('target' => 'slave')); + $result = db_query("SELECT n.nid, n.title, n.type, n.changed, n.uid, u.name, SUM(l.comment_count) AS comment_count FROM {node} 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) GROUP BY 1, 2, 3, 4, 5, 6", array(':nids' => array_keys($nodes)), array('target' => 'slave')); foreach ($result as $node) { $node->last_activity = $nodes[$node->nid]->changed; $nodes[$node->nid] = $node; @@ -63,7 +63,7 @@ function tracker_page($account = NULL, $set_title = FALSE) { if ($node->comment_count) { $comments = $node->comment_count; - if ($new = comment_num_new($node->nid)) { + if ($new = comment_num_new($node->nid, 'node')) { $comments .= '
'; $comments .= l(format_plural($new, '1 new', '@count new'), 'node/' . $node->nid, 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 8b5db87..b9128e2 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php @@ -32,6 +32,14 @@ public static function getInfo() { } /** + * Setup the test. + */ + public function setup() { + parent::setup(); + comment_add_default_comment_field('node', 'page'); + } + + /** * Attempt to cancel account without permission. */ function testUserCancelWithoutPermission() { @@ -42,9 +50,17 @@ function testUserCancelWithoutPermission() { $this->drupalLogin($account); // Load real user object. $account = user_load($account->uid, TRUE); + comment_add_default_comment_field('node', 'page'); // Create a node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_CLOSED) + ) + ) + )); // Attempt to cancel account. $this->drupalGet('user/' . $account->uid . '/edit'); @@ -114,7 +130,10 @@ function testUserCancelInvalid() { $account = user_load($account->uid, TRUE); // Create a node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))) + )); // Attempt to cancel account. $this->drupalPost('user/' . $account->uid . '/edit', NULL, t('Cancel account')); @@ -191,7 +210,10 @@ function testUserBlockUnpublish() { $account = user_load($account->uid, TRUE); // Create a node with two revisions. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))) + )); $settings = get_object_vars($node); $settings['revision'] = 1; $node = $this->drupalCreateNode($settings); @@ -235,11 +257,17 @@ function testUserAnonymize() { $account = user_load($account->uid, TRUE); // Create a simple node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))) + )); // Create a node with two revisions, the initial one belonging to the // cancelling user. - $revision_node = $this->drupalCreateNode(array('uid' => $account->uid)); + $revision_node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))) + )); $revision = $revision_node->vid; $settings = get_object_vars($revision_node); $settings['revision'] = 1; @@ -286,7 +314,14 @@ function testUserDelete() { $account = user_load($account->uid, TRUE); // Create a simple node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('comment' => COMMENT_OPEN) + ) + ) + )); // Create comment. $langcode = LANGUAGE_NOT_SPECIFIED; @@ -294,7 +329,7 @@ function testUserDelete() { $edit['subject'] = $this->randomName(8); $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); + $this->drupalPost('comment/reply/node/' . $node->nid . '/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'])); @@ -303,7 +338,10 @@ function testUserDelete() { // Create a node with two revisions, the initial one belonging to the // cancelling user. - $revision_node = $this->drupalCreateNode(array('uid' => $account->uid)); + $revision_node = $this->drupalCreateNode(array( + 'uid' => $account->uid, + 'comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))) + )); $revision = $revision_node->vid; $settings = get_object_vars($revision_node); $settings['revision'] = 1; diff --git a/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php index 9ed88ca..4eb2944 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php @@ -37,6 +37,8 @@ function setUp() { // Create Basic page node type. $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + // Add a comment field. + comment_add_default_comment_field('node', 'page'); // Prefetch and create text formats. $this->plain_text_format = filter_format_load('plain_text'); @@ -75,8 +77,8 @@ function setUp() { * upon display. */ function testUserSignature() { - // Create a new node with comments on. - $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN)); + // Create a new node with comments on (default). + $node = $this->drupalCreateNode(array('comment' => array(LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN))))); // Verify that user signature field is not displayed on registration form. $this->drupalGet('user/register'); @@ -100,7 +102,7 @@ function testUserSignature() { $edit = array(); $edit['subject'] = $this->randomName(8); $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); + $this->drupalPost('comment/reply/node/' . $node->nid .'/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 6e86a0c..edbb70b 100644 --- a/core/modules/views/config/views.view.comments_recent.yml +++ b/core/modules/views/config/views.view.comments_recent.yml @@ -31,10 +31,11 @@ display: options: items_per_page: 5 relationships: - nid: - id: nid + node: + field: node + id: node + required: '1' table: comment - field: nid fields: subject: id: subject @@ -59,7 +60,7 @@ display: id: status_extra table: node field: status_extra - relationship: nid + relationship: node group: 0 style: type: html_list @@ -85,7 +86,7 @@ display: id: title table: node field: title - relationship: nid + relationship: node label: 'Reply to' link_to_node: 1 timestamp: diff --git a/core/modules/views/config/views.view.tracker.yml b/core/modules/views/config/views.view.tracker.yml index 5d31e2f..16f1e03 100644 --- a/core/modules/views/config/views.view.tracker.yml +++ b/core/modules/views/config/views.view.tracker.yml @@ -51,12 +51,12 @@ display: label: Author comment_count: id: comment_count - table: node_comment_statistics + table: comment_entity_statistics field: comment_count label: Replies last_comment_timestamp: id: last_comment_timestamp - table: node_comment_statistics + table: comment_entity_statistics field: last_comment_timestamp label: 'Last Post' timestamp: @@ -77,7 +77,7 @@ display: sorts: last_comment_timestamp: id: last_comment_timestamp - table: node_comment_statistics + table: comment_entity_statistics field: last_comment_timestamp arguments: uid_touch: diff --git a/core/modules/views/lib/Drupal/views/Tests/Comment/CommentTestBase.php b/core/modules/views/lib/Drupal/views/Tests/Comment/CommentTestBase.php index a855afe..980bd2d 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Comment/CommentTestBase.php +++ b/core/modules/views/lib/Drupal/views/Tests/Comment/CommentTestBase.php @@ -30,12 +30,25 @@ function setUp() { $this->account2 = $this->drupalCreateUser(); $this->drupalLogin($this->account); - $this->node_user_posted = $this->drupalCreateNode(); - $this->node_user_commented = $this->drupalCreateNode(array('uid' => $this->account2->uid)); + comment_add_default_comment_field('node', 'page'); + + $this->node_user_posted = $this->drupalCreateNode(array( + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), + )); + $this->node_user_commented = $this->drupalCreateNode(array( + 'uid' => $this->account2->uid, + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), + )); $comment = array( 'uid' => $this->loggedInUser->uid, - 'nid' => $this->node_user_commented->nid, + 'entity_id' => $this->node_user_commented->nid, + 'entity_type' => 'node', + 'field_name' => 'comment', 'cid' => '', 'pid' => '', ); diff --git a/core/modules/views/lib/Drupal/views/Tests/Comment/DefaultViewRecentComments.php b/core/modules/views/lib/Drupal/views/Tests/Comment/DefaultViewRecentComments.php index 73ddfe0..92c6c76 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Comment/DefaultViewRecentComments.php +++ b/core/modules/views/lib/Drupal/views/Tests/Comment/DefaultViewRecentComments.php @@ -68,20 +68,28 @@ public function setUp() { // Create a new content type $content_type = $this->drupalCreateContentType(); + $language_not_specified = LANGUAGE_NOT_SPECIFIED; // Add a node of the new content type. $node_data = array( 'type' => $content_type->type, + "comment[$language_not_specified][0][comment]" => COMMENT_OPEN ); + comment_add_default_comment_field('node', $content_type->type); $this->node = $this->drupalCreateNode($node_data); + views_invalidate_cache(); + // Create some comments and attach them to the created node. for ($i = 0; $i < $this->masterDisplayResults; $i++) { $comment = entity_create('comment', array()); $comment->uid = 0; - $comment->nid = $this->node->nid; + $comment->entity_type = 'node'; + // Stagger the comments so the timestamp sorting works. + $comment->created = REQUEST_TIME - $i; + $comment->field_name = 'comment'; + $comment->entity_id = $this->node->nid; $comment->subject = 'Test comment ' . $i; - $comment->node_type = 'comment_node_' . $this->node->type; $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0]['value'] = 'Test body ' . $i; $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0]['format'] = 'full_html'; @@ -101,14 +109,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; + $expected_result[$key]['entity_id'] = $comment->entity_id; $expected_result[$key]['subject'] = $comment->subject; $expected_result[$key]['cid'] = $comment->cid; $expected_result[$key]['changed'] = $comment->changed; @@ -132,7 +140,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', @@ -140,7 +148,7 @@ public function testPageDisplay() { ); $expected_result = array(); foreach (array_values($this->commentsCreated) as $key => $comment) { - $expected_result[$key]['nid'] = $comment->nid; + $expected_result[$key]['entity_id'] = $comment->entity_id; $expected_result[$key]['subject'] = $comment->subject; $expected_result[$key]['changed'] = $comment->changed; $expected_result[$key]['created'] = $comment->created; diff --git a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php index 7e37452..5999c31 100644 --- a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php @@ -88,12 +88,15 @@ protected function setUp() { // Create a time in the past for the archive. $time = time() - 3600; + comment_add_default_comment_field('node', 'page'); + for ($i = 0; $i <= 10; $i++) { $user = $this->drupalCreateUser(); $term = $this->createTerm($this->vocabulary); $values = array('created' => $time, 'type' => 'page'); $values[$this->field_name][LANGUAGE_NOT_SPECIFIED][]['tid'] = $term->tid; + $values['comment'][LANGUAGE_NOT_SPECIFIED][]['comment'] = COMMENT_OPEN; // Make every other node promoted. if ($i % 2) { @@ -107,7 +110,9 @@ protected function setUp() { $comment = array( 'uid' => $user->uid, - 'nid' => $node->nid, + 'entity_id' => $node->nid, + '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 a950621..e109dad 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Entity/FieldEntityTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Entity/FieldEntityTest.php @@ -14,7 +14,6 @@ */ class FieldEntityTest extends ViewTestBase { - /** * Modules to enable. * @@ -39,9 +38,21 @@ public function testGetEntity() { $account = entity_create('user', array('name' => $this->randomName(), 'bundle' => 'user')); $account->save(); - $node = entity_create('node', array('uid' => $account->id(), 'type' => 'page')); + comment_add_default_comment_field('node', 'page'); + $node = entity_create('node', array( + 'uid' => $account->id(), + 'type' => 'page', + 'comment' => array( + LANGUAGE_NOT_SPECIFIED => array(array('comment' => COMMENT_OPEN)) + ), + )); $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 85861cd..64db9c0 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerAllTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerAllTest.php @@ -52,6 +52,7 @@ public static function getInfo() { * Tests most of the handlers. */ public function testHandlers() { + comment_add_default_comment_field('node', 'page'); $object_types = array_keys(ViewExecutable::viewsHandlerTypes()); foreach (views_fetch_data() as $base_table => $info) { if (!isset($info['table']['base'])) { @@ -66,7 +67,7 @@ public function testHandlers() { $exclude[] = 'taxonomy_term_data:tid_representative'; $exclude[] = 'users:uid_representative'; - // Go through all fields and there through all handler types. + // Go through all fields and then through all handler types. foreach ($info as $field => $field_info) { // Table is a reserved key for the metainformation. if ($field != 'table' && !in_array("$base_table:$field", $exclude)) { 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 00d6b3e..da498db 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php @@ -33,7 +33,7 @@ public static function getInfo() { protected function setUp() { parent::setUp(); - + comment_add_default_comment_field('node', 'page'); $this->enableViewsTestModule(); } @@ -267,7 +267,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 9fb3fbc..eac0eee 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php @@ -92,6 +92,7 @@ public function testInitMethods() { * Overrides Drupal\views\Tests\ViewTestBase::getBasicView(). */ protected function getBasicView() { + comment_add_default_comment_field('node', 'page'); return $this->createViewFromConfig('test_destroy'); } diff --git a/core/modules/views/tests/views_test_config/config/views.view.test_destroy.yml b/core/modules/views/tests/views_test_config/config/views.view.test_destroy.yml index a28c6b1..711f8bd 100644 --- a/core/modules/views/tests/views_test_config/config/views.view.test_destroy.yml +++ b/core/modules/views/tests/views_test_config/config/views.view.test_destroy.yml @@ -117,29 +117,29 @@ display: query: type: views_query relationships: - cid: - field: cid - id: cid + comment_cid: + field: comment_cid + id: comment_cid table: node pid: field: pid id: pid table: comment - relationship: cid + relationship: comment_cid uid: field: uid id: uid table: comment - relationship: cid + relationship: comment_cid sorts: last_comment_name: field: last_comment_name id: last_comment_name - table: node_comment_statistics + table: comment_entity_statistics last_comment_timestamp: field: last_comment_timestamp id: last_comment_timestamp - table: node_comment_statistics + table: comment_entity_statistics style: type: default row: diff --git a/core/modules/views/tests/views_test_config/config/views.view.test_field_get_entity.yml b/core/modules/views/tests/views_test_config/config/views.view.test_field_get_entity.yml index 5ba2fc9..1d14d56 100644 --- a/core/modules/views/tests/views_test_config/config/views.view.test_field_get_entity.yml +++ b/core/modules/views/tests/views_test_config/config/views.view.test_field_get_entity.yml @@ -21,7 +21,7 @@ display: field: nid id: nid table: node - relationship: nid + relationship: node uid: field: uid id: uid @@ -36,9 +36,9 @@ display: query: type: views_query relationships: - nid: - field: nid - id: nid + node: + field: node + id: node required: '1' table: comment uid: @@ -47,7 +47,7 @@ display: group_type: group id: uid label: author - relationship: nid + relationship: node required: '0' table: node sorts: { } diff --git a/core/modules/views/tests/views_test_config/config/views.view.test_handler_relationships.yml b/core/modules/views/tests/views_test_config/config/views.view.test_handler_relationships.yml index 409fb2c..ab03b57 100644 --- a/core/modules/views/tests/views_test_config/config/views.view.test_handler_relationships.yml +++ b/core/modules/views/tests/views_test_config/config/views.view.test_handler_relationships.yml @@ -12,15 +12,15 @@ display: table: node field: title relationships: - cid: - id: cid + comment_cid: + id: comment_cid table: node - field: cid + field: comment_cid nid: id: nid table: comment - field: nid - relationship: cid + field: node + relationship: comment_cid display_plugin: default display_title: Master id: default diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index c063a6d..3c5cba4 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -246,9 +246,11 @@ function standard_install() { rdf_mapping_save($rdf_mapping); } - // Default "Basic page" to not be promoted and have comments disabled. + // Default "Basic page" to not be promoted. variable_set('node_options_page', array('status')); - variable_set('comment_page', COMMENT_NODE_HIDDEN); + + // Add comment field to article node type. + comment_add_default_comment_field('node', 'article', 'comment', COMMENT_OPEN); // Don't display date and author information for "Basic page" nodes by default. variable_set('node_submitted_page', FALSE); diff --git a/core/themes/bartik/templates/comment-wrapper.tpl.php b/core/themes/bartik/templates/comment-wrapper.tpl.php index 764f91e..b40893c 100644 --- a/core/themes/bartik/templates/comment-wrapper.tpl.php +++ b/core/themes/bartik/templates/comment-wrapper.tpl.php @@ -33,7 +33,7 @@ */ ?>
> - type != 'forum'): ?> + entityType() != 'node' || $entity->bundle() != 'forum')): ?>

diff --git a/core/themes/bartik/templates/comment.tpl.php b/core/themes/bartik/templates/comment.tpl.php index ef525c1..c75616e 100644 --- a/core/themes/bartik/templates/comment.tpl.php +++ b/core/themes/bartik/templates/comment.tpl.php @@ -30,7 +30,8 @@ * It includes the 'class' information, which includes: * - 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. @@ -59,7 +60,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() * @see template_preprocess_comment() diff --git a/core/update.php b/core/update.php index 98296bb..0d1b405 100644 --- a/core/update.php +++ b/core/update.php @@ -461,6 +461,7 @@ function update_check_requirements($skip_warnings = FALSE) { ->addArgument(new Reference('router.dumper')) ->addArgument(new Reference('lock')) ->addArgument(new Reference('dispatcher')); +$container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager'); // Turn error reporting back on. From now on, only fatal errors (which are // not passed through the error handler) will cause a message to be printed.