Newer
Older
* 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.
Angie Byron
committed
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
Angie Byron
committed
use Drupal\file\Plugin\Core\Entity\File;
use Drupal\Core\Entity\EntityInterface;
Dries Buytaert
committed
use Symfony\Component\HttpFoundation\Request;
Dries Buytaert
committed
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Dries Buytaert
committed
use Symfony\Component\HttpKernel\HttpKernelInterface;
Dries Buytaert
committed
* Comment is awaiting approval.
Dries Buytaert
committed
*/
const COMMENT_NOT_PUBLISHED = 0;
Dries Buytaert
committed
* Comment is published.
const COMMENT_PUBLISHED = 1;
/**
* Comments are displayed in a flat list - expanded.
*/
const COMMENT_MODE_FLAT = 0;
/**
* Comments are displayed as a threaded list - expanded.
*/
const COMMENT_MODE_THREADED = 1;
* Anonymous posters cannot enter their contact information.
const COMMENT_ANONYMOUS_MAYNOT_CONTACT = 0;
/**
* Anonymous posters may leave their contact information.
*/
const COMMENT_ANONYMOUS_MAY_CONTACT = 1;
* Anonymous posters are required to leave their contact information.
const COMMENT_ANONYMOUS_MUST_CONTACT = 2;
* Comment form should be displayed on a separate page.
const COMMENT_FORM_SEPARATE_PAGE = 0;
/**
* Comment form should be shown below post or list of comments.
*/
const COMMENT_FORM_BELOW = 1;
Dries Buytaert
committed
* Comments for this node are hidden.
const COMMENT_NODE_HIDDEN = 0;
Dries Buytaert
committed
* Comments for this node are closed.
const COMMENT_NODE_CLOSED = 1;
Dries Buytaert
committed
* Comments for this node are open.
const COMMENT_NODE_OPEN = 2;
Dries Buytaert
committed
Angie Byron
committed
use Drupal\comment\Plugin\Core\Entity\Comment;
Dries Buytaert
committed
* Implements hook_help().
Gábor Hojtsy
committed
function comment_help($path, $arg) {
switch ($path) {
Angie Byron
committed
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Comment module allows users to comment on site content, set commenting defaults and permissions, and moderate comments. For more information, see the online handbook entry for <a href="@comment">Comment module</a>.', array('@comment' => 'http://drupal.org/documentation/modules/comment')) . '</p>';
Angie Byron
committed
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Default and custom settings') . '</dt>';
$output .= '<dd>' . t("Each <a href='@content-type'>content type</a> can have its own default comment settings configured as: <em>Open</em> to allow new comments, <em>Hidden</em> to hide existing comments and prevent new comments, or <em>Closed</em> 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'))) . '</dd>';
$output .= '<dt>' . t('Comment approval') . '</dt>';
$output .= '<dd>' . t("Comments from users who have the <em>Skip comment approval</em> permission are published immediately. All other comments are placed in the <a href='@comment-approval'>Unapproved comments</a> queue, until a user who has permission to <em>Administer comments</em> publishes or deletes them. Published comments can be bulk managed on the <a href='@admin-comment'>Published comments</a> administration page.", array('@comment-approval' => url('admin/content/comment/approval'), '@admin-comment' => url('admin/content/comment'))) . '</dd>';
Angie Byron
committed
$output .= '</dl>';
Dries Buytaert
committed
return $output;
Gábor Hojtsy
committed
}
Dries Buytaert
committed
/**
* Implements hook_entity_bundle_info().
*/
function comment_entity_bundle_info() {
$bundles = array();
Dries Buytaert
committed
foreach (node_type_get_names() as $type => $name) {
Dries Buytaert
committed
$bundles['comment']['comment_node_' . $type] = array(
'label' => t('@node_type comment', array('@node_type' => $name)),
// Provide the node type/bundle name for other modules, so it does not
// have to be extracted manually from the bundle name.
'node bundle' => $type,
Dries Buytaert
committed
);
}
Dries Buytaert
committed
return $bundles;
Dries Buytaert
committed
}
* 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()
*/
function comment_node_type_load($name) {
if ($type = node_type_load($name)) {
return 'comment_node_' . $type->type;
}
}
Angie Byron
committed
/**
Angie Byron
committed
* Entity URI callback.
Angie Byron
committed
*/
function comment_uri(Comment $comment) {
Angie Byron
committed
return array(
'path' => 'comment/' . $comment->id(),
'options' => array('fragment' => 'comment-' . $comment->id()),
Angie Byron
committed
);
Angie Byron
committed
}
/**
* Implements hook_field_extra_fields().
*/
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(
Dries Buytaert
committed
'form' => array(
'author' => array(
'label' => t('Author'),
'description' => t('Author textfield'),
'weight' => -2,
),
'subject' => array(
Dries Buytaert
committed
'label' => t('Subject'),
'description' => t('Subject textfield'),
'weight' => -1,
),
),
);
}
}
return $return;
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_theme().
Dries Buytaert
committed
*/
function comment_theme() {
return array(
'comment_block' => array(
Dries Buytaert
committed
'variables' => array('number' => NULL),
Dries Buytaert
committed
),
'comment_preview' => array(
'variables' => array('comment' => NULL),
Dries Buytaert
committed
),
'comment' => array(
'template' => 'comment',
'render element' => 'elements',
Dries Buytaert
committed
),
'comment_post_forbidden' => array(
'variables' => array('node' => NULL),
Dries Buytaert
committed
),
'comment_wrapper' => array(
'template' => 'comment-wrapper',
'render element' => 'content',
Dries Buytaert
committed
),
);
}
Dries Buytaert
committed
* Implements hook_menu().
Dries Buytaert
committed
function comment_menu() {
Dries Buytaert
committed
$items['admin/content/comment'] = array(
'title' => 'Comments',
Dries Buytaert
committed
'description' => 'List and edit site comments and the comment approval queue.',
Dries Buytaert
committed
'page callback' => 'comment_admin',
'access arguments' => array('administer comments'),
'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
'file' => 'comment.admin.inc',
Dries Buytaert
committed
);
Dries Buytaert
committed
$items['admin/content/comment/new'] = array(
'title' => 'Published comments',
Dries Buytaert
committed
'type' => MENU_DEFAULT_LOCAL_TASK,
);
Dries Buytaert
committed
$items['admin/content/comment/approval'] = array(
Angie Byron
committed
'title' => 'Unapproved comments',
'title callback' => 'comment_count_unpublished',
Dries Buytaert
committed
'page arguments' => array('approval'),
Dries Buytaert
committed
'access arguments' => array('administer comments'),
Dries Buytaert
committed
'type' => MENU_LOCAL_TASK,
);
$items['comment/%comment'] = array(
Dries Buytaert
committed
'title' => 'Comment permalink',
'page callback' => 'comment_permalink',
'page arguments' => array(1),
'access callback' => 'entity_page_access',
'access arguments' => array(1, 'view'),
Dries Buytaert
committed
);
$items['comment/%comment/view'] = array(
Dries Buytaert
committed
'title' => 'View comment',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
Angie Byron
committed
// Every other comment path uses %, but this one loads the comment directly,
// so we don't end up loading it twice (in the page and access callback).
Dries Buytaert
committed
$items['comment/%comment/edit'] = array(
'title' => 'Edit',
'type' => MENU_LOCAL_TASK,
'route_name' => 'comment_edit_page',
Dries Buytaert
committed
);
$items['comment/%comment/approve'] = array(
Dries Buytaert
committed
'title' => 'Approve',
'page callback' => 'comment_approve',
'page arguments' => array(1),
'access callback' => 'entity_page_access',
'access arguments' => array(1, 'approve'),
Dries Buytaert
committed
'file' => 'comment.pages.inc',
'weight' => 10,
Dries Buytaert
committed
);
$items['comment/%comment/delete'] = array(
Dries Buytaert
committed
'title' => 'Delete',
Angie Byron
committed
'page callback' => 'comment_confirm_delete_page',
'page arguments' => array(1),
'access callback' => 'entity_page_access',
'access arguments' => array(1, 'delete'),
Dries Buytaert
committed
'type' => MENU_LOCAL_TASK,
'file' => 'comment.admin.inc',
'weight' => 20,
Dries Buytaert
committed
);
$items['comment/reply/%node'] = array(
Dries Buytaert
committed
'title' => 'Add new comment',
Dries Buytaert
committed
'page callback' => 'comment_reply',
'page arguments' => array(2),
Dries Buytaert
committed
'access callback' => 'node_access',
'access arguments' => array('view', 2),
'file' => 'comment.pages.inc',
Dries Buytaert
committed
);
Dries Buytaert
committed
/**
* 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.';
Dries Buytaert
committed
// Adjust the Field UI tabs on admin/structure/types/manage/[node-type].
Dries Buytaert
committed
// See comment_entity_bundle_info().
$items['admin/structure/types/manage/%/comment/fields']['title'] = 'Comment fields';
$items['admin/structure/types/manage/%/comment/fields']['weight'] = 3;
$items['admin/structure/types/manage/%/comment/display']['title'] = 'Comment display';
$items['admin/structure/types/manage/%/comment/display']['weight'] = 4;
Dries Buytaert
committed
}
Angie Byron
committed
/**
* Returns a menu title which includes the number of unapproved comments.
*/
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));
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_type_insert().
Dries Buytaert
committed
*
* 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,
Dries Buytaert
committed
* hook_modules_enabled() serves to create the body fields.
*
* @see comment_modules_enabled()
Dries Buytaert
committed
*/
function comment_node_type_insert($info) {
Dries Buytaert
committed
_comment_body_field_create($info);
Dries Buytaert
committed
Dries Buytaert
committed
* Implements hook_node_type_update().
*/
function comment_node_type_update($info) {
if (!empty($info->old_type) && $info->type != $info->old_type) {
Angie Byron
committed
entity_invoke_bundle_hook('rename', 'comment', 'comment_node_' . $info->old_type, 'comment_node_' . $info->type);
Dries Buytaert
committed
Dries Buytaert
committed
* Implements hook_node_type_delete().
*/
function comment_node_type_delete($info) {
Angie Byron
committed
entity_invoke_bundle_hook('delete', 'comment', 'comment_node_' . $info->type);
$settings = array(
'comment',
'comment_default_mode',
'comment_default_per_page',
'comment_anonymous',
'comment_subject_field',
'comment_preview',
'comment_form_location',
);
foreach ($settings as $setting) {
variable_del($setting . '_' . $info->type);
Dries Buytaert
committed
}
}
/**
Dries Buytaert
committed
* Creates a comment_body field instance for a given node type.
*
* @param $info
* An object representing the content type. The only property that is
* currently used is $info->type, which is the machine name of the content
* type for which the body field (instance) is to be created.
*/
Dries Buytaert
committed
function _comment_body_field_create($info) {
// Create the field if needed.
if (!field_read_field('comment_body', array('include_inactive' => TRUE))) {
$field = array(
'field_name' => 'comment_body',
'type' => 'text_long',
'entity_types' => array('comment'),
);
field_create_field($field);
}
// Create the instance if needed.
if (!field_read_instance('comment', 'comment_body', 'comment_node_' . $info->type, array('include_inactive' => TRUE))) {
Angie Byron
committed
entity_invoke_bundle_hook('create', 'comment', 'comment_node_' . $info->type);
Dries Buytaert
committed
// Attaches the body field by default.
$instance = array(
'field_name' => 'comment_body',
'label' => 'Comment',
'entity_type' => 'comment',
'bundle' => 'comment_node_' . $info->type,
'settings' => array('text_processing' => 1),
'required' => TRUE,
);
field_create_instance($instance);
Alex Pott
committed
// Assign widget settings for the 'default' form mode.
entity_get_form_display('comment', 'comment_node_' . $info->type, 'default')
->setComponent('comment_body', array(
'type' => 'text_textarea',
))
->save();
// Assign display settings for the 'default' view mode.
entity_get_display('comment', 'comment_node_' . $info->type, 'default')
->setComponent('comment_body', array(
'label' => 'hidden',
'type' => 'text_default',
'weight' => 0,
))
->save();
Dries Buytaert
committed
}
}
Dries Buytaert
committed
* Implements hook_permission().
function comment_permission() {
return array(
Angie Byron
committed
'administer comments' => array(
'title' => t('Administer comments and comment settings'),
Angie Byron
committed
),
'access comments' => array(
'title' => t('View comments'),
Angie Byron
committed
),
'post comments' => array(
'title' => t('Post comments'),
Angie Byron
committed
),
'skip comment approval' => array(
'title' => t('Skip comment approval'),
Angie Byron
committed
),
Angie Byron
committed
'edit own comments' => array(
'title' => t('Edit own comments'),
),
);
Dries Buytaert
committed
/**
* Redirects comment links to the correct page depending on comment settings.
*
* Since comments are paged there is no way to guarantee which page a comment
* appears on. Comment paging and threading settings may be changed at any time.
* With threaded comments, an individual comment may move between pages as
* comments can be added either before or after it in the overall discussion.
* Therefore we use a central routing function for comment links, which
* calculates the page number based on current comment settings and returns
* the full comment view with the pager set dynamically.
*
* @param \Drupal\comment\Plugin\Core\Entity\Comment $comment
* A comment entity.
Dries Buytaert
committed
* @return
* The comment listing set to the page on which the comment appears.
*/
function comment_permalink(Comment $comment) {
if ($node = $comment->nid->entity) {
Dries Buytaert
committed
// Find the current display page for this comment.
$page = comment_get_display_page($comment->id(), $node->type);
Dries Buytaert
committed
Dries Buytaert
committed
// @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());
Dries Buytaert
committed
$subrequest->query->set('page', $page);
// @todo: Convert the pager to use the request object.
Dries Buytaert
committed
$_GET['page'] = $page;
return drupal_container()->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
Dries Buytaert
committed
}
Dries Buytaert
committed
throw new NotFoundHttpException();
Dries Buytaert
committed
}
* Finds the most recent comments that are available to the current user.
*
* @param integer $number
Dries Buytaert
committed
* (optional) The maximum number of comments to find. Defaults to 10.
Dries Buytaert
committed
*
* @return
Dries Buytaert
committed
* An array of comment objects or an empty array if there are no recent
* comments visible to the current user.
*/
function comment_get_recent($number = 10) {
Dries Buytaert
committed
$query = db_select('comment', 'c');
$query->innerJoin('node', 'n', 'n.nid = c.nid');
$query->addTag('node_access');
Dries Buytaert
committed
$query->addMetaData('base_table', 'comment');
Dries Buytaert
committed
$comments = $query
->fields('c')
->condition('c.status', COMMENT_PUBLISHED)
->condition('n.status', NODE_PUBLISHED)
Dries Buytaert
committed
->orderBy('c.created', 'DESC')
Dries Buytaert
committed
// Additionally order by cid to ensure that comments with the same timestamp
// are returned in the exact order posted.
->orderBy('c.cid', 'DESC')
Dries Buytaert
committed
->range(0, $number)
->execute()
->fetchAll();
return $comments ? $comments : array();
Gábor Hojtsy
committed
/**
* Calculates the page number for the first new comment.
*
* @param $num_comments
* Number of comments.
* @param $new_replies
* Number of new replies.
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $node
* The first new comment node.
* @return
* "page=X" if the page number is greater than zero; empty string otherwise.
Gábor Hojtsy
committed
*/
Angie Byron
committed
function comment_new_page_count($num_comments, $new_replies, EntityInterface $node) {
$mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED);
$comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50);
Gábor Hojtsy
committed
$pagenum = NULL;
Dries Buytaert
committed
$flat = $mode == COMMENT_MODE_FLAT ? TRUE : FALSE;
Dries Buytaert
committed
if ($num_comments <= $comments_per_page) {
// Only one page of comments.
Gábor Hojtsy
committed
$pageno = 0;
Dries Buytaert
committed
elseif ($flat) {
// Flat comments.
$count = $num_comments - $new_replies;
$pageno = $count / $comments_per_page;
Dries Buytaert
committed
}
Gábor Hojtsy
committed
else {
Dries Buytaert
committed
// Threaded comments: we build a query with a subquery to find the first
// thread with a new comment.
// 1. Find all the threads with a new comment.
$unread_threads_query = db_select('comment')
->fields('comment', array('thread'))
->condition('nid', $node->nid)
->condition('status', COMMENT_PUBLISHED)
->orderBy('created', 'DESC')
->orderBy('cid', 'DESC')
Dries Buytaert
committed
->range(0, $new_replies);
// 2. Find the first thread.
$first_thread = db_select($unread_threads_query, 'thread')
->fields('thread', array('thread'))
->orderBy('SUBSTRING(thread, 1, (LENGTH(thread) - 1))')
->range(0, 1)
->execute()
->fetchField();
// Remove the final '/'.
$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(
':status' => COMMENT_PUBLISHED,
Dries Buytaert
committed
':nid' => $node->nid,
Dries Buytaert
committed
':thread' => $first_thread,
))->fetchField();
Dries Buytaert
committed
$pageno = $count / $comments_per_page;
Gábor Hojtsy
committed
}
Gábor Hojtsy
committed
if ($pageno >= 1) {
Dries Buytaert
committed
$pagenum = array('page' => intval($pageno));
Gábor Hojtsy
committed
}
Gábor Hojtsy
committed
return $pagenum;
}
* Returns HTML for a list of recent comments.
*
* @ingroup themeable
*/
Dries Buytaert
committed
function theme_comment_block($variables) {
$items = array();
Dries Buytaert
committed
$number = $variables['number'];
Dries Buytaert
committed
foreach (comment_get_recent($number) as $comment) {
$items[] = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)) . ' <span>' . t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->changed))) . '</span>';
Dries Buytaert
committed
if ($items) {
Dries Buytaert
committed
return theme('item_list', array('items' => $items));
Dries Buytaert
committed
}
Angie Byron
committed
else {
return t('No comments available.');
}
Dries Buytaert
committed
* Implements hook_node_view().
Angie Byron
committed
function comment_node_view(EntityInterface $node, EntityDisplay $display, $view_mode) {
Angie Byron
committed
if ($node->comment != COMMENT_NODE_HIDDEN) {
if ($view_mode == 'rss') {
Angie Byron
committed
// 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))
);
Dries Buytaert
committed
}
elseif ($view_mode == 'teaser') {
Dries Buytaert
committed
// 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 (!empty($node->comment_count)) {
$links['comment-comments'] = array(
Dries Buytaert
committed
'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,
Dries Buytaert
committed
);
Angie Byron
committed
// 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",
Dries Buytaert
committed
'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,
Dries Buytaert
committed
);
Angie Byron
committed
}
if ($node->comment == COMMENT_NODE_OPEN) {
$comment_form_location = variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW);
Angie Byron
committed
if (user_access('post comments')) {
$links['comment-add'] = array(
'title' => t('Add new comment'),
'href' => "node/$node->nid",
Angie Byron
committed
'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";
}
Angie Byron
committed
}
Dries Buytaert
committed
$links['comment-forbidden'] = array(
Angie Byron
committed
'title' => theme('comment_post_forbidden', array('node' => $node)),
'html' => TRUE,
);
Angie Byron
committed
elseif ($view_mode != 'search_index' && $view_mode != 'search_result') {
Dries Buytaert
committed
// 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
Angie Byron
committed
// indexing or constructing a search result excerpt.
Dries Buytaert
committed
if ($node->comment == COMMENT_NODE_OPEN) {
Angie Byron
committed
$comment_form_location = variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW);
Angie Byron
committed
// 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";
}
Dries Buytaert
committed
}
Dries Buytaert
committed
$links['comment-forbidden'] = array(
Angie Byron
committed
'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')),
);
Dries Buytaert
committed
// 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.
Angie Byron
committed
if ($node->comment && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
Dries Buytaert
committed
$node->content['comments'] = comment_node_page_additions($node);
Dries Buytaert
committed
/**
* Builds the comment-related elements for node detail pages.
Dries Buytaert
committed
*
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $node
* The node entity for which to build the comment-related elements.
*
* @return
* A renderable array representing the comment-related page elements for the
* node.
Dries Buytaert
committed
*/
Angie Byron
committed
function comment_node_page_additions(EntityInterface $node) {
Dries Buytaert
committed
$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.
Angie Byron
committed
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)) {
Dries Buytaert
committed
$comments = comment_load_multiple($cids);
comment_prepare_thread($comments);
Angie Byron
committed
$build = comment_view_multiple($comments);
Dries Buytaert
committed
$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);
Dries Buytaert
committed
}
if ($additions) {
$additions += array(
Dries Buytaert
committed
'#theme' => 'comment_wrapper__node_' . $node->type,
Dries Buytaert
committed
'#node' => $node,
'comments' => array(),
'comment_form' => array(),
);
}
return $additions;
}
/**
* Returns a rendered form to comment the given node.
*
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $node
* The node entity to be commented.
Dries Buytaert
committed
* @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.
*
* @return array
* The renderable array for the comment addition form.
*/
Angie Byron
committed
function comment_add(EntityInterface $node, $pid = NULL) {
$values = array('nid' => $node->nid, 'pid' => $pid, 'node_type' => 'comment_node_' . $node->type);
$comment = entity_create('comment', $values);
return entity_get_form($comment);
}
Dries Buytaert
committed
/**
* Retrieves comments for a thread.
Dries Buytaert
committed
*
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $node
Dries Buytaert
committed
* The node whose comment(s) needs rendering.
* @param $mode
* The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED.
* @param $comments_per_page
* The amount of comments to display per page.
Dries Buytaert
committed
*
* @return
* An array of the IDs of the comment to be displayed.
*
Dries Buytaert
committed
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
* To display threaded comments in the correct order we keep a 'thread' field
* and order by that value. This field keeps this data in
* a way which is easy to update and convenient to use.
*
* A "thread" value starts at "1". If we add a child (A) to this comment,
* we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
* brother of (A) will get "1.2". Next brother of the parent of (A) will get
* "2" and so on.
*
* First of all note that the thread field stores the depth of the comment:
* depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
*
* Now to get the ordering right, consider this example:
*
* 1
* 1.1
* 1.1.1
* 1.2
* 2
*
* If we "ORDER BY thread ASC" we get the above result, and this is the
* natural order sorted by time. However, if we "ORDER BY thread DESC"
* we get:
*
* 2
* 1.2
* 1.1.1
* 1.1
* 1
*
* Clearly, this is not a natural way to see a thread, and users will get
* confused. The natural order to show a thread by time desc would be:
*
* 2
* 1
* 1.2
* 1.1
* 1.1.1
*
* which is what we already did before the standard pager patch. To achieve
* this we simply add a "/" at the end of each "thread" value. This way, the
* thread fields will look like this:
*
* 1/
* 1.1/
* 1.1.1/
* 1.2/
* 2/
*
* we add "/" since this char is, in ASCII, higher than every number, so if
* now we "ORDER BY thread DESC" we get the correct order. However this would
* spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
* to consider the trailing "/" so we use a substring only.
*/
Angie Byron
committed
function comment_get_thread(EntityInterface $node, $mode, $comments_per_page) {
catch
committed
$query = db_select('comment', 'c')
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
Dries Buytaert
committed
$query->addField('c', 'cid');
$query
->condition('c.nid', $node->nid)
->addTag('node_access')
->addTag('comment_filter')
Dries Buytaert
committed
->addMetaData('base_table', 'comment')
->addMetaData('node', $node)
Dries Buytaert
committed
->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')
Dries Buytaert
committed
->addMetaData('base_table', 'comment')
->addMetaData('node', $node);
Dries Buytaert
committed
if (!user_access('administer comments')) {
$query->condition('c.status', COMMENT_PUBLISHED);
$count_query->condition('c.status', COMMENT_PUBLISHED);
}
if ($mode === COMMENT_MODE_FLAT) {
$query->orderBy('c.cid', 'ASC');
}
else {
// See comment above. Analysis reveals that this doesn't cost too
// much. It scales much much better than having the whole comment
// structure.
$query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
$query->orderBy('torder', 'ASC');
Dries Buytaert
committed
}
$query->setCountQuery($count_query);
$cids = $query->execute()->fetchCol();
return $cids;
}
/**
* Calculates the indentation level of each comment in a comment thread.
*
* This function loops over an array representing a comment thread. For each
* comment, the function calculates the indentation level and saves it in the
* 'divs' property of the comment object.
Dries Buytaert
committed
*
* @param array $comments
* An array of comment objects, keyed by comment ID.
Dries Buytaert
committed
*/
function comment_prepare_thread(&$comments) {
// A flag stating if we are still searching for first new comment on the thread.
$first_new = TRUE;
// A counter that helps track how indented we are.
$divs = 0;
foreach ($comments as $key => $comment) {
if ($first_new && $comment->new->value != MARK_READ) {
Dries Buytaert
committed
// Assign the anchor only for the first new comment. This avoids duplicate
// id attributes on a page.
$first_new = FALSE;
$comment->first_new = TRUE;
}
// The $divs element instructs #prefix whether to add an indent div or
// close existing divs (a negative value).
$comment->depth = count(explode('.', $comment->thread->value)) - 1;
Dries Buytaert
committed
if ($comment->depth > $divs) {
$comment->divs = 1;
$divs++;
}
else {
$comment->divs = $comment->depth - $divs;
while ($comment->depth < $divs) {
$divs--;
}
}
$comments[$key] = $comment;
}
// The final comment must close up some hanging divs
$comments[$key]->divs_final = $divs;
}
/**
* Generates an array for rendering a comment.
Dries Buytaert
committed
*
* @param Drupal\comment\Comment $comment
* The comment object.
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
Dries Buytaert
committed
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
Dries Buytaert
committed
*
* @return
* An array as expected by drupal_render().
*/
Angie Byron
committed
function comment_view(Comment $comment, $view_mode = 'full', $langcode = NULL) {
return entity_view($comment, $view_mode, $langcode);
Dries Buytaert
committed
}
Angie Byron
committed
/**
* Adds reply, edit, delete, etc. links, depending on user permissions.
Angie Byron
committed
*
* @param Drupal\comment\Comment $comment
Angie Byron
committed
* The comment object.
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $node
* The node the comment is attached to.
Angie Byron
committed
* @return
* A structured array of links.
*/
Angie Byron
committed
function comment_links(Comment $comment, EntityInterface $node) {
Angie Byron
committed
$links = array();
if ($node->comment == COMMENT_NODE_OPEN) {
if ($comment->access('delete')) {
$links['comment-delete'] = array(
Angie Byron
committed
'title' => t('delete'),
'href' => "comment/{$comment->id()}/delete",
Angie Byron
committed
'html' => TRUE,
);
}
if ($comment->access('update')) {
$links['comment-edit'] = array(
Angie Byron
committed
'title' => t('edit'),
'href' => "comment/{$comment->id()}/edit",
Angie Byron
committed
'html' => TRUE,
);
}
if ($comment->access('create')) {
$links['comment-reply'] = array(
Angie Byron
committed
'title' => t('reply'),
Dries Buytaert
committed
'href' => "comment/reply/{$comment->nid->target_id}/{$comment->id()}",
Angie Byron
committed
'html' => TRUE,
);
}
if ($comment->status->value == COMMENT_NOT_PUBLISHED && $comment->access('approve')) {
$links['comment-approve'] = array(
'title' => t('approve'),
'href' => "comment/{$comment->id()}/approve",
'html' => TRUE,
'query' => array('token' => drupal_get_token("comment/{$comment->id()}/approve")),
);
}
if (empty($links)) {
Dries Buytaert
committed
$links['comment-forbidden']['title'] = theme('comment_post_forbidden', array('node' => $node));
$links['comment-forbidden']['html'] = TRUE;
Angie Byron
committed
}
}
Angie Byron
committed
// Add translations link for translation-enabled comment bundles.
if (module_exists('translation_entity') && translation_entity_translate_access($comment)) {
$links['comment-translations'] = array(
'title' => t('translations'),
'href' => 'comment/' . $comment->id() . '/translations',
'html' => TRUE,
);
}
Angie Byron
committed
return $links;
}
Dries Buytaert
committed
/**
* Constructs render array from an array of loaded comments.
Dries Buytaert
committed
*
* @param $comments
* An array of comments as returned by comment_load_multiple().
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
Dries Buytaert
committed
* @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.
*
Dries Buytaert
committed
* @return
* An array in the format expected by drupal_render().
*
* @see drupal_render()
Dries Buytaert
committed
*/
Angie Byron
committed
function comment_view_multiple($comments, $view_mode = 'full', $langcode = NULL) {
return entity_view_multiple($comments, $view_mode, $langcode);
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Implements hook_form_FORM_ID_alter().
*/
Angie Byron
committed
function comment_form_node_type_form_alter(&$form, $form_state) {
if (isset($form['type'])) {
Dries Buytaert
committed
$form['comment'] = array(
'#type' => 'details',
Dries Buytaert
committed
'#title' => t('Comment settings'),
'#collapsed' => TRUE,
'#group' => 'additional_settings',
Dries Buytaert
committed
'#attributes' => array(
'class' => array('comment-node-type-settings-form'),
),
Dries Buytaert
committed
'#attached' => array(
'library' => array(array('comment', 'drupal.comment')),