diff --git a/modules/comment/NOTES.txt b/modules/comment/NOTES.txt index 66ba115..ba3c056 100644 --- a/modules/comment/NOTES.txt +++ b/modules/comment/NOTES.txt @@ -7,13 +7,20 @@ TODO: > Database Schema Index + Drop comment table on uninstall > Core API Comment Entity + CommentController Save / Load / Update / Delete + comment_node_type_delete() + > Hooks Implementations + comment_file_download_access() + comment_form_node_form_alter() + > Pages * comment_enable() @@ -37,6 +44,7 @@ Check: --- * comment_node_search_result($node) * search table name node_comment_statistics +* comment_num_new(). theme('comment_post_forbidden', array('node' => $node)) > theme('comment_post_forbidden', array('entity' => $entity, 'entity_type' => $entity_type)) diff --git a/modules/comment/comment.install b/modules/comment/comment.install index 0213808..c91eb43 100644 --- a/modules/comment/comment.install +++ b/modules/comment/comment.install @@ -12,20 +12,33 @@ function comment_uninstall() { // Delete comment_body field. field_delete_field('comment_body'); - // Remove variables. + // Remove variables & bundles variable_del('comment_block_count'); - $node_types = array_keys(node_type_get_types()); - foreach ($node_types as $node_type) { - field_attach_delete_bundle('comment', 'comment_node_' . $node_type); - variable_del('comment_' . $node_type); - variable_del('comment_anonymous_' . $node_type); - variable_del('comment_controls_' . $node_type); - variable_del('comment_default_mode_' . $node_type); - variable_del('comment_default_order_' . $node_type); - variable_del('comment_default_per_page_' . $node_type); - variable_del('comment_form_location_' . $node_type); - variable_del('comment_preview_' . $node_type); - variable_del('comment_subject_field_' . $node_type); + module_load_include('module', 'comment'); + foreach (comment_get_entity_info() as $entity_type => $entity_info) { + foreach ($entity_info['bundles'] as $bundle_type => $bundle) { + field_attach_delete_bundle('comment', "comment_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_anonymous_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_controls_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_default_mode_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_default_order_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_default_per_page_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_form_location_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_preview_entity_{$entity_type}_{$bundle_type}"); + variable_del("comment_subject_field_entity_{$entity_type}_{$bundle_type}"); + + # @TODO: Drop foreign keys? + # ... + + # @TODO: Drop comment columns in entity tables + try { + db_drop_field($entity_info['base table'], 'comment'); + } + catch (Exception $e) { + // Field created before, we can safely ignore it. + } + } } } @@ -33,19 +46,24 @@ 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'); + foreach (comment_get_entity_info() as $entity_type => $entity_info) { + // Insert records into the comment_entity_statistics for entities that are missing. + $id_key = $entity_info['entity keys']['id']; + $query = db_select($entity_info['base table'], 'e'); + $query->leftJoin('comment_entity_statistics', 'ces', 'ces.entity_id = e.' . $id_key); + $query->addField('e', 'created', 'last_comment_timestamp'); + $query->addField('e', 'uid', 'last_comment_uid'); + $query->addExpression(':node', 'entity_type', array(':node' => 'node')); + $query->addField('e', 'type', 'bundle'); + $query->addField('e', $id_key, 'entity_id'); + $query->addExpression('0', 'comment_count'); + $query->addExpression('NULL', 'last_comment_name'); + $query->isNull('ces.comment_count'); - db_insert('node_comment_statistics') - ->from($query) - ->execute(); + db_insert('comment_entity_statistics') + ->from($query) + ->execute(); + } } /** @@ -69,12 +87,14 @@ function comment_modules_enabled($modules) { // 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(); + // Ensure that the list of entities reflects newly enabled modules. + entity_info_cache_clear(); // Create comment body fields for each node type, if needed. - foreach (node_type_get_types() as $type => $info) { - _comment_body_field_create($info); + foreach (comment_get_entity_info() as $entity_type => $entity_info) { + foreach ($entity_info['bundles'] as $bundle_type => $bundle) { + _comment_body_field_create($entity_type, $bundle_type); + } } } } @@ -97,11 +117,25 @@ 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_type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Type of entity to which this comment is a reply.', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance.', + ), + 'entity_id' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, - 'description' => 'The {node}.nid to which this comment is a reply.', + 'description' => 'The ID of entity to which this comment is a reply.', ), 'uid' => array( 'type' => 'int', @@ -176,18 +210,17 @@ function comment_schema() { ), ), 'indexes' => array( + 'entity_type' => array('entity_type'), + 'bundle' => array('bundle'), + 'entity_id' => array('entity_id'), 'comment_status_pid' => array('pid', 'status'), - 'comment_num_new' => array('nid', 'status', 'created', 'cid', 'thread'), + 'comment_num_new' => array('entity_type', 'entity_id', 'status', 'created', 'cid', 'thread'), 'comment_uid' => array('uid'), - 'comment_nid_language' => array('nid', 'language'), + 'comment_entity_id_language' => array('entity_type', 'entity_id', 'language'), 'comment_created' => array('created'), ), 'primary key' => array('cid'), 'foreign keys' => array( - 'comment_node' => array( - 'table' => 'node', - 'columns' => array('nid' => 'nid'), - ), 'comment_author' => array( 'table' => 'users', 'columns' => array('uid' => 'uid'), @@ -195,15 +228,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_type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The entity type', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance.', + ), + '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.', ), 'cid' => array( 'type' => 'int', @@ -237,17 +284,16 @@ function comment_schema() { 'description' => 'The total number of comments on this node.', ), ), - 'primary key' => array('nid'), + 'primary key' => array('entity_id'), 'indexes' => array( - 'node_comment_timestamp' => array('last_comment_timestamp'), + 'entity_type' => array('entity_type'), + 'bundle' => array('bundle'), + 'entity_id' => array('entity_id'), + 'entity_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( @@ -259,3 +305,94 @@ function comment_schema() { return $schema; } + +/** + * Rename comment/node variable to comment/entity. + * + * comment_article > comment_node_article + * comment_subject_field_article > comment_subject_field_node_article + */ +function comment_update_8000() { + foreach (node_type_get_types() as $node_type) { + $old_keys[] = 'comment_'; + $old_keys[] = 'comment_default_mode_'; + $old_keys[] = 'comment_default_per_page_'; + $old_keys[] = 'comment_anonymous_'; + $old_keys[] = 'comment_subject_field_'; + $old_keys[] = 'comment_form_location_'; + $old_keys[] = 'comment_preview_'; + foreach ($old_keys as $old_key) { + $new_key = "{$old_key}_node_{$node_type->type}"; + $old_key = "{$old_key}_{$node_type->type}"; + $variable = variable_get($old_key); + variable_set($new_key, $variable); + variable_del($old_key); + } + } +} + +/** + * Update table {node_comment_statistics}. + */ +function comment_update_8001() { + # Rename {node_comment_statistics} to {comment_entity_statistics} + db_rename_table('node_comment_statistics', 'comment_entity_statistics'); + + # Add field entity_type + $comment_schema = comment_schema(); + $entity_column = $comment_schema['comment_entity_statistics']['fields']['entity_type']; + db_add_field('comment_entity_statistics', 'entity_type', $entity_column); + + # Add column bundle + $bundle_column = $comment_schema['comment_entity_statistics']['fields']['bundle']; + db_add_field('comment_entity_statistics', 'bundle', $bundle_column); + + # Set entity_type to 'node' + db_update('comment_entity_statistics') + ->fields(array('entity_type' => 'node')) + ->execute(); + + # @TODO: set bundle value = node type + # ... + + # Rename nid to entity_id + $entity_id_column = $comment_schema['comment_entity_statistics']['fields']['entity_id']; + db_change_field('comment_entity_statistics', 'nid', 'entity_id', $entity_id_column); + + # Add indexes + db_add_index('comment_entity_statistics', 'entity_type', array('entity_type')); + db_add_index('comment_entity_statistics', 'bundle', array('bundle')); + db_add_index('comment_entity_statistics', 'entity_id', array('entity_id')); +} + +/** + * Update table {comment} + */ +function comment_update_8002() { + $comment_schema = comment_schema(); + + # Add column entity_type + $entity_type_column = $comment_schema['comment']['fields']['entity_type']; + db_add_field('comment', 'entity_type', $entity_type_column); + + # Add column bundle + $bundle_column = $comment_schema['comment']['fields']['bundle']; + db_add_field('comment', 'bundle', $bundle_column); + + # set entity_type = 'node' to all row + db_update('comment') + ->fields(array('entity_type' => 'node')) + ->execute(); + + # @TODO: set bundle value = node type + # ... + + # rename column nid to entity_id + $entity_id_column = $comment_schema['comment']['fields']['entity_id']; + db_change_field('comment', 'nid', 'entity_id', $entity_id_column); + + # Add indexes + db_add_index('comment', 'entity_type', array('entity_type')); + db_add_index('comment', 'bundle', array('bundle')); + db_add_index('comment', 'entity_id', array('entity_id')); +} diff --git a/modules/comment/comment.module b/modules/comment/comment.module index ae9278c..53c9dbf 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -57,17 +57,17 @@ define('COMMENT_FORM_BELOW', 1); /** * Comments for this node are hidden. */ -define('COMMENT_NODE_HIDDEN', 0); +define('COMMENT_ENTITY_HIDDEN', 0); /** * Comments for this node are closed. */ -define('COMMENT_NODE_CLOSED', 1); +define('COMMENT_ENTITY_CLOSED', 1); /** * Comments for this node are open. */ -define('COMMENT_NODE_OPEN', 2); +define('COMMENT_ENTITY_OPEN', 2); /** * Implements hook_help(). @@ -277,8 +277,9 @@ function comment_menu() { 'file' => 'comment.admin.inc', 'weight' => 2, ); - $items['comment/reply/%node'] = array( + $items['comment/reply/%_comment_entity/%'] = array( 'title' => 'Add new comment', + 'load arguments' => array(3), 'page callback' => 'comment_reply', 'page arguments' => array(2), 'access callback' => 'node_access', @@ -290,6 +291,14 @@ function comment_menu() { } /** + * Entity loader for comment menu items. + */ +function _comment_entity_load($entity_type, $entity_id) { + $entity = entity_load($entity_type, array($entity_id)); + return $entity ? reset($entity) : FALSE; +} + +/** * Implements hook_menu_alter(). */ function comment_menu_alter(&$items) { @@ -351,14 +360,14 @@ function comment_node_type_delete($info) { 'comment_form_location', ); foreach ($settings as $setting) { - variable_del($setting . '_' . $info->type); + variable_del($setting . '_node_' . $info->type); } } /** - * Creates a comment_body field instance for a given node type. + * Creates a comment_body field instance for a given entity bundle. */ -function _comment_body_field_create($info) { +function _comment_body_field_create($entity_type, $bundle_type) { // Create the field if needed. if (!field_read_field('comment_body', array('include_inactive' => TRUE))) { $field = array( @@ -369,14 +378,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); + $bundle_name = "comment_{$entity_type}_{$bundle_type}"; + if (!field_read_instance('comment', 'comment_body', $bundle_name, array('include_inactive' => TRUE))) { + field_attach_create_bundle('comment', $bundle_name); // Attaches the body field by default. $instance = array( 'field_name' => 'comment_body', 'label' => 'Comment', 'entity_type' => 'comment', - 'bundle' => 'comment_node_' . $info->type, + 'bundle' => $bundle_name, 'settings' => array('text_processing' => 1), 'required' => TRUE, 'display' => array( @@ -609,143 +619,166 @@ function theme_comment_block() { } /** - * Implements hook_node_view(). + * @TODO: Implements hook_entity_view(). */ -function comment_node_view($node, $view_mode) { - $links = array(); +function comment_entity_view($entity, $entity_type, $view_mode, $langcode) { + // Entity does not support comment, we do nothing + if (!isset($entity->comment)) { + return; + } - 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', + // Comment is hidden, we do nothing + if ($entity->comment == COMMENT_ENTITY_HIDDEN) { + return; + } + + $entity_info = entity_get_info($entity_type); + $id_key = $entity_info['entity keys']['id']; + $bundle_key = $entity_info['entity keys']['bundle']; + + if ($view_mode === 'rss') { # TODO +// // 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($entity->comment_count)) { + $links['comment-comments'] = array( + 'title' => format_plural($entity->comment_count, '1 comment', '@count comments'), + 'href' => $entity_type . '/' . $entity->{$id_key}, # @TODO: Check this + '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_key})) { + $links['comment-new-comments'] = array( + 'title' => format_plural($new, '1 new comment', '@count new comments'), + 'href' => $entity_type . '/' . $entity->{$id_key}, # @TODO: Check this + 'query' => comment_new_page_count($entity->comment_count, $new, $entity), + 'attributes' => array('title' => t('Jump to the first new comment of this posting.')), + 'fragment' => 'new', '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) { - if (user_access('post comments')) { + } + if ($entity->comment == COMMENT_ENTITY_OPEN) { + if (user_access('post comments')) { + $links['comment-add'] = array( + 'title' => t('Add new comment'), + 'href' => "comment/reply/{$entity_type}/{$entity->nid}", + 'attributes' => array('title' => t('Add a new comment to this page.')), + 'fragment' => 'comment-form', + ); + } + else { + $links['comment-forbidden'] = array( + 'title' => theme('comment_post_forbidden', array('entity' => $entity, 'entity_type' => $entity_type)), + '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 ($entity->comment == COMMENT_ENTITY_OPEN) { + if (user_access('post comments')) { + $comment_form_location = variable_get("comment_form_location_{$entity_type}_" . $entity->{$bundle_key}, COMMENT_FORM_BELOW); + + // 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_count) && user_access('access comments'))) { $links['comment-add'] = array( 'title' => t('Add new comment'), - 'href' => "comment/reply/$node->nid", - 'attributes' => array('title' => t('Add a new comment to this page.')), + 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')), + 'href' => $entity_type . '/' . $entity->{$id_key}, 'fragment' => 'comment-form', ); - } - 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"; - } + if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) { + $links['comment-add']['href'] = "comment/reply/{$entity_type}/" . $entity->{$id_key}; } } - else { - $links['comment-forbidden'] = array( - 'title' => theme('comment_post_forbidden', array('node' => $node)), - 'html' => TRUE, - ); - } + } + else { + $links['comment-forbidden'] = array( + 'title' => theme('comment_post_forbidden', array('entity' => $entity, 'entity_type' => $entity_type)), + 'html' => TRUE, + ); } } + } - $node->content['links']['comment'] = array( + if (!empty($links)) { + $entity->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); - } + // 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 ($entity->comment && $view_mode == 'full' && empty($entity->in_preview)) { # @TODO: && node_is_page($entity) + $entity->content['comments'] = comment_entity_page_additions($entity, $entity_type); } } /** * Build the comment-related elements for node detail pages. * - * @param $node + * @param $entity * A node object. */ -function comment_node_page_additions($node) { +function comment_entity_page_additions($entity, $entity_type) { $additions = array(); + $entity_info = entity_get_info($entity_type); + $id_key = $entity_info['entity keys']['id']; + $bundle_key = $entity_info['entity keys']['bundle']; + // 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)) { + if (($entity->comment_count && user_access('access comments')) || user_access('administer comments')) { + $mode = variable_get("comment_default_mode_{$entity_type}_" . $entity->{$bundle_key}, COMMENT_MODE_THREADED); + $comments_per_page = variable_get("comment_default_per_page_{$entity_type}_" . $entity->{$bundle_key}, 50); + if ($cids = comment_get_thread($entity, $entity_type, $mode, $comments_per_page)) { $comments = comment_load_multiple($cids); comment_prepare_thread($comments); - $build = comment_view_multiple($comments, $node); + $build = comment_view_multiple($comments, $entity); $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)) { - $build = drupal_get_form("comment_node_{$node->type}_form", (object) array('nid' => $node->nid)); - $additions['comment_form'] = $build; + if (user_access('post comments') ) { + if ($entity->comment == COMMENT_ENTITY_OPEN) { + $form_location = "comment_form_location_{$entity_type}_". $entity->{$bundle_key}; + $form_location = variable_get($form_location, COMMENT_FORM_BELOW); + if ($form_location == COMMENT_FORM_BELOW) { + $build = drupal_get_form("comment_entity_{$entity_type}_". $entity->{$bundle_key} ."_form", (object) array('entity_type' => $entity_type, 'entity_id' => $entity->nid)); + $additions['comment_form'] = $build; + } + } } if ($additions) { $additions += array( - '#theme' => 'comment_wrapper__node_' . $node->type, - '#node' => $node, + '#theme' => 'comment_wrapper__node_' . $entity->type, + '#entity' => $entity, 'comments' => array(), 'comment_form' => array(), ); @@ -757,8 +790,8 @@ function comment_node_page_additions($node) { /** * Retrieve comments for a thread. * - * @param $node - * The node whose comment(s) needs rendering. + * @param $entity + * The entity whose comment(s) needs rendering. * @param $mode * The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED. * @param $comments_per_page @@ -818,23 +851,26 @@ function comment_node_page_additions($node) { * 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, $mode, $comments_per_page) { +function comment_get_thread($entity, $entity_type, $mode, $comments_per_page) { + $entity_info = entity_get_info($entity_type); + $id_key = $entity_info['entity keys']['id']; + $query = db_select('comment', 'c')->extend('PagerDefault'); $query->addField('c', 'cid'); $query - ->condition('c.nid', $node->nid) - ->addTag('node_access') - ->addTag('comment_filter') - ->addMetaData('node', $node) + ->condition('c.entity_type', $entity_type) + ->condition('c.entity_id', $entity->{$id_key}) + ->addTag('comment_filter') # ->addTag('node_access') + ->addMetaData('entity', $entity) ->limit($comments_per_page); $count_query = db_select('comment', 'c'); $count_query->addExpression('COUNT(*)'); $count_query - ->condition('c.nid', $node->nid) - ->addTag('node_access') - ->addTag('comment_filter') - ->addMetaData('node', $node); + ->condition('c.entity_type', $entity_type) + ->condition('c.entity_id', $entity->{$id_key}) + ->addTag('comment_filter') # ->addTag('node_access') + ->addMetaData('entity', $entity); if (!user_access('administer comments')) { $query->condition('c.status', COMMENT_PUBLISHED); @@ -1021,14 +1057,14 @@ function comment_build_content($comment, $node, $view_mode = 'full', $langcode = * * @param $comment * The comment object. - * @param $node + * @param $entity * The node the comment is attached to. * @return * A structured array of links. */ -function comment_links($comment, $node) { +function comment_links($comment, $entity) { $links = array(); - if ($node->comment == COMMENT_NODE_OPEN) { + if (isset($entity->comment) && $entity->comment == COMMENT_ENTITY_OPEN) { if (user_access('administer comments') && user_access('post comments')) { $links['comment-delete'] = array( 'title' => t('delete'), @@ -1042,7 +1078,7 @@ function comment_links($comment, $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->cid", 'html' => TRUE, ); if ($comment->status == COMMENT_NOT_PUBLISHED) { @@ -1064,12 +1100,12 @@ function comment_links($comment, $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->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)); $links['comment-forbidden']['html'] = TRUE; } } @@ -1130,32 +1166,36 @@ function comment_form_node_type_form_alter(&$form, $form_state) { // 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( + $name = 'comment_node_' . $form['#node_type']->type; + $form['comment']['comment_node'] = array( '#type' => 'select', '#title' => t('Default comment setting for new content'), - '#default_value' => variable_get('comment_' . $form['#node_type']->type, COMMENT_NODE_OPEN), + '#default_value' => variable_get($name, COMMENT_ENTITY_OPEN), '#options' => array( - COMMENT_NODE_OPEN => t('Open'), - COMMENT_NODE_CLOSED => t('Closed'), - COMMENT_NODE_HIDDEN => t('Hidden'), + COMMENT_ENTITY_OPEN => t('Open'), + COMMENT_ENTITY_CLOSED => t('Closed'), + COMMENT_ENTITY_HIDDEN => t('Hidden'), ), ); - $form['comment']['comment_default_mode'] = array( + $name = 'comment_default_mode_node_' . $form['#node_type']->type; + $form['comment']['comment_default_mode_node'] = array( '#type' => 'checkbox', '#title' => t('Threading'), - '#default_value' => variable_get('comment_default_mode_' . $form['#node_type']->type, COMMENT_MODE_THREADED), + '#default_value' => variable_get($name, COMMENT_MODE_THREADED), '#description' => t('Show comment replies in a threaded list.'), ); - $form['comment']['comment_default_per_page'] = array( + $name = 'comment_default_per_page_node_' . $form['#node_type']->type; + $form['comment']['comment_default_per_page_node'] = array( '#type' => 'select', '#title' => t('Comments per page'), - '#default_value' => variable_get('comment_default_per_page_' . $form['#node_type']->type, 50), + '#default_value' => variable_get($name, 50), '#options' => _comment_per_page(), ); - $form['comment']['comment_anonymous'] = array( + $name = 'comment_anonymous_node_' . $form['#node_type']->type; + $form['comment']['comment_anonymous_node'] = array( '#type' => 'select', '#title' => t('Anonymous commenting'), - '#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT), + '#default_value' => variable_get($name, 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'), @@ -1163,20 +1203,23 @@ function comment_form_node_type_form_alter(&$form, $form_state) { ), '#access' => user_access('post comments', drupal_anonymous_user()), ); - $form['comment']['comment_subject_field'] = array( + $name = 'comment_subject_field_node_' . $form['#node_type']->type; + $form['comment']['comment_subject_field_node'] = array( '#type' => 'checkbox', '#title' => t('Allow comment title'), - '#default_value' => variable_get('comment_subject_field_' . $form['#node_type']->type, 1), + '#default_value' => variable_get($name, 1), ); - $form['comment']['comment_form_location'] = array( + $name = 'comment_form_location_node_' . $form['#node_type']->type; + $form['comment']['comment_form_location_node'] = 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), + '#default_value' => variable_get($name, COMMENT_FORM_BELOW), ); - $form['comment']['comment_preview'] = array( + $name = 'comment_preview_node_' . $form['#node_type']->type; + $form['comment']['comment_preview_node'] = array( '#type' => 'radios', '#title' => t('Preview comment'), - '#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, DRUPAL_OPTIONAL), + '#default_value' => variable_get($name, DRUPAL_OPTIONAL), '#options' => array( DRUPAL_DISABLED => t('Disabled'), DRUPAL_OPTIONAL => t('Optional'), @@ -1206,8 +1249,8 @@ function comment_form_node_form_alter(&$form, $form_state) { ), '#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; + $comment_count = isset($node->nid) ? db_query('SELECT comment_count FROM {comment_entity_statistics} WHERE entity_id = :entity_id', array(':entity_id' => $node->nid))->fetchField() : 0; + $comment_settings = ($node->comment == COMMENT_ENTITY_HIDDEN && empty($comment_count)) ? COMMENT_ENTITY_CLOSED : $node->comment; $form['comment_settings']['comment'] = array( '#type' => 'radios', '#title' => t('Comments'), @@ -1215,60 +1258,67 @@ function comment_form_node_form_alter(&$form, $form_state) { '#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_ENTITY_OPEN => t('Open'), + COMMENT_ENTITY_CLOSED => t('Closed'), + COMMENT_ENTITY_HIDDEN => t('Hidden'), ), - COMMENT_NODE_OPEN => array( + COMMENT_ENTITY_OPEN => array( '#description' => t('Users with the "Post comments" permission can post comments.'), ), - COMMENT_NODE_CLOSED => array( + COMMENT_ENTITY_CLOSED => array( '#description' => t('Users cannot post comments, but existing comments will be displayed.'), ), - COMMENT_NODE_HIDDEN => array( + 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)) { - unset($form['comment_settings']['comment']['#options'][COMMENT_NODE_HIDDEN]); - unset($form['comment_settings']['comment'][COMMENT_NODE_HIDDEN]); - $form['comment_settings']['comment'][COMMENT_NODE_CLOSED]['#description'] = t('Users cannot post comments.'); + unset($form['comment_settings']['comment']['#options'][COMMENT_ENTITY_HIDDEN]); + unset($form['comment_settings']['comment'][COMMENT_ENTITY_HIDDEN]); + $form['comment_settings']['comment'][COMMENT_ENTITY_CLOSED]['#description'] = t('Users cannot post comments.'); } } /** - * Implements hook_node_load(). + * Implements hook_entity_load(). */ -function comment_node_load($nodes, $types) { - $comments_enabled = array(); +function comment_entity_load($entities, $entity_type) { + $entity_info = entity_get_info($entity_type); + + // Entity does not support comment, we do nothing. + if (!comment_is_valid_entity($entity_info)) { + return; + } - // Check if comments are enabled for each node. If comments are disabled, - // assign values without hitting the database. - foreach ($nodes as $node) { + $id_field = $entity_info['entity keys']['id']; + + $comments_enabled = array(); + foreach ($entities as $entity) { // Store whether comments are enabled for this node. - if ($node->comment != COMMENT_NODE_HIDDEN) { - $comments_enabled[] = $node->nid; + if ($entity->comment != COMMENT_ENTITY_HIDDEN) { + $comments_enabled[] = $entity->{$id_field}; } else { - $node->cid = 0; - $node->last_comment_timestamp = $node->created; - $node->last_comment_name = ''; - $node->last_comment_uid = $node->uid; - $node->comment_count = 0; + $entity->cid = 0; + $entity->last_comment_timestamp = isset($entity->created) ? $entity->created : NULL; + $entity->last_comment_name = ''; + $entity->last_comment_uid = $entity->uid; + $entity->last_comment_uid = isset($entity->uid) ? $entity->uid : 0; + $entity->comment_count = 0; } } // 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)); + $result = db_query('SELECT cid, entity_id, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count FROM {comment_entity_statistics} WHERE entity_type = :entity_type AND entity_id IN (:comments_enabled)', array(':entity_type' => $entity_type, ':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; + $entities[$record->entity_id]->cid = $record->cid; + $entities[$record->entity_id]->last_comment_timestamp = $record->last_comment_timestamp; + $entities[$record->entity_id]->last_comment_name = $record->last_comment_name; + $entities[$record->entity_id]->last_comment_uid = $record->last_comment_uid; + $entities[$record->entity_id]->comment_count = $record->comment_count; } } } @@ -1278,7 +1328,7 @@ function comment_node_load($nodes, $types) { */ function comment_node_prepare($node) { if (!isset($node->comment)) { - $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN); + $node->comment = variable_get("comment_$node->type", COMMENT_ENTITY_OPEN); } } @@ -1367,11 +1417,11 @@ function comment_update_index() { */ function comment_node_search_result($node) { // Do not make a string if comments are hidden. - if (user_access('access comments') && $node->comment != COMMENT_NODE_HIDDEN) { + if (user_access('access comments') && $node->comment != COMMENT_ENTITY_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) { + if ($node->comment != COMMENT_ENTITY_CLOSED || $comments > 0) { return array('comment' => format_plural($comments, '1 comment', '@count comments')); } } @@ -1440,6 +1490,8 @@ function comment_access($op, $comment) { * @see comment_int_to_alphadecimal() */ function comment_save($comment) { + kpr($comment); + exit; global $user; $transaction = db_transaction(); @@ -1481,7 +1533,7 @@ function comment_save($comment) { db_ignore_slave(); // Update the {node_comment_statistics} table prior to executing hooks. - _comment_update_node_statistics($comment->nid); + _comment_update_entity_statistics($comment->nid); field_attach_update('comment', $comment); // Allow modules to respond to the updating of a comment. @@ -1562,7 +1614,7 @@ function comment_save($comment) { db_ignore_slave(); // Update the {node_comment_statistics} table prior to executing hooks. - _comment_update_node_statistics($comment->nid); + _comment_update_entity_statistics($comment->nid); field_attach_insert('comment', $comment); @@ -1616,7 +1668,7 @@ function comment_delete_multiple($cids) { // Delete the comment's replies. $child_cids = db_query('SELECT cid FROM {comment} WHERE pid = :cid', array(':cid' => $comment->cid))->fetchCol(); comment_delete_multiple($child_cids); - _comment_update_node_statistics($comment->nid); + _comment_update_entity_statistics($comment->nid); } } catch (Exception $e) { @@ -1681,9 +1733,7 @@ class CommentController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $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 $query->innerJoin('users', 'u', 'base.uid = u.uid'); $query->addField('u', 'name', 'registered_name'); $query->fields('u', array('uid', 'signature', 'signature_format', 'picture')); @@ -1694,7 +1744,7 @@ class CommentController extends DrupalDefaultEntityController { // Setup 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->new = node_mark($comment->entity_id, $comment->changed); $comment->node_type = 'comment_node_' . $comment->node_type; $comments[$key] = $comment; } @@ -1705,26 +1755,26 @@ class CommentController extends DrupalDefaultEntityController { /** * Get number of new comments for current user and specified node. * - * @param $nid + * @param $entity_id * Node-id to count comments for. * @param $timestamp * Time to count from (defaults to time of last user access * to node). * @return The result or FALSE on error. */ -function comment_num_new($nid, $timestamp = 0) { +function comment_num_new($entity_id, $timestamp = 0) { global $user; if ($user->uid) { // Retrieve the timestamp at which the current user last viewed this node. if (!$timestamp) { - $timestamp = node_last_viewed($nid); + $timestamp = node_last_viewed($entity_id); } $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_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, + return db_query('SELECT COUNT(cid) FROM {comment} WHERE entity_id = :entity_id AND created > :timestamp AND status = :status', array( + ':entity_id' => $entity_id, ':timestamp' => $timestamp, ':status' => COMMENT_PUBLISHED, ))->fetchField(); @@ -1810,9 +1860,13 @@ function comment_edit_page($comment) { */ function comment_forms() { $forms = array(); - foreach (node_type_get_types() as $type) { - $forms["comment_node_{$type->type}_form"]['callback'] = 'comment_form'; + + foreach (comment_get_entity_info() as $entity_type => $entity_info) { + foreach ($entity_info['bundles'] as $bundle_type => $bundle) { + $forms["comment_{$entity_type}_{$bundle_type}_form"]['callback'] = 'comment_form'; + } } + return $forms; } @@ -1853,14 +1907,18 @@ function comment_form($form, &$form_state, $comment) { $comment = $form_state['comment']; } - $node = node_load($comment->nid); - $form['#node'] = $node; + $entity_info = entity_get_info($comment->entity_type); + $id_key = $entity_info['entity keys']['id']; + $bundle_key = $entity_info['bundle keys']['bundle']; + $entity = entity_load($comment->entity_type, array($comment->entity_id)); + $entity = reset($entity); + $form['#entity'] = $entity; - // Use #comment-form as unique jump target, regardless of node type. + // Use #comment-form as unique jump target, regardless of entity/bundle. $form['#id'] = drupal_html_id('comment_form'); - $form['#theme'] = array('comment_form__node_' . $node->type, 'comment_form'); + $form['#theme'] = array("comment_form__entity_{$comment->entity_type}_" . $entity->{$bundle_key}, 'comment_form'); - $anonymous_contact = variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT); + $anonymous_contact = variable_get("comment_anonymous_{$comment->entity_type}_" . $entity->{$bundle_key}, COMMENT_ANONYMOUS_MAYNOT_CONTACT); $is_admin = (!empty($comment->cid) && user_access('administer comments')); if (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) { @@ -1871,7 +1929,7 @@ function comment_form($form, &$form_state, $comment) { // If not replying to a comment, use our dedicated page callback for new // comments on nodes. if (empty($comment->cid) && empty($comment->pid)) { - $form['#action'] = url('comment/reply/' . $comment->nid); + $form['#action'] = url("comment/reply/{$comment->entity_type}/" . $entity->{$id_key}); } if (isset($form_state['comment_preview'])) { @@ -1991,7 +2049,7 @@ function comment_form($form, &$form_state, $comment) { '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => $comment->subject, - '#access' => variable_get('comment_subject_field_' . $node->type, 1) == 1, + '#access' => variable_get("comment_subject_field_{$comment->entity_type}_" . $entity->{$bundle_key}, 1) == 1, '#weight' => -1, ); @@ -2002,10 +2060,12 @@ function comment_form($form, &$form_state, $comment) { ); // Add internal comment properties. - foreach (array('cid', 'pid', 'nid', 'language', 'uid') as $key) { + foreach (array('cid', 'pid', 'entity_type', 'entity_id', 'language', 'uid') as $key) { $form[$key] = array('#type' => 'value', '#value' => $comment->$key); } - $form['node_type'] = array('#type' => 'value', '#value' => 'comment_node_' . $node->type); + + $form['entity_type'] = array('#type' => 'value', '#value' => $comment->entity_type); + $form['bundle_type'] = array('#type' => 'value', '#value' => $entity->{$bundle_key}); // Only show the save button if comment previews are optional or if we are // already previewing the submission. @@ -2013,19 +2073,19 @@ function comment_form($form, &$form_state, $comment) { $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save'), - '#access' => ($comment->cid && user_access('administer comments')) || variable_get('comment_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_REQUIRED || isset($form_state['comment_preview']), + '#access' => ($comment->cid && user_access('administer comments')) || variable_get('comment_preview_' . $entity->type, DRUPAL_OPTIONAL) != DRUPAL_REQUIRED || isset($form_state['comment_preview']), '#weight' => 19, ); $form['actions']['preview'] = array( '#type' => 'submit', '#value' => t('Preview'), - '#access' => (variable_get('comment_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_DISABLED), + '#access' => (variable_get('comment_preview_' . $entity->type, DRUPAL_OPTIONAL) != DRUPAL_DISABLED), '#weight' => 20, '#submit' => array('comment_form_build_preview'), ); // Attach fields. - $comment->node_type = 'comment_node_' . $node->type; + $comment->node_type = 'comment_node_' . $entity->type; field_attach_form('comment', $comment, $form, $form_state); return $form; @@ -2213,7 +2273,7 @@ function comment_form_submit_build_comment($form, &$form_state) { function comment_form_submit($form, &$form_state) { $node = node_load($form_state['values']['nid']); $comment = comment_form_submit_build_comment($form, $form_state); - if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) { + if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_ENTITY_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')))); @@ -2371,8 +2431,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'] = variable_get('comment_default_mode_' . $variables['entity']->type, COMMENT_MODE_THREADED); // The comment form is optional and may not exist. $variables['content'] += array('comment_form' => array()); } @@ -2399,7 +2459,7 @@ function _comment_per_page() { } /** - * Updates the comment statistics for a given node. This should be called any + * Updates the comment statistics for a given entity. This should be called any * time a comment is added, deleted, or updated. * * The following fields are contained in the node_comment_statistics table. @@ -2408,25 +2468,27 @@ function _comment_per_page() { * - last_comment_uid: the uid of the poster for the last comment for this node or the node authors uid if no comments exists for the node. * - comment_count: the total number of approved/published comments on this node. */ -function _comment_update_node_statistics($nid) { +function _comment_update_entity_statistics($entity_type, $entiy_id) { // Allow bulk updates and inserts to temporarily disable the // maintenance of the {node_comment_statistics} table. - if (!variable_get('comment_maintain_node_statistics', TRUE)) { + if (!variable_get("comment_maintain_{$entity_type}_statistics", TRUE)) { return; } - $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND status = :status', array( - ':nid' => $nid, + $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE entity_type = :entity_type AND entiy_id = :entiy_id AND status = :status', array( + ':entity_type' => $entity_type, + ':entiy_id' => $entiy_id, ':status' => COMMENT_PUBLISHED, ))->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, + $last_reply = db_query_range('SELECT cid, name, changed, uid FROM {comment} WHERE entity_type = :entity_type AND entiy_id = :entiy_id AND status = :status ORDER BY cid DESC', 0, 1, array( + ':entity_type' => $entity_type, + ':entiy_id' => $entiy_id, ':status' => COMMENT_PUBLISHED, ))->fetchObject(); - db_update('node_comment_statistics') + db_update('comment_entity_statistics') ->fields(array( 'cid' => $last_reply->cid, 'comment_count' => $count, @@ -2434,21 +2496,32 @@ function _comment_update_node_statistics($nid) { 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name, 'last_comment_uid' => $last_reply->uid, )) - ->condition('nid', $nid) + ->condition('entity_type', $entity_type) + ->condition('entiy_id', $entiy_id) ->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_info = entity_get_info($entity_type); + + # Get entity uid, created + $entity = db_select($entity_info['base table'], 'entity') + ->fields('entity', array('uid', 'created')) # @TODO: How are we sure entity has uid and created columns? + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute() + ->fetchObject(); + + db_update('comment_entity_statistics') ->fields(array( 'cid' => 0, 'comment_count' => 0, - 'last_comment_timestamp' => $node->created, + 'last_comment_timestamp' => $entity->created, 'last_comment_name' => '', - 'last_comment_uid' => $node->uid, + 'last_comment_uid' => $entity->uid, )) - ->condition('nid', $nid) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) ->execute(); } } @@ -2709,7 +2782,7 @@ function comment_rdf_mapping() { * Implements hook_file_download_access(). */ function comment_file_download_access($field, $entity_type, $entity) { - if ($entity_type == 'comment') { + if ($entity_type === 'comment') { if (user_access('access comments') && $entity->status == COMMENT_PUBLISHED || user_access('administer comments')) { $node = node_load($entity->nid); return node_access('view', $node); @@ -2717,3 +2790,86 @@ function comment_file_download_access($field, $entity_type, $entity) { return FALSE; } } + +/** + * Function to get entities which support comment feature. + * Invalid bundles does not exist in returned entities. + * + * @return array(). + */ +function comment_get_entity_info($entity_type = NULL) { + $comment_entities = array(); + foreach (entity_get_info() as $entity => $entity_info) { + if (comment_is_valid_entity($entity_info)) { + $comment_entities[$entity] = $entity_info; + } + } + return !is_null($entity_type) ? $comment_entities[$entity_type] : $comment_entities; +} + +/** + * Function to check is an entity supporting comment feature. + * @param array $entity_info Entity info array returned by entity_get_info($entity_type). + */ +function comment_is_valid_entity($entity_info) { + $valid = !empty($entity_info['comment']); + $valid = $valid && !empty($entity_info['uri callback']); + // Remove invalid bundle -- which does not have admin path + if ($valid && !empty($entity_info['bundles'])) { + foreach ($entity_info['bundles'] as $bundle_type => $bundle) { + if (!isset($bundle['admin']['path'])) { + unset($entity_info['bundles'][$bundle_type]); + } + } + } + $valid = $valid && !empty($entity_info['bundles']); + return $valid; +} + +/** + * Implements hook_schema_alter(). + * Create comment field to entity base and revision tables. + */ +function comment_schema_alter(&$schema) { + foreach (comment_get_entity_info() as $entity_type => $entity_info) { + // Define foreign keys to comment tables + $schema['comment']['foreign keys']["comment_{$entity_type}"] = array( + 'table' => $entity_info['base table'], + 'columns' => array('entity_id' => $entity_info['entity keys']['id']), + ); + $schema['comment_entity_statistics']['foreign keys']["statistics_{$entity_type}"] = array( + 'table' => $entity_info['base table'], + 'columns' => array('entity_id' => $entity_info['entity keys']['id']), + ); + + // Add comment column to base table and revision table. + foreach (array('base table', 'revision table') as $i) { + // entity does not list base/revision table. + if (!isset($entity_info[$i])) { + continue; + } + + $table_name = $entity_info[$i]; + + // schema for entity base/revision table does not exist or entity module + // already define it. + if (!isset($schema[$table_name]) || isset($schema[$table_name]['fields']['comment'])) { + continue; + } + + $schema[$table_name]['fields']['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, + ); + + try { + db_add_field($table_name, 'comment', $schema[$table_name]['fields']['comment']); + } + catch (Exception $e) { + // Field created before, we can safely ignore it. + } + } + } +} diff --git a/modules/comment/comment.pages.inc b/modules/comment/comment.pages.inc index 7e88bff..f6dfb80 100644 --- a/modules/comment/comment.pages.inc +++ b/modules/comment/comment.pages.inc @@ -9,37 +9,38 @@ * This function is responsible for generating a comment reply form. * 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 entites + * - attempts to reply to entites 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 $node - * Every comment belongs to a node. This is that node. + * @param $entity + * Every comment belongs to an entity. This is that entity. * * @param $pid * Some comments are replies to other comments. In those cases, $pid is the parent * comment's cid. * * @return - * The rendered parent node or comment plus the new comment form. + * The rendered parent entity or comment plus the new comment form. */ -function comment_reply($node, $pid = NULL) { +function comment_reply($entity, $pid = NULL) { // Set the breadcrumb trail. - drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->title, 'node/' . $node->nid))); + drupal_set_breadcrumb(array(l(t('Home'), NULL), l($entity->title, 'node/' . $entity->nid))); $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'] = drupal_get_form("comment_node_{$node->type}_form", (object) array('pid' => $pid, 'nid' => $node->nid)); + $comment = (object) array('pid' => $pid, 'nid' => $entity->nid); + $build['comment_form'] = drupal_get_form("comment_node_{$entity->type}_form", $comment); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto("node/$entity->nid"); } } else { @@ -54,44 +55,45 @@ function comment_reply($node, $pid = NULL) { if ($comment) { // 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) { + if ($comment->nid != $entity->nid) { // Attempting to reply to a comment not belonging to the current nid. drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto("node/$entity->nid"); } // Display the parent comment - $comment->node_type = 'comment_node_' . $node->type; + $comment->node_type = 'comment_node_' . $entity->type; field_attach_load('comment', array($comment->cid => $comment)); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; - $build['comment_parent'] = comment_view($comment, $node); + $build['comment_parent'] = comment_view($comment, $entity); } else { drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto("node/$entity->nid"); } } else { drupal_set_message(t('You are not authorized to view comments.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto("node/$entity->nid"); } } // This is the case where the comment is in response to a node. Display the node. elseif (user_access('access content')) { - $build['comment_node'] = node_view($node); + $build['comment_node'] = node_view($entity); } // Should we show the reply box? - if ($node->comment != COMMENT_NODE_OPEN) { + if ($entity->comment != COMMENT_ENTITY_OPEN) { drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto("node/$entity->nid"); } elseif (user_access('post comments')) { - $edit = array('nid' => $node->nid, 'pid' => $pid); - $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) $edit); + # @TODO: Remove arg(2) + $edit = array('entity_type' => arg(2), 'entity_id' => $entity->nid, 'pid' => $pid); + $build['comment_form'] = drupal_get_form("comment_node_{$entity->type}_form", (object)$edit); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); - drupal_goto("node/$node->nid"); + drupal_goto("node/$entity->nid"); } } diff --git a/modules/comment/comment.test b/modules/comment/comment.test index 2e96ba3..2a7896e 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -615,7 +615,7 @@ class CommentInterfaceTest extends CommentHelperCase { * USER_REGISTER_VISITORS. * - contact: COMMENT_ANONYMOUS_MAY_CONTACT or * COMMENT_ANONYMOUS_MAYNOT_CONTACT. - * - comments: COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, or + * - comments: COMMENT_ENTITY_OPEN, COMMENT_NODE_CLOSED, or * COMMENT_NODE_HIDDEN. * - User permissions: * These are granted or revoked for the user, according to the @@ -637,7 +637,7 @@ class CommentInterfaceTest extends CommentHelperCase { 'form' => COMMENT_FORM_BELOW, 'user_register' => USER_REGISTER_VISITORS, 'contact' => COMMENT_ANONYMOUS_MAY_CONTACT, - 'comments' => COMMENT_NODE_OPEN, + 'comments' => COMMENT_ENTITY_OPEN, 'access comments' => 0, 'post comments' => 0, // Enabled by default, because it's irrelevant for this test. @@ -715,9 +715,9 @@ class CommentInterfaceTest extends CommentHelperCase { COMMENT_ANONYMOUS_MUST_CONTACT => 'required', ); $t_comments = array( - COMMENT_NODE_OPEN => 'open', - COMMENT_NODE_CLOSED => 'closed', - COMMENT_NODE_HIDDEN => 'hidden', + COMMENT_ENTITY_OPEN => 'open', + COMMENT_ENTITY_CLOSED => 'closed', + COMMENT_ENTITY_HIDDEN => 'hidden', ); $verbose = $info; $verbose['form'] = $t_form[$info['form']]; diff --git a/modules/node/node.install b/modules/node/node.install index 3f732a4..48cf50a 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -70,12 +70,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', @@ -234,12 +228,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/modules/node/node.module b/modules/node/node.module index 0c3cfb7..45b5ce0 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -172,6 +172,7 @@ function node_entity_info() { 'revision table' => 'node_revision', 'uri callback' => 'node_uri', 'fieldable' => TRUE, + 'comment' => TRUE, 'entity keys' => array( 'id' => 'nid', 'revision' => 'vid', diff --git a/themes/bartik/templates/comment-wrapper.tpl.php b/themes/bartik/templates/comment-wrapper.tpl.php index 864dc41..1a58a9a 100644 --- a/themes/bartik/templates/comment-wrapper.tpl.php +++ b/themes/bartik/templates/comment-wrapper.tpl.php @@ -36,7 +36,7 @@ */ ?>
> - type != 'forum'): ?> + type != 'forum'): ?>