5 * Enables users to comment on published content.
7 * When enabled, the Comment module creates a discussion board for each Drupal
8 * node. Users can post comments to discuss a forum topic, story, collaborative
13 * Comment is awaiting approval.
15 const COMMENT_NOT_PUBLISHED
= 0;
18 * Comment is published.
20 const COMMENT_PUBLISHED
= 1;
23 * Comments are displayed in a flat list - expanded.
25 const COMMENT_MODE_FLAT
= 0;
28 * Comments are displayed as a threaded list - expanded.
30 const COMMENT_MODE_THREADED
= 1;
33 * Anonymous posters cannot enter their contact information.
35 const COMMENT_ANONYMOUS_MAYNOT_CONTACT
= 0;
38 * Anonymous posters may leave their contact information.
40 const COMMENT_ANONYMOUS_MAY_CONTACT
= 1;
43 * Anonymous posters are required to leave their contact information.
45 const COMMENT_ANONYMOUS_MUST_CONTACT
= 2;
48 * Comment form should be displayed on a separate page.
50 const COMMENT_FORM_SEPARATE_PAGE
= 0;
53 * Comment form should be shown below post or list of comments.
55 const COMMENT_FORM_BELOW
= 1;
58 * Comments for this node are hidden.
60 const COMMENT_NODE_HIDDEN
= 0;
63 * Comments for this node are closed.
65 const COMMENT_NODE_CLOSED
= 1;
68 * Comments for this node are open.
70 const COMMENT_NODE_OPEN
= 2;
73 * Implements hook_help().
75 function comment_help($path, $arg) {
77 case
'admin/help#comment':
78 $output = '<h3>' .
t('About') .
'</h3>';
79 $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/handbook/modules/comment/')) .
'</p>';
80 $output .
= '<h3>' .
t('Uses') .
'</h3>';
82 $output .
= '<dt>' .
t('Default and custom settings') .
'</dt>';
83 $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>';
84 $output .
= '<dt>' .
t('Comment approval') .
'</dt>';
85 $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>';
92 * Implements hook_entity_info().
94 function comment_entity_info() {
97 'label' => t('Comment'),
98 'base table' => 'comment',
99 'uri callback' => 'comment_uri',
101 'controller class' => 'CommentStorageController',
102 'entity class' => 'Comment',
103 'entity keys' => array(
105 'bundle' => 'node_type',
106 'label' => 'subject',
108 'bundles' => array(),
109 'view modes' => array(
111 'label' => t('Full comment'),
112 'custom settings' => FALSE
,
115 'static cache' => FALSE
,
119 foreach (node_type_get_names() as
$type => $name) {
120 $return['comment']['bundles']['comment_node_' .
$type] = array(
121 'label' => t('@node_type comment', array('@node_type' => $name)),
122 // Provide the node type/bundle name for other modules, so it does not
123 // have to be extracted manually from the bundle name.
124 'node bundle' => $type,
126 // Place the Field UI paths for comments one level below the
127 // corresponding paths for nodes, so that they appear in the same set
128 // of local tasks. Note that the paths use a different placeholder name
129 // and thus a different menu loader callback, so that Field UI page
130 // callbacks get a comment bundle name from the node type in the URL.
131 // See comment_node_type_load() and comment_menu_alter().
132 'path' => 'admin/structure/types/manage/%comment_node_type/comment',
133 'bundle argument' => 4,
134 'real path' => 'admin/structure/types/manage/' .
str_replace('_', '-', $type) .
'/comment',
135 'access arguments' => array('administer content types'),
144 * Loads the comment bundle name corresponding a given content type.
146 * This function is used as a menu loader callback in comment_menu().
149 * The URL-formatted machine name of the node type whose comment fields are
150 * to be edited. 'URL-formatted' means that underscores are replaced by
154 * The comment bundle name corresponding to the node type.
156 * @see comment_menu_alter()
158 function comment_node_type_load($name) {
159 if ($type = node_type_get_type(strtr($name, array('-' => '_')))) {
160 return 'comment_node_' .
$type->type
;
165 * Entity uri callback.
167 function comment_uri($comment) {
169 'path' => 'comment/' .
$comment->cid
,
170 'options' => array('fragment' => 'comment-' .
$comment->cid
),
175 * Implements hook_field_extra_fields().
177 function comment_field_extra_fields() {
180 foreach (node_type_get_types() as
$type) {
181 if (variable_get('comment_subject_field_' .
$type->type
, 1) == 1) {
182 $return['comment']['comment_node_' .
$type->type
] = array(
185 'label' => t('Author'),
186 'description' => t('Author textfield'),
190 'label' => t('Subject'),
191 'description' => t('Subject textfield'),
203 * Implements hook_theme().
205 function comment_theme() {
207 'comment_block' => array(
208 'variables' => array(),
210 'comment_preview' => array(
211 'variables' => array('comment' => NULL
),
214 'template' => 'comment',
215 'render element' => 'elements',
217 'comment_post_forbidden' => array(
218 'variables' => array('node' => NULL
),
220 'comment_wrapper' => array(
221 'template' => 'comment-wrapper',
222 'render element' => 'content',
228 * Implements hook_menu().
230 function comment_menu() {
231 $items['admin/content/comment'] = array(
232 'title' => 'Comments',
233 'description' => 'List and edit site comments and the comment approval queue.',
234 'page callback' => 'comment_admin',
235 'access arguments' => array('administer comments'),
236 'type' => MENU_LOCAL_TASK
| MENU_NORMAL_ITEM
,
237 'file' => 'comment.admin.inc',
240 $items['admin/content/comment/new'] = array(
241 'title' => 'Published comments',
242 'type' => MENU_DEFAULT_LOCAL_TASK
,
245 $items['admin/content/comment/approval'] = array(
246 'title' => 'Unapproved comments',
247 'title callback' => 'comment_count_unpublished',
248 'page arguments' => array('approval'),
249 'access arguments' => array('administer comments'),
250 'type' => MENU_LOCAL_TASK
,
252 $items['comment/%'] = array(
253 'title' => 'Comment permalink',
254 'page callback' => 'comment_permalink',
255 'page arguments' => array(1),
256 'access arguments' => array('access comments'),
258 $items['comment/%/view'] = array(
259 'title' => 'View comment',
260 'type' => MENU_DEFAULT_LOCAL_TASK
,
263 // Every other comment path uses %, but this one loads the comment directly,
264 // so we don't end up loading it twice (in the page and access callback).
265 $items['comment/%comment/edit'] = array(
267 'page callback' => 'comment_edit_page',
268 'page arguments' => array(1),
269 'access callback' => 'comment_access',
270 'access arguments' => array('edit', 1),
271 'type' => MENU_LOCAL_TASK
,
274 $items['comment/%/approve'] = array(
275 'title' => 'Approve',
276 'page callback' => 'comment_approve',
277 'page arguments' => array(1),
278 'access arguments' => array('administer comments'),
279 'file' => 'comment.pages.inc',
282 $items['comment/%/delete'] = array(
284 'page callback' => 'comment_confirm_delete_page',
285 'page arguments' => array(1),
286 'access arguments' => array('administer comments'),
287 'type' => MENU_LOCAL_TASK
,
288 'file' => 'comment.admin.inc',
291 $items['comment/reply/%node'] = array(
292 'title' => 'Add new comment',
293 'page callback' => 'comment_reply',
294 'page arguments' => array(2),
295 'access callback' => 'node_access',
296 'access arguments' => array('view', 2),
297 'file' => 'comment.pages.inc',
304 * Implements hook_menu_alter().
306 function comment_menu_alter(&$items) {
307 // Add comments to the description for admin/content.
308 $items['admin/content']['description'] = 'Administer content and comments.';
310 // Adjust the Field UI tabs on admin/structure/types/manage/[node-type].
311 // See comment_entity_info().
312 $items['admin/structure/types/manage/%comment_node_type/comment/fields']['title'] = 'Comment fields';
313 $items['admin/structure/types/manage/%comment_node_type/comment/fields']['weight'] = 3;
314 $items['admin/structure/types/manage/%comment_node_type/comment/display']['title'] = 'Comment display';
315 $items['admin/structure/types/manage/%comment_node_type/comment/display']['weight'] = 4;
319 * Returns a menu title which includes the number of unapproved comments.
321 function comment_count_unpublished() {
322 $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE status = :status', array(
323 ':status' => COMMENT_NOT_PUBLISHED
,
325 return t('Unapproved comments (@count)', array('@count' => $count));
329 * Implements hook_node_type_insert().
331 * Creates a comment body field for a node type created while the Comment module
332 * is enabled. For node types created before the Comment module is enabled,
333 * hook_modules_enabled() serves to create the body fields.
335 * @see comment_modules_enabled()
337 function comment_node_type_insert($info) {
338 _comment_body_field_create($info);
342 * Implements hook_node_type_update().
344 function comment_node_type_update($info) {
345 if (!empty($info->old_type
) && $info->type
!= $info->old_type
) {
346 field_attach_rename_bundle('comment', 'comment_node_' .
$info->old_type
, 'comment_node_' .
$info->type
);
351 * Implements hook_node_type_delete().
353 function comment_node_type_delete($info) {
354 field_attach_delete_bundle('comment', 'comment_node_' .
$info->type
);
357 'comment_default_mode',
358 'comment_default_per_page',
360 'comment_subject_field',
362 'comment_form_location',
364 foreach ($settings as
$setting) {
365 variable_del($setting .
'_' .
$info->type
);
370 * Creates a comment_body field instance for a given node type.
373 * An object representing the content type. The only property that is
374 * currently used is $info->type, which is the machine name of the content
375 * type for which the body field (instance) is to be created.
377 function _comment_body_field_create($info) {
378 // Create the field if needed.
379 if (!field_read_field('comment_body', array('include_inactive' => TRUE
))) {
381 'field_name' => 'comment_body',
382 'type' => 'text_long',
383 'entity_types' => array('comment'),
385 field_create_field($field);
387 // Create the instance if needed.
388 if (!field_read_instance('comment', 'comment_body', 'comment_node_' .
$info->type
, array('include_inactive' => TRUE
))) {
389 field_attach_create_bundle('comment', 'comment_node_' .
$info->type
);
390 // Attaches the body field by default.
392 'field_name' => 'comment_body',
393 'label' => 'Comment',
394 'entity_type' => 'comment',
395 'bundle' => 'comment_node_' .
$info->type
,
396 'settings' => array('text_processing' => 1),
401 'type' => 'text_default',
406 field_create_instance($instance);
411 * Implements hook_permission().
413 function comment_permission() {
415 'administer comments' => array(
416 'title' => t('Administer comments and comment settings'),
418 'access comments' => array(
419 'title' => t('View comments'),
421 'post comments' => array(
422 'title' => t('Post comments'),
424 'skip comment approval' => array(
425 'title' => t('Skip comment approval'),
427 'edit own comments' => array(
428 'title' => t('Edit own comments'),
434 * Implements hook_block_info().
436 function comment_block_info() {
437 $blocks['recent']['info'] = t('Recent comments');
438 $blocks['recent']['properties']['administrative'] = TRUE
;
444 * Implements hook_block_configure().
446 function comment_block_configure($delta = '') {
447 $form['comment_block_count'] = array(
449 '#title' => t('Number of recent comments'),
450 '#default_value' => variable_get('comment_block_count', 10),
451 '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
458 * Implements hook_block_save().
460 function comment_block_save($delta = '', $edit = array()) {
461 variable_set('comment_block_count', (int) $edit['comment_block_count']);
465 * Implements hook_block_view().
467 * Generates a block with the most recent comments.
469 function comment_block_view($delta = '') {
470 if (user_access('access comments')) {
471 $block['subject'] = t('Recent comments');
472 $block['content'] = theme('comment_block');
479 * Redirects comment links to the correct page depending on comment settings.
481 * Since comments are paged there is no way to guarantee which page a comment
482 * appears on. Comment paging and threading settings may be changed at any time.
483 * With threaded comments, an individual comment may move between pages as
484 * comments can be added either before or after it in the overall discussion.
485 * Therefore we use a central routing function for comment links, which
486 * calculates the page number based on current comment settings and returns
487 * the full comment view with the pager set dynamically.
490 * A comment identifier.
493 * The comment listing set to the page on which the comment appears.
495 function comment_permalink($cid) {
496 if (($comment = comment_load($cid)) && ($node = node_load($comment->nid
))) {
498 // Find the current display page for this comment.
499 $page = comment_get_display_page($comment->cid
, $node->type
);
501 // Set $_GET['q'] and $_GET['page'] ourselves so that the node callback
502 // behaves as it would when visiting the page directly.
503 $_GET['q'] = 'node/' .
$node->nid
;
504 $_GET['page'] = $page;
506 // Return the node view, this will show the correct comment in context.
507 return menu_execute_active_handler('node/' .
$node->nid
, FALSE
);
513 * Finds the most recent comments that are available to the current user.
515 * @param integer $number
516 * (optional) The maximum number of comments to find. Defaults to 10.
519 * An array of comment objects or an empty array if there are no recent
520 * comments visible to the current user.
522 function comment_get_recent($number = 10) {
523 $query = db_select('comment', 'c');
524 $query->innerJoin('node', 'n', 'n.nid = c.nid');
525 $query->addTag('node_access');
528 ->condition('c.status', COMMENT_PUBLISHED
)
529 ->condition('n.status', NODE_PUBLISHED
)
530 ->orderBy('c.created', 'DESC')
531 // Additionally order by cid to ensure that comments with the same timestamp
532 // are returned in the exact order posted.
533 ->orderBy('c.cid', 'DESC')
538 return $comments ?
$comments : array();
542 * Calculates the page number for the first new comment.
544 * @param $num_comments
545 * Number of comments.
546 * @param $new_replies
547 * Number of new replies.
549 * The first new comment node.
552 * "page=X" if the page number is greater than zero; empty string otherwise.
554 function comment_new_page_count($num_comments, $new_replies, $node) {
555 $mode = variable_get('comment_default_mode_' .
$node->type
, COMMENT_MODE_THREADED
);
556 $comments_per_page = variable_get('comment_default_per_page_' .
$node->type
, 50);
558 $flat = $mode == COMMENT_MODE_FLAT ? TRUE
: FALSE
;
559 if ($num_comments <= $comments_per_page) {
560 // Only one page of comments.
565 $count = $num_comments - $new_replies;
566 $pageno = $count / $comments_per_page;
569 // Threaded comments: we build a query with a subquery to find the first
570 // thread with a new comment.
572 // 1. Find all the threads with a new comment.
573 $unread_threads_query = db_select('comment')
574 ->fields('comment', array('thread'))
575 ->condition('nid', $node->nid
)
576 ->condition('status', COMMENT_PUBLISHED
)
577 ->orderBy('created', 'DESC')
578 ->orderBy('cid', 'DESC')
579 ->range(0, $new_replies);
581 // 2. Find the first thread.
582 $first_thread = db_select($unread_threads_query, 'thread')
583 ->fields('thread', array('thread'))
584 ->orderBy('SUBSTRING(thread, 1, (LENGTH(thread) - 1))')
589 // Remove the final '/'.
590 $first_thread = substr($first_thread, 0, -1);
592 // Find the number of the first comment of the first unread thread.
593 $count = db_query('SELECT COUNT(*) FROM {comment} WHERE nid = :nid AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array(
594 ':status' => COMMENT_PUBLISHED
,
595 ':nid' => $node->nid
,
596 ':thread' => $first_thread,
599 $pageno = $count / $comments_per_page;
603 $pagenum = array('page' => intval($pageno));
610 * Returns HTML for a list of recent comments.
614 function theme_comment_block() {
616 $number = variable_get('comment_block_count', 10);
617 foreach (comment_get_recent($number) as
$comment) {
618 $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>';
622 return theme('item_list', array('items' => $items));
625 return t('No comments available.');
630 * Implements hook_node_view().
632 function comment_node_view($node, $view_mode) {
635 if ($node->comment
!= COMMENT_NODE_HIDDEN
) {
636 if ($view_mode == 'rss') {
637 // Add a comments RSS element which is a URL to the comments of this node.
638 $node->rss_elements
[] = array(
640 'value' => url('node/' .
$node->nid
, array('fragment' => 'comments', 'absolute' => TRUE
))
643 elseif ($view_mode == 'teaser') {
644 // Teaser view: display the number of comments that have been posted,
645 // or a link to add new comments if the user has permission, the node
646 // is open to new comments, and there currently are none.
647 if (user_access('access comments')) {
648 if (!empty($node->comment_count
)) {
649 $links['comment-comments'] = array(
650 'title' => format_plural($node->comment_count
, '1 comment', '@count comments'),
651 'href' => "node/$node->nid",
652 'attributes' => array('title' => t('Jump to the first comment of this posting.')),
653 'fragment' => 'comments',
656 // Show a link to the first new comment.
657 if ($new = comment_num_new($node->nid
)) {
658 $links['comment-new-comments'] = array(
659 'title' => format_plural($new, '1 new comment', '@count new comments'),
660 'href' => "node/$node->nid",
661 'query' => comment_new_page_count($node->comment_count
, $new, $node),
662 'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
669 if ($node->comment
== COMMENT_NODE_OPEN
) {
670 if (user_access('post comments')) {
671 $links['comment-add'] = array(
672 'title' => t('Add new comment'),
673 'href' => "comment/reply/$node->nid",
674 'attributes' => array('title' => t('Add a new comment to this page.')),
675 'fragment' => 'comment-form',
679 $links['comment-forbidden'] = array(
680 'title' => theme('comment_post_forbidden', array('node' => $node)),
686 elseif ($view_mode != 'search_index' && $view_mode != 'search_result') {
687 // Node in other view modes: add a "post comment" link if the user is
688 // allowed to post comments and if this node is allowing new comments.
689 // But we don't want this link if we're building the node for search
690 // indexing or constructing a search result excerpt.
691 if ($node->comment
== COMMENT_NODE_OPEN
) {
692 $comment_form_location = variable_get('comment_form_location_' .
$node->type
, COMMENT_FORM_BELOW
);
693 if (user_access('post comments')) {
694 // Show the "post comment" link if the form is on another page, or
695 // if there are existing comments that the link will skip past.
696 if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE
|| (!empty($node->comment_count
) && user_access('access comments'))) {
697 $links['comment-add'] = array(
698 'title' => t('Add new comment'),
699 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
700 'href' => "node/$node->nid",
701 'fragment' => 'comment-form',
703 if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE
) {
704 $links['comment-add']['href'] = "comment/reply/$node->nid";
709 $links['comment-forbidden'] = array(
710 'title' => theme('comment_post_forbidden', array('node' => $node)),
717 $node->content
['links']['comment'] = array(
718 '#theme' => 'links__node__comment',
720 '#attributes' => array('class' => array('links', 'inline')),
723 // Only append comments when we are building a node on its own node detail
724 // page. We compare $node and $page_node to ensure that comments are not
725 // appended to other nodes shown on the page, for example a node_reference
726 // displayed in 'full' view mode within another node.
727 if ($node->comment
&& $view_mode == 'full' && node_is_page($node) && empty($node->in_preview
)) {
728 $node->content
['comments'] = comment_node_page_additions($node);
734 * Builds the comment-related elements for node detail pages.
737 * The node object for which to build the comment-related elements.
740 * A renderable array representing the comment-related page elements for the
743 function comment_node_page_additions($node) {
744 $additions = array();
746 // Only attempt to render comments if the node has visible comments.
747 // Unpublished comments are not included in $node->comment_count, so show
748 // comments unconditionally if the user is an administrator.
749 if (($node->comment_count
&& user_access('access comments')) || user_access('administer comments')) {
750 $mode = variable_get('comment_default_mode_' .
$node->type
, COMMENT_MODE_THREADED
);
751 $comments_per_page = variable_get('comment_default_per_page_' .
$node->type
, 50);
752 if ($cids = comment_get_thread($node, $mode, $comments_per_page)) {
753 $comments = comment_load_multiple($cids);
754 comment_prepare_thread($comments);
755 $build = comment_view_multiple($comments, $node);
756 $build['pager']['#theme'] = 'pager';
757 $additions['comments'] = $build;
761 // Append comment form if needed.
762 if (user_access('post comments') && $node->comment
== COMMENT_NODE_OPEN
&& (variable_get('comment_form_location_' .
$node->type
, COMMENT_FORM_BELOW
) == COMMENT_FORM_BELOW
)) {
763 $comment = entity_create('comment', array('nid' => $node->nid
));
764 $additions['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment);
769 '#theme' => 'comment_wrapper__node_' .
$node->type
,
771 'comments' => array(),
772 'comment_form' => array(),
780 * Retrieves comments for a thread.
783 * The node whose comment(s) needs rendering.
785 * The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED.
786 * @param $comments_per_page
787 * The amount of comments to display per page.
790 * An array of the IDs of the comment to be displayed.
792 * To display threaded comments in the correct order we keep a 'thread' field
793 * and order by that value. This field keeps this data in
794 * a way which is easy to update and convenient to use.
796 * A "thread" value starts at "1". If we add a child (A) to this comment,
797 * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
798 * brother of (A) will get "1.2". Next brother of the parent of (A) will get
801 * First of all note that the thread field stores the depth of the comment:
802 * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
804 * Now to get the ordering right, consider this example:
812 * If we "ORDER BY thread ASC" we get the above result, and this is the
813 * natural order sorted by time. However, if we "ORDER BY thread DESC"
822 * Clearly, this is not a natural way to see a thread, and users will get
823 * confused. The natural order to show a thread by time desc would be:
831 * which is what we already did before the standard pager patch. To achieve
832 * this we simply add a "/" at the end of each "thread" value. This way, the
833 * thread fields will look like this:
841 * we add "/" since this char is, in ASCII, higher than every number, so if
842 * now we "ORDER BY thread DESC" we get the correct order. However this would
843 * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
844 * to consider the trailing "/" so we use a substring only.
846 function comment_get_thread($node, $mode, $comments_per_page) {
847 $query = db_select('comment', 'c')->extend('PagerDefault');
848 $query->addField('c', 'cid');
850 ->condition('c.nid', $node->nid
)
851 ->addTag('node_access')
852 ->addTag('comment_filter')
853 ->addMetaData('node', $node)
854 ->limit($comments_per_page);
856 $count_query = db_select('comment', 'c');
857 $count_query->addExpression('COUNT(*)');
859 ->condition('c.nid', $node->nid
)
860 ->addTag('node_access')
861 ->addTag('comment_filter')
862 ->addMetaData('node', $node);
864 if (!user_access('administer comments')) {
865 $query->condition('c.status', COMMENT_PUBLISHED
);
866 $count_query->condition('c.status', COMMENT_PUBLISHED
);
868 if ($mode === COMMENT_MODE_FLAT
) {
869 $query->orderBy('c.cid', 'ASC');
872 // See comment above. Analysis reveals that this doesn't cost too
873 // much. It scales much much better than having the whole comment
875 $query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
876 $query->orderBy('torder', 'ASC');
879 $query->setCountQuery($count_query);
880 $cids = $query->execute()->fetchCol();
886 * Calculates the indentation level of each comment in a comment thread.
888 * This function loops over an array representing a comment thread. For each
889 * comment, the function calculates the indentation level and saves it in the
890 * 'divs' property of the comment object.
892 * @param array $comments
893 * An array of comment objects, keyed by comment ID.
895 function comment_prepare_thread(&$comments) {
896 // A flag stating if we are still searching for first new comment on the thread.
899 // A counter that helps track how indented we are.
902 foreach ($comments as
$key => $comment) {
903 if ($first_new && $comment->new
!= MARK_READ
) {
904 // Assign the anchor only for the first new comment. This avoids duplicate
905 // id attributes on a page.
907 $comment->first_new
= TRUE
;
910 // The $divs element instructs #prefix whether to add an indent div or
911 // close existing divs (a negative value).
912 $comment->depth
= count(explode('.', $comment->thread
)) - 1;
913 if ($comment->depth
> $divs) {
918 $comment->divs
= $comment->depth
- $divs;
919 while ($comment->depth
< $divs) {
923 $comments[$key] = $comment;
926 // The final comment must close up some hanging divs
927 $comments[$key]->divs_final
= $divs;
931 * Generates an array for rendering a comment.
934 * The comment object.
936 * The node the comment is attached to.
938 * View mode, e.g. 'full', 'teaser'...
940 * (optional) A language code to use for rendering. Defaults to the global
941 * content language of the current request.
944 * An array as expected by drupal_render().
946 function comment_view($comment, $node, $view_mode = 'full', $langcode = NULL
) {
947 if (!isset($langcode)) {
948 $langcode = $GLOBALS['language_content']->langcode
;
951 // Populate $comment->content with a render() array.
952 comment_build_content($comment, $node, $view_mode, $langcode);
954 $build = $comment->content
;
955 // We don't need duplicate rendering info in comment->content.
956 unset($comment->content
);
959 '#theme' => 'comment__node_' .
$node->type
,
960 '#comment' => $comment,
962 '#view_mode' => $view_mode,
963 '#language' => $langcode,
966 if (empty($comment->in_preview
)) {
968 $is_threaded = isset($comment->divs
) && variable_get('comment_default_mode_' .
$node->type
, COMMENT_MODE_THREADED
) == COMMENT_MODE_THREADED
;
970 // Add 'new' anchor if needed.
971 if (!empty($comment->first_new
)) {
972 $prefix .
= "<a id=\"new\"></a>\n";
975 // Add indentation div or close open divs as needed.
977 $prefix .
= $comment->divs
<= 0 ?
str_repeat('</div>', abs($comment->divs
)) : "\n" .
'<div class="indented">';
980 // Add anchor for each comment.
981 $prefix .
= "<a id=\"comment-$comment->cid\"></a>\n";
982 $build['#prefix'] = $prefix;
984 // Close all open divs.
985 if ($is_threaded && !empty($comment->divs_final
)) {
986 $build['#suffix'] = str_repeat('</div>', $comment->divs_final
);
990 // Allow modules to modify the structured comment.
992 drupal_alter(array('comment_view', 'entity_view'), $build, $type);
998 * Builds a structured array representing the comment's content.
1000 * The content built for the comment (field values, comments, file attachments
1001 * or other comment components) will vary depending on the $view_mode parameter.
1006 * The node the comment is attached to.
1008 * View mode, e.g. 'full', 'teaser'...
1010 * (optional) A language code to use for rendering. Defaults to the global
1011 * content language of the current request.
1013 function comment_build_content($comment, $node, $view_mode = 'full', $langcode = NULL
) {
1014 if (!isset($langcode)) {
1015 $langcode = $GLOBALS['language_content']->langcode
;
1018 // Remove previously built content, if exists.
1019 $comment->content
= array();
1021 // Build fields content.
1022 field_attach_prepare_view('comment', array($comment->cid
=> $comment), $view_mode, $langcode);
1023 entity_prepare_view('comment', array($comment->cid
=> $comment), $langcode);
1024 $comment->content
+= field_attach_view('comment', $comment, $view_mode, $langcode);
1026 $comment->content
['links'] = array(
1027 '#theme' => 'links__comment',
1028 '#pre_render' => array('drupal_pre_render_links'),
1029 '#attributes' => array('class' => array('links', 'inline')),
1031 if (empty($comment->in_preview
)) {
1032 $comment->content
['links']['comment'] = array(
1033 '#theme' => 'links__comment__comment',
1034 '#links' => comment_links($comment, $node),
1035 '#attributes' => array('class' => array('links', 'inline')),
1039 // Allow modules to make their own additions to the comment.
1040 module_invoke_all('comment_view', $comment, $view_mode, $langcode);
1041 module_invoke_all('entity_view', $comment, 'comment', $view_mode, $langcode);
1045 * Adds reply, edit, delete, etc. links, depending on user permissions.
1048 * The comment object.
1050 * The node the comment is attached to.
1053 * A structured array of links.
1055 function comment_links($comment, $node) {
1057 if ($node->comment
== COMMENT_NODE_OPEN
) {
1058 if (user_access('administer comments') && user_access('post comments')) {
1059 $links['comment-delete'] = array(
1060 'title' => t('delete'),
1061 'href' => "comment/$comment->cid/delete",
1064 $links['comment-edit'] = array(
1065 'title' => t('edit'),
1066 'href' => "comment/$comment->cid/edit",
1069 $links['comment-reply'] = array(
1070 'title' => t('reply'),
1071 'href' => "comment/reply/$comment->nid/$comment->cid",
1074 if ($comment->status
== COMMENT_NOT_PUBLISHED
) {
1075 $links['comment-approve'] = array(
1076 'title' => t('approve'),
1077 'href' => "comment/$comment->cid/approve",
1079 'query' => array('token' => drupal_get_token("comment/$comment->cid/approve")),
1083 elseif (user_access('post comments')) {
1084 if (comment_access('edit', $comment)) {
1085 $links['comment-edit'] = array(
1086 'title' => t('edit'),
1087 'href' => "comment/$comment->cid/edit",
1091 $links['comment-reply'] = array(
1092 'title' => t('reply'),
1093 'href' => "comment/reply/$comment->nid/$comment->cid",
1098 $links['comment-forbidden']['title'] = theme('comment_post_forbidden', array('node' => $node));
1099 $links['comment-forbidden']['html'] = TRUE
;
1106 * Constructs render array from an array of loaded comments.
1109 * An array of comments as returned by comment_load_multiple().
1111 * The node the comments are attached to.
1113 * View mode, e.g. 'full', 'teaser'...
1115 * An integer representing the weight of the first comment in the list.
1117 * A string indicating the language field values are to be shown in. If no
1118 * language is provided the current content language is used.
1121 * An array in the format expected by drupal_render().
1123 * @see drupal_render()
1125 function comment_view_multiple($comments, $node, $view_mode = 'full', $weight = 0, $langcode = NULL
) {
1126 field_attach_prepare_view('comment', $comments, $view_mode, $langcode);
1127 entity_prepare_view('comment', $comments, $langcode);
1132 foreach ($comments as
$comment) {
1133 $build[$comment->cid
] = comment_view($comment, $node, $view_mode, $langcode);
1134 $build[$comment->cid
]['#weight'] = $weight;
1141 * Implements hook_form_FORM_ID_alter().
1143 function comment_form_node_type_form_alter(&$form, $form_state) {
1144 if (isset($form['type'])) {
1145 $form['comment'] = array(
1146 '#type' => 'fieldset',
1147 '#title' => t('Comment settings'),
1148 '#collapsible' => TRUE
,
1149 '#collapsed' => TRUE
,
1150 '#group' => 'additional_settings',
1151 '#attributes' => array(
1152 'class' => array('comment-node-type-settings-form'),
1154 '#attached' => array(
1155 'js' => array(drupal_get_path('module', 'comment') .
'/comment-node-form.js'),
1158 // Unlike coment_form_node_form_alter(), all of these settings are applied
1159 // as defaults to all new nodes. Therefore, it would be wrong to use #states
1160 // to hide the other settings based on the primary comment setting.
1161 $form['comment']['comment'] = array(
1162 '#type' => 'select',
1163 '#title' => t('Default comment setting for new content'),
1164 '#default_value' => variable_get('comment_' .
$form['#node_type']->type
, COMMENT_NODE_OPEN
),
1165 '#options' => array(
1166 COMMENT_NODE_OPEN
=> t('Open'),
1167 COMMENT_NODE_CLOSED
=> t('Closed'),
1168 COMMENT_NODE_HIDDEN
=> t('Hidden'),
1171 $form['comment']['comment_default_mode'] = array(
1172 '#type' => 'checkbox',
1173 '#title' => t('Threading'),
1174 '#default_value' => variable_get('comment_default_mode_' .
$form['#node_type']->type
, COMMENT_MODE_THREADED
),
1175 '#description' => t('Show comment replies in a threaded list.'),
1177 $form['comment']['comment_default_per_page'] = array(
1178 '#type' => 'select',
1179 '#title' => t('Comments per page'),
1180 '#default_value' => variable_get('comment_default_per_page_' .
$form['#node_type']->type
, 50),
1181 '#options' => _comment_per_page(),
1183 $form['comment']['comment_anonymous'] = array(
1184 '#type' => 'select',
1185 '#title' => t('Anonymous commenting'),
1186 '#default_value' => variable_get('comment_anonymous_' .
$form['#node_type']->type
, COMMENT_ANONYMOUS_MAYNOT_CONTACT
),
1187 '#options' => array(
1188 COMMENT_ANONYMOUS_MAYNOT_CONTACT
=> t('Anonymous posters may not enter their contact information'),
1189 COMMENT_ANONYMOUS_MAY_CONTACT
=> t('Anonymous posters may leave their contact information'),
1190 COMMENT_ANONYMOUS_MUST_CONTACT
=> t('Anonymous posters must leave their contact information'),
1192 '#access' => user_access('post comments', drupal_anonymous_user()),
1194 $form['comment']['comment_subject_field'] = array(
1195 '#type' => 'checkbox',
1196 '#title' => t('Allow comment title'),
1197 '#default_value' => variable_get('comment_subject_field_' .
$form['#node_type']->type
, 1),
1199 $form['comment']['comment_form_location'] = array(
1200 '#type' => 'checkbox',
1201 '#title' => t('Show reply form on the same page as comments'),
1202 '#default_value' => variable_get('comment_form_location_' .
$form['#node_type']->type
, COMMENT_FORM_BELOW
),
1204 $form['comment']['comment_preview'] = array(
1205 '#type' => 'radios',
1206 '#title' => t('Preview comment'),
1207 '#default_value' => variable_get('comment_preview_' .
$form['#node_type']->type
, DRUPAL_OPTIONAL
),
1208 '#options' => array(
1209 DRUPAL_DISABLED
=> t('Disabled'),
1210 DRUPAL_OPTIONAL
=> t('Optional'),
1211 DRUPAL_REQUIRED
=> t('Required'),
1218 * Implements hook_form_BASE_FORM_ID_alter().
1220 function comment_form_node_form_alter(&$form, $form_state) {
1221 $node = $form['#node'];
1222 $form['comment_settings'] = array(
1223 '#type' => 'fieldset',
1224 '#access' => user_access('administer comments'),
1225 '#title' => t('Comment settings'),
1226 '#collapsible' => TRUE
,
1227 '#collapsed' => TRUE
,
1228 '#group' => 'additional_settings',
1229 '#attributes' => array(
1230 'class' => array('comment-node-settings-form'),
1232 '#attached' => array(
1233 'js' => array(drupal_get_path('module', 'comment') .
'/comment-node-form.js'),
1237 $comment_count = isset($node->nid
) ?
db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array(':nid' => $node->nid
))->fetchField() : 0;
1238 $comment_settings = ($node->comment
== COMMENT_NODE_HIDDEN
&& empty($comment_count)) ? COMMENT_NODE_CLOSED
: $node->comment
;
1239 $form['comment_settings']['comment'] = array(
1240 '#type' => 'radios',
1241 '#title' => t('Comments'),
1242 '#title_display' => 'invisible',
1243 '#parents' => array('comment'),
1244 '#default_value' => $comment_settings,
1245 '#options' => array(
1246 COMMENT_NODE_OPEN
=> t('Open'),
1247 COMMENT_NODE_CLOSED
=> t('Closed'),
1248 COMMENT_NODE_HIDDEN
=> t('Hidden'),
1250 COMMENT_NODE_OPEN
=> array(
1251 '#description' => t('Users with the "Post comments" permission can post comments.'),
1253 COMMENT_NODE_CLOSED
=> array(
1254 '#description' => t('Users cannot post comments, but existing comments will be displayed.'),
1256 COMMENT_NODE_HIDDEN
=> array(
1257 '#description' => t('Comments are hidden from view.'),
1260 // If the node doesn't have any comments, the "hidden" option makes no
1261 // sense, so don't even bother presenting it to the user.
1262 if (empty($comment_count)) {
1263 $form['comment_settings']['comment'][COMMENT_NODE_HIDDEN
]['#access'] = FALSE
;
1264 // Also adjust the description of the "closed" option.
1265 $form['comment_settings']['comment'][COMMENT_NODE_CLOSED
]['#description'] = t('Users cannot post comments.');
1270 * Implements hook_node_load().
1272 function comment_node_load($nodes, $types) {
1273 $comments_enabled = array();
1275 // Check if comments are enabled for each node. If comments are disabled,
1276 // assign values without hitting the database.
1277 foreach ($nodes as
$node) {
1278 // Store whether comments are enabled for this node.
1279 if ($node->comment
!= COMMENT_NODE_HIDDEN
) {
1280 $comments_enabled[] = $node->nid
;
1284 $node->last_comment_timestamp
= $node->created
;
1285 $node->last_comment_name
= '';
1286 $node->last_comment_uid
= $node->uid
;
1287 $node->comment_count
= 0;
1291 // For nodes with comments enabled, fetch information from the database.
1292 if (!empty($comments_enabled)) {
1293 $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));
1294 foreach ($result as
$record) {
1295 $nodes[$record->nid
]->cid
= $record->cid
;
1296 $nodes[$record->nid
]->last_comment_timestamp
= $record->last_comment_timestamp
;
1297 $nodes[$record->nid
]->last_comment_name
= $record->last_comment_name
;
1298 $nodes[$record->nid
]->last_comment_uid
= $record->last_comment_uid
;
1299 $nodes[$record->nid
]->comment_count
= $record->comment_count
;
1305 * Implements hook_node_prepare().
1307 function comment_node_prepare($node) {
1308 if (!isset($node->comment
)) {
1309 $node->comment
= variable_get("comment_$node->type", COMMENT_NODE_OPEN
);
1314 * Implements hook_node_insert().
1316 function comment_node_insert($node) {
1317 // Allow bulk updates and inserts to temporarily disable the
1318 // maintenance of the {node_comment_statistics} table.
1319 if (variable_get('comment_maintain_node_statistics', TRUE
)) {
1320 db_insert('node_comment_statistics')
1322 'nid' => $node->nid
,
1324 'last_comment_timestamp' => $node->changed
,
1325 'last_comment_name' => NULL
,
1326 'last_comment_uid' => $node->uid
,
1327 'comment_count' => 0,
1334 * Implements hook_node_predelete().
1336 function comment_node_predelete($node) {
1337 $cids = db_query('SELECT cid FROM {comment} WHERE nid = :nid', array(':nid' => $node->nid
))->fetchCol();
1338 comment_delete_multiple($cids);
1339 db_delete('node_comment_statistics')
1340 ->condition('nid', $node->nid
)
1345 * Implements hook_node_update_index().
1347 function comment_node_update_index($node) {
1348 $index_comments = &drupal_static(__FUNCTION__
);
1350 if ($index_comments === NULL
) {
1351 // Find and save roles that can 'access comments' or 'search content'.
1352 $perms = array('access comments' => array(), 'search content' => array());
1353 $result = db_query("SELECT rid, permission FROM {role_permission} WHERE permission IN ('access comments', 'search content')");
1354 foreach ($result as
$record) {
1355 $perms[$record->permission
][$record->rid
] = $record->rid
;
1358 // Prevent indexing of comments if there are any roles that can search but
1359 // not view comments.
1360 $index_comments = TRUE
;
1361 foreach ($perms['search content'] as
$rid) {
1362 if (!isset($perms['access comments'][$rid]) && ($rid <= DRUPAL_AUTHENTICATED_RID
|| !isset($perms['access comments'][DRUPAL_AUTHENTICATED_RID
]))) {
1363 $index_comments = FALSE
;
1369 if ($index_comments) {
1370 $mode = variable_get('comment_default_mode_' .
$node->type
, COMMENT_MODE_THREADED
);
1371 $comments_per_page = variable_get('comment_default_per_page_' .
$node->type
, 50);
1372 if ($node->comment
&& $cids = comment_get_thread($node, $mode, $comments_per_page)) {
1373 $comments = comment_load_multiple($cids);
1374 comment_prepare_thread($comments);
1375 $build = comment_view_multiple($comments, $node);
1376 return drupal_render($build);
1383 * Implements hook_update_index().
1385 function comment_update_index() {
1386 // Store the maximum possible comments per thread (used for ranking by reply count)
1387 variable_set('node_cron_comments_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}')->fetchField()));
1391 * Implements hook_node_search_result().
1393 * Formats a comment count string and returns it, for display with search
1396 function comment_node_search_result($node) {
1397 // Do not make a string if comments are hidden.
1398 if (user_access('access comments') && $node->comment
!= COMMENT_NODE_HIDDEN
) {
1399 $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid
))->fetchField();
1400 // Do not make a string if comments are closed and there are currently
1402 if ($node->comment
!= COMMENT_NODE_CLOSED
|| $comments > 0) {
1403 return array('comment' => format_plural($comments, '1 comment', '@count comments'));
1409 * Implements hook_user_cancel().
1411 function comment_user_cancel($edit, $account, $method) {
1413 case
'user_cancel_block_unpublish':
1414 $comments = comment_load_multiple(array(), array('uid' => $account->uid
));
1415 foreach ($comments as
$comment) {
1416 $comment->status
= 0;
1417 comment_save($comment);
1421 case
'user_cancel_reassign':
1422 $comments = comment_load_multiple(array(), array('uid' => $account->uid
));
1423 foreach ($comments as
$comment) {
1425 comment_save($comment);
1432 * Implements hook_user_predelete().
1434 function comment_user_predelete($account) {
1435 $cids = db_query('SELECT c.cid FROM {comment} c WHERE uid = :uid', array(':uid' => $account->uid
))->fetchCol();
1436 comment_delete_multiple($cids);
1440 * Determines whether the current user has access to a particular comment.
1442 * Authenticated users can edit their comments as long they have not been
1443 * replied to. This prevents people from changing or revising their statements
1444 * based on the replies to their posts.
1447 * The operation that is to be performed on the comment. Only 'edit' is
1450 * The comment object.
1453 * TRUE if the current user has acces to the comment, FALSE otherwise.
1455 function comment_access($op, $comment) {
1458 if ($op == 'edit') {
1459 return ($user->uid
&& $user->uid
== $comment->uid
&& $comment->status
== COMMENT_PUBLISHED
&& user_access('edit own comments')) || user_access('administer comments');
1464 * Accepts a submission of new or changed comment content.
1469 function comment_save($comment) {
1474 * Deletes a comment and all its replies.
1477 * The ID of the comment to delete.
1479 function comment_delete($cid) {
1480 comment_delete_multiple(array($cid));
1484 * Deletes comments and all their replies.
1487 * The IDs of the comments to delete.
1489 * @see hook_comment_predelete()
1490 * @see hook_comment_delete()
1492 function comment_delete_multiple($cids) {
1493 entity_delete_multiple('comment', $cids);
1497 * Loads comments from the database.
1500 * An array of comment IDs.
1501 * @param $conditions
1502 * (deprecated) An associative array of conditions on the {comments}
1503 * table, where the keys are the database fields and the values are the
1504 * values those fields must have. Instead, it is preferable to use
1505 * EntityFieldQuery to retrieve a list of entity IDs loadable by
1508 * Whether to reset the internal static entity cache. Note that the static
1509 * cache is disabled in comment_entity_info() by default.
1512 * An array of comment objects, indexed by comment ID.
1514 * @todo Remove $conditions in Drupal 8.
1516 * @see entity_load()
1517 * @see EntityFieldQuery
1519 function comment_load_multiple($cids = array(), $conditions = array(), $reset = FALSE
) {
1520 return entity_load('comment', $cids, $conditions, $reset);
1524 * Loads the entire comment by comment ID.
1527 * The ID of the comment to be loaded.
1529 * Whether to reset the internal static entity cache. Note that the static
1530 * cache is disabled in comment_entity_info() by default.
1533 * The comment object.
1535 function comment_load($cid, $reset = FALSE
) {
1536 $comment = comment_load_multiple(array($cid), array(), $reset);
1537 return $comment ?
$comment[$cid] : FALSE
;
1541 * Gets the number of new comments for the current user and the specified node.
1544 * Node ID to count comments for.
1546 * Time to count from (defaults to time of last user access
1550 * The number of new comments or FALSE if the user is not logged in.
1552 function comment_num_new($nid, $timestamp = 0) {
1556 // Retrieve the timestamp at which the current user last viewed this node.
1558 $timestamp = node_last_viewed($nid);
1560 $timestamp = ($timestamp > NODE_NEW_LIMIT ?
$timestamp : NODE_NEW_LIMIT
);
1562 // Use the timestamp to retrieve the number of new comments.
1563 return db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND created > :timestamp AND status = :status', array(
1565 ':timestamp' => $timestamp,
1566 ':status' => COMMENT_PUBLISHED
,
1576 * Gets the display ordinal for a comment, starting from 0.
1578 * Count the number of comments which appear before the comment we want to
1579 * display, taking into account display settings and threading.
1584 * The node type of the comment's parent.
1587 * The display ordinal for the comment.
1589 * @see comment_get_display_page()
1591 function comment_get_display_ordinal($cid, $node_type) {
1592 // Count how many comments (c1) are before $cid (c2) in display order. This is
1593 // the 0-based display ordinal.
1594 $query = db_select('comment', 'c1');
1595 $query->innerJoin('comment', 'c2', 'c2.nid = c1.nid');
1596 $query->addExpression('COUNT(*)', 'count');
1597 $query->condition('c2.cid', $cid);
1598 if (!user_access('administer comments')) {
1599 $query->condition('c1.status', COMMENT_PUBLISHED
);
1601 $mode = variable_get('comment_default_mode_' .
$node_type, COMMENT_MODE_THREADED
);
1603 if ($mode == COMMENT_MODE_FLAT
) {
1604 // For flat comments, cid is used for ordering comments due to
1605 // unpredicatable behavior with timestamp, so we make the same assumption
1607 $query->condition('c1.cid', $cid, '<');
1610 // For threaded comments, the c.thread column is used for ordering. We can
1611 // use the sorting code for comparison, but must remove the trailing slash.
1612 // See comment_view_multiple().
1613 $query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) -1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) -1))');
1616 return $query->execute()->fetchField();
1620 * Returns the page number for a comment.
1622 * Finds the correct page number for a comment taking into account display
1623 * and paging settings.
1628 * The node type the comment is attached to.
1633 function comment_get_display_page($cid, $node_type) {
1634 $ordinal = comment_get_display_ordinal($cid, $node_type);
1635 $comments_per_page = variable_get('comment_default_per_page_' .
$node_type, 50);
1636 return floor($ordinal / $comments_per_page);
1640 * Page callback: Displays the comment editing form.
1642 * Path: comment/%comment/edit
1645 * The comment object representing the comment to be edited.
1647 * @see comment_menu()
1649 function comment_edit_page($comment) {
1650 drupal_set_title(t('Edit comment %comment', array('%comment' => $comment->subject
)), PASS_THROUGH
);
1651 $node = node_load($comment->nid
);
1652 return drupal_get_form("comment_node_{$node->type}_form", $comment);
1656 * Implements hook_forms().
1658 function comment_forms() {
1660 foreach (node_type_get_types() as
$type) {
1661 $forms["comment_node_{$type->type}_form"]['callback'] = 'comment_form';
1667 * Form constructor for the basic commenting form.
1669 * @see comment_form_validate()
1670 * @see comment_form_submit()
1671 * @see comment_form_build_preview()
1674 function comment_form($form, &$form_state, $comment) {
1675 global $user, $language_content;
1677 // During initial form build, add the comment entity to the form state for
1678 // use during form building and processing. During a rebuild, use what is in
1680 if (!isset($form_state['comment'])) {
1681 $form_state['comment'] = $comment;
1684 $comment = $form_state['comment'];
1687 $node = node_load($comment->nid
);
1688 $form['#node'] = $node;
1690 // Use #comment-form as unique jump target, regardless of node type.
1691 $form['#id'] = drupal_html_id('comment_form');
1692 $form['#theme'] = array('comment_form__node_' .
$node->type
, 'comment_form');
1694 $anonymous_contact = variable_get('comment_anonymous_' .
$node->type
, COMMENT_ANONYMOUS_MAYNOT_CONTACT
);
1695 $is_admin = (!empty($comment->cid
) && user_access('administer comments'));
1697 if (!$user->uid
&& $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT
) {
1698 $form['#attached']['library'][] = array('system', 'jquery.cookie');
1699 $form['#attributes']['class'][] = 'user-info-from-cookie';
1702 // If not replying to a comment, use our dedicated page callback for new
1703 // comments on nodes.
1704 if (empty($comment->cid
) && empty($comment->pid
)) {
1705 $form['#action'] = url('comment/reply/' .
$comment->nid
);
1708 if (isset($form_state['comment_preview'])) {
1709 $form += $form_state['comment_preview'];
1712 // Display author information in a fieldset for comment moderators.
1714 $form['author'] = array(
1715 '#type' => 'fieldset',
1716 '#title' => t('Administration'),
1717 '#collapsible' => TRUE
,
1718 '#collapsed' => TRUE
,
1723 // Sets the author form elements above the subject.
1724 $form['author'] = array(
1729 // Prepare default values for form elements.
1731 $author = (!$comment->uid
&& $comment->name ?
$comment->name
: $comment->registered_name
);
1732 $status = (isset($comment->status
) ?
$comment->status
: COMMENT_NOT_PUBLISHED
);
1733 $date = (!empty($comment->date) ?
$comment->date : format_date($comment->created
, 'custom', 'Y-m-d H:i O'));
1737 $author = $user->name
;
1740 $author = ($comment->name ?
$comment->name
: '');
1742 $status = (user_access('skip comment approval') ? COMMENT_PUBLISHED
: COMMENT_NOT_PUBLISHED
);
1746 // Add the author name field depending on the current user.
1748 $form['author']['name'] = array(
1749 '#type' => 'textfield',
1750 '#title' => t('Authored by'),
1751 '#default_value' => $author,
1754 '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
1755 '#autocomplete_path' => 'user/autocomplete',
1758 elseif ($user->uid
) {
1759 $form['author']['_author'] = array(
1761 '#title' => t('Your name'),
1762 '#markup' => theme('username', array('account' => $user)),
1764 $form['author']['name'] = array(
1766 '#value' => $author,
1770 $form['author']['name'] = array(
1771 '#type' => 'textfield',
1772 '#title' => t('Your name'),
1773 '#default_value' => $author,
1774 '#required' => (!$user->uid
&& $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT
),
1780 // Add author e-mail and homepage fields depending on the current user.
1781 $form['author']['mail'] = array(
1782 '#type' => 'textfield',
1783 '#title' => t('E-mail'),
1784 '#default_value' => $comment->mail,
1785 '#required' => (!$user->uid
&& $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT
),
1788 '#description' => t('The content of this field is kept private and will not be shown publicly.'),
1789 '#access' => $is_admin || (!$user->uid
&& $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT
),
1791 $form['author']['homepage'] = array(
1792 '#type' => 'textfield',
1793 '#title' => t('Homepage'),
1794 '#default_value' => $comment->homepage
,
1795 '#maxlength' => 255,
1797 '#access' => $is_admin || (!$user->uid
&& $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT
),
1800 // Add administrative comment publishing options.
1801 $form['author']['date'] = array(
1802 '#type' => 'textfield',
1803 '#title' => t('Authored on'),
1804 '#default_value' => $date,
1807 '#access' => $is_admin,
1809 $form['author']['status'] = array(
1810 '#type' => 'radios',
1811 '#title' => t('Status'),
1812 '#default_value' => $status,
1813 '#options' => array(
1814 COMMENT_PUBLISHED
=> t('Published'),
1815 COMMENT_NOT_PUBLISHED
=> t('Not published'),
1817 '#access' => $is_admin,
1820 $form['subject'] = array(
1821 '#type' => 'textfield',
1822 '#title' => t('Subject'),
1824 '#default_value' => $comment->subject
,
1825 '#access' => variable_get('comment_subject_field_' .
$node->type
, 1) == 1,
1829 // Used for conditional validation of author fields.
1830 $form['is_anonymous'] = array(
1832 '#value' => ($comment->cid ?
!$comment->uid
: !$user->uid
),
1835 // Add internal comment properties.
1836 foreach (array('cid', 'pid', 'nid', 'uid') as
$key) {
1837 $form[$key] = array('#type' => 'value', '#value' => $comment->$key);
1839 $form['node_type'] = array('#type' => 'value', '#value' => 'comment_node_' .
$node->type
);
1841 // If a content type has multilingual support we set the comment to inherit the
1842 // content language. Otherwise mark the comment as language neutral.
1843 $comment_langcode = $comment->language
;
1844 if (($comment_langcode == LANGUAGE_NONE
) && variable_get('language_content_type_' .
$node->type
, 0)) {
1845 $comment_langcode = $language_content->langcode
;
1847 $form['language'] = array(
1849 '#value' => $comment_langcode,
1852 // Only show the save button if comment previews are optional or if we are
1853 // already previewing the submission.
1854 $form['actions'] = array('#type' => 'actions');
1855 $form['actions']['submit'] = array(
1856 '#type' => 'submit',
1857 '#value' => t('Save'),
1858 '#access' => ($comment->cid
&& user_access('administer comments')) || variable_get('comment_preview_' .
$node->type
, DRUPAL_OPTIONAL
) != DRUPAL_REQUIRED
|| isset($form_state['comment_preview']),
1861 $form['actions']['preview'] = array(
1862 '#type' => 'submit',
1863 '#value' => t('Preview'),
1864 '#access' => (variable_get('comment_preview_' .
$node->type
, DRUPAL_OPTIONAL
) != DRUPAL_DISABLED
),
1866 '#submit' => array('comment_form_build_preview'),
1870 $comment->node_type
= 'comment_node_' .
$node->type
;
1871 field_attach_form('comment', $comment, $form, $form_state);
1877 * Form submission handler for the 'preview' button in comment_form().
1879 function comment_form_build_preview($form, &$form_state) {
1880 $comment = comment_form_submit_build_comment($form, $form_state);
1881 $form_state['comment_preview'] = comment_preview($comment);
1882 $form_state['rebuild'] = TRUE
;
1886 * Generates a comment preview.
1888 * @see comment_form_build_preview()
1890 function comment_preview($comment) {
1893 drupal_set_title(t('Preview comment'), PASS_THROUGH
);
1895 $node = node_load($comment->nid
);
1897 if (!form_get_errors()) {
1898 $comment->format
= $comment->comment_body
[LANGUAGE_NONE
][0]['format'];
1899 // Attach the user and time information.
1900 if (!empty($comment->name
)) {
1901 $account = user_load_by_name($comment->name
);
1903 elseif ($user->uid
&& empty($comment->is_anonymous
)) {
1907 if (!empty($account->uid
)) {
1908 $comment->uid
= $account->uid
;
1909 $comment->name
= check_plain($account->name
);
1911 elseif (empty($comment->name
)) {
1912 $comment->name
= variable_get('anonymous', t('Anonymous'));
1915 $comment->created
= !empty($comment->created
) ?
$comment->created
: REQUEST_TIME
;
1916 $comment->changed
= REQUEST_TIME
;
1917 $comment->in_preview
= TRUE
;
1918 $comment_build = comment_view($comment, $node);
1919 $comment_build['#weight'] = -100;
1921 $form['comment_preview'] = $comment_build;
1924 if ($comment->pid
) {
1926 if ($comments = comment_load_multiple(array($comment->pid
), array('status' => COMMENT_PUBLISHED
))) {
1927 $parent_comment = $comments[$comment->pid
];
1928 $build = comment_view($parent_comment, $node);
1932 $build = node_view($node);
1935 $form['comment_output_below'] = $build;
1936 $form['comment_output_below']['#weight'] = 100;
1942 * Form validation handler for comment_form().
1944 * @see comment_form_submit()
1946 function comment_form_validate($form, &$form_state) {
1949 entity_form_field_validate('comment', $form, $form_state);
1951 if (!empty($form_state['values']['cid'])) {
1952 // Verify the name in case it is being changed from being anonymous.
1953 $account = user_load_by_name($form_state['values']['name']);
1954 $form_state['values']['uid'] = $account ?
$account->uid
: 0;
1956 if ($form_state['values']['date'] && strtotime($form_state['values']['date']) === FALSE
) {
1957 form_set_error('date', t('You have to specify a valid date.'));
1959 if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) {
1960 form_set_error('name', t('You have to specify a valid author.'));
1963 elseif ($form_state['values']['is_anonymous']) {
1964 // Validate anonymous comment author fields (if given). If the (original)
1965 // author of this comment was an anonymous user, verify that no registered
1966 // user with this name exists.
1967 if ($form_state['values']['name']) {
1968 $query = db_select('users', 'u');
1969 $query->addField('u', 'uid', 'uid');
1971 ->condition('name', db_like($form_state['values']['name']), 'LIKE')
1976 form_set_error('name', t('The name you used belongs to a registered user.'));
1980 if ($form_state['values']['mail'] && !valid_email_address($form_state['values']['mail'])) {
1981 form_set_error('mail', t('The e-mail address you specified is not valid.'));
1983 if ($form_state['values']['homepage'] && !valid_url($form_state['values']['homepage'], TRUE
)) {
1984 form_set_error('homepage', t('The URL of your homepage is not valid. Remember that it must be fully qualified, i.e. of the form <code>http://example.com/directory</code>.'));
1989 * Prepare a comment for submission.
1991 function comment_submit($comment) {
1992 if (empty($comment->date)) {
1993 $comment->date = 'now';
1995 $comment->created
= strtotime($comment->date);
1996 $comment->changed
= REQUEST_TIME
;
1998 // If the comment was posted by a registered user, assign the author's ID.
1999 // @todo Too fragile. Should be prepared and stored in comment_form() already.
2000 if (!$comment->is_anonymous
&& !empty($comment->name
) && ($account = user_load_by_name($comment->name
))) {
2001 $comment->uid
= $account->uid
;
2003 // If the comment was posted by an anonymous user and no author name was
2004 // required, use "Anonymous" by default.
2005 if ($comment->is_anonymous
&& (!isset($comment->name
) || $comment->name
=== '')) {
2006 $comment->name
= variable_get('anonymous', t('Anonymous'));
2009 // Validate the comment's subject. If not specified, extract from comment body.
2010 if (trim($comment->subject
) == '') {
2011 // The body may be in any format, so:
2012 // 1) Filter it into HTML
2013 // 2) Strip out all HTML tags
2014 // 3) Convert entities back to plain-text.
2015 $comment_body = $comment->comment_body
[LANGUAGE_NONE
][0];
2016 if (isset($comment_body['format'])) {
2017 $comment_text = check_markup($comment_body['value'], $comment_body['format']);
2020 $comment_text = check_plain($comment_body['value']);
2022 $comment->subject
= truncate_utf8(trim(decode_entities(strip_tags($comment_text))), 29, TRUE
);
2023 // Edge cases where the comment body is populated only by HTML tags will
2024 // require a default subject.
2025 if ($comment->subject
== '') {
2026 $comment->subject
= t('(No subject)');
2033 * Updates the comment entity by processing the submission's values.
2035 * This is the default builder function for the comment form. It is called
2036 * during the "Save" and "Preview" submit handlers to retrieve the entity to
2037 * save or preview. This function can also be called by a "Next" button of a
2038 * wizard to update the form state's entity with the current step's values
2039 * before proceeding to the next step.
2041 * @see comment_form()
2042 * @see comment_form_preview()
2043 * @see comment_form_submit()
2045 function comment_form_submit_build_comment($form, &$form_state) {
2046 $comment = $form_state['comment'];
2047 entity_form_submit_build_entity('comment', $comment, $form, $form_state);
2048 comment_submit($comment);
2053 * Form submission handler for comment_form().
2055 * @see comment_form_validate()
2056 * @see comment_form_submit_build_comment()
2058 function comment_form_submit($form, &$form_state) {
2059 $node = node_load($form_state['values']['nid']);
2060 $comment = comment_form_submit_build_comment($form, $form_state);
2061 if (user_access('post comments') && (user_access('administer comments') || $node->comment
== COMMENT_NODE_OPEN
)) {
2062 // Save the anonymous user information to a cookie for reuse.
2063 if (user_is_anonymous()) {
2064 user_cookie_save(array_intersect_key($form_state['values'], array_flip(array('name', 'mail', 'homepage'))));
2067 comment_save($comment);
2068 $form_state['values']['cid'] = $comment->cid
;
2070 // Add an entry to the watchdog log.
2071 watchdog('content', 'Comment posted: %subject.', array('%subject' => $comment->subject
), WATCHDOG_NOTICE
, l(t('view'), 'comment/' .
$comment->cid
, array('fragment' => 'comment-' .
$comment->cid
)));
2073 // Explain the approval queue if necessary.
2074 if ($comment->status
== COMMENT_NOT_PUBLISHED
) {
2075 if (!user_access('administer comments')) {
2076 drupal_set_message(t('Your comment has been queued for review by site administrators and will be published after approval.'));
2080 drupal_set_message(t('Your comment has been posted.'));
2083 // Find the current display page for this comment.
2084 $page = comment_get_display_page($comment->cid
, $node->type
);
2086 $query['page'] = $page;
2088 // Redirect to the newly posted comment.
2089 $redirect = array('node/' .
$node->nid
, array('query' => $query, 'fragment' => 'comment-' .
$comment->cid
));
2092 watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject
), WATCHDOG_WARNING
);
2093 drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject
)), 'error');
2094 // Redirect the user to the node they are commenting on.
2095 $redirect = 'node/' .
$node->nid
;
2097 $form_state['redirect'] = $redirect;
2098 // Clear the block and page caches so that anonymous users see the comment
2099 // they have posted.
2104 * Implements hook_preprocess_block().
2106 function comment_preprocess_block(&$variables) {
2107 if ($variables['block']->module
== 'comment') {
2108 $variables['attributes_array']['role'] = 'navigation';
2113 * Preprocesses variables for comment.tpl.php.
2115 * @see comment.tpl.php
2117 function template_preprocess_comment(&$variables) {
2118 $comment = $variables['elements']['#comment'];
2119 $node = $variables['elements']['#node'];
2120 $variables['comment'] = $comment;
2121 $variables['node'] = $node;
2122 $variables['author'] = theme('username', array('account' => $comment));
2123 $variables['created'] = format_date($comment->created
);
2124 $variables['changed'] = format_date($comment->changed
);
2126 $variables['new'] = !empty($comment->new
) ?
t('new') : '';
2127 $variables['user_picture'] = theme_get_setting('toggle_comment_user_picture') ?
theme('user_picture', array('account' => $comment)) : '';
2128 $variables['signature'] = $comment->signature
;
2130 $uri = entity_uri('comment', $comment);
2131 $uri['options'] += array('attributes' => array('class' => 'permalink', 'rel' => 'bookmark'));
2133 $variables['title'] = l($comment->subject
, $uri['path'], $uri['options']);
2134 $variables['permalink'] = l(t('Permalink'), $uri['path'], $uri['options']);
2135 $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['author'], '!datetime' => $variables['created']));
2137 // Preprocess fields.
2138 field_attach_preprocess('comment', $comment, $variables['elements'], $variables);
2140 // Helpful $content variable for templates.
2141 foreach (element_children($variables['elements']) as
$key) {
2142 $variables['content'][$key] = $variables['elements'][$key];
2145 // Set status to a string representation of comment->status.
2146 if (isset($comment->in_preview
)) {
2147 $variables['status'] = 'comment-preview';
2150 $variables['status'] = ($comment->status
== COMMENT_NOT_PUBLISHED
) ?
'comment-unpublished' : 'comment-published';
2153 // Gather comment classes.
2154 // 'comment-published' class is not needed, it is either 'comment-preview' or
2155 // 'comment-unpublished'.
2156 if ($variables['status'] != 'comment-published') {
2157 $variables['classes_array'][] = $variables['status'];
2159 if ($variables['new']) {
2160 $variables['classes_array'][] = 'comment-new';
2162 if (!$comment->uid
) {
2163 $variables['classes_array'][] = 'comment-by-anonymous';
2166 if ($comment->uid
== $variables['node']->uid
) {
2167 $variables['classes_array'][] = 'comment-by-node-author';
2169 if ($comment->uid
== $variables['user']->uid
) {
2170 $variables['classes_array'][] = 'comment-by-viewer';
2176 * Returns HTML for a "you can't post comments" notice.
2179 * An associative array containing:
2180 * - node: The comment node.
2182 * @ingroup themeable
2184 function theme_comment_post_forbidden($variables) {
2185 $node = $variables['node'];
2188 // Since this is expensive to compute, we cache it so that a page with many
2189 // comments only has to query the database once for all the links.
2190 $authenticated_post_comments = &drupal_static(__FUNCTION__
, NULL
);
2193 if (!isset($authenticated_post_comments)) {
2194 // We only output a link if we are certain that users will get permission
2195 // to post comments by logging in.
2196 $comment_roles = user_roles(TRUE
, 'post comments');
2197 $authenticated_post_comments = isset($comment_roles[DRUPAL_AUTHENTICATED_RID
]);
2200 if ($authenticated_post_comments) {
2201 // We cannot use drupal_get_destination() because these links
2202 // sometimes appear on /node and taxonomy listing pages.
2203 if (variable_get('comment_form_location_' .
$node->type
, COMMENT_FORM_BELOW
) == COMMENT_FORM_SEPARATE_PAGE
) {
2204 $destination = array('destination' => "comment/reply/$node->nid#comment-form");
2207 $destination = array('destination' => "node/$node->nid#comment-form");
2210 if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL
)) {
2211 // Users can register themselves.
2212 return t('<a href="@login">Log in</a> or <a href="@register">register</a> to post comments', array('@login' => url('user/login', array('query' => $destination)), '@register' => url('user/register', array('query' => $destination))));
2215 // Only admins can add new users, no public registration.
2216 return t('<a href="@login">Log in</a> to post comments', array('@login' => url('user/login', array('query' => $destination))));
2223 * Preprocesses variables for comment-wrapper.tpl.php.
2225 * @see comment-wrapper.tpl.php
2226 * @see theme_comment_wrapper()
2228 function template_preprocess_comment_wrapper(&$variables) {
2229 // Provide contextual information.
2230 $variables['node'] = $variables['content']['#node'];
2231 $variables['display_mode'] = variable_get('comment_default_mode_' .
$variables['node']->type
, COMMENT_MODE_THREADED
);
2232 // The comment form is optional and may not exist.
2233 $variables['content'] += array('comment_form' => array());
2237 * Returns an array of viewing modes for comment listings.
2239 * We can't use a global variable array because the locale system
2240 * is not initialized yet when the Comment module is loaded.
2242 function _comment_get_modes() {
2244 COMMENT_MODE_FLAT
=> t('Flat list'),
2245 COMMENT_MODE_THREADED
=> t('Threaded list')
2250 * Returns an array of "comments per page" values that users can select from.
2252 function _comment_per_page() {
2253 return drupal_map_assoc(array(10, 30, 50, 70, 90, 150, 200, 250, 300));
2257 * Generates a sorting code.
2259 * Consists of a leading character indicating length, followed by N digits
2260 * with a numerical value in base 36 (alphadecimal). These codes can be sorted
2261 * as strings without altering numerical order.
2264 * 00, 01, 02, ..., 0y, 0z,
2265 * 110, 111, ... , 1zy, 1zz,
2266 * 2100, 2101, ..., 2zzy, 2zzz,
2269 function comment_int_to_alphadecimal($i = 0) {
2270 $num = base_convert((int) $i, 10, 36);
2271 $length = strlen($num);
2273 return chr($length + ord('0') - 1) .
$num;
2277 * Decodes a sorting code back to an integer.
2279 * @see comment_int_to_alphadecimal()
2281 function comment_alphadecimal_to_int($c = '00') {
2282 return base_convert(substr($c, 1), 36, 10);
2286 * Increments a sorting code to the next value.
2288 * @see comment_int_to_alphadecimal()
2290 function comment_increment_alphadecimal($c = '00') {
2291 return comment_int_to_alphadecimal(comment_alphadecimal_to_int($c) + 1);
2295 * Implements hook_action_info().
2297 function comment_action_info() {
2299 'comment_publish_action' => array(
2300 'label' => t('Publish comment'),
2301 'type' => 'comment',
2302 'configurable' => FALSE
,
2303 'behavior' => array('changes_property'),
2304 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
2306 'comment_unpublish_action' => array(
2307 'label' => t('Unpublish comment'),
2308 'type' => 'comment',
2309 'configurable' => FALSE
,
2310 'behavior' => array('changes_property'),
2311 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
2313 'comment_unpublish_by_keyword_action' => array(
2314 'label' => t('Unpublish comment containing keyword(s)'),
2315 'type' => 'comment',
2316 'configurable' => TRUE
,
2317 'behavior' => array('changes_property'),
2318 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
2320 'comment_save_action' => array(
2321 'label' => t('Save comment'),
2322 'type' => 'comment',
2323 'configurable' => FALSE
,
2324 'triggers' => array('comment_insert', 'comment_update'),
2330 * Publishes a comment.
2333 * An optional comment object.
2334 * @param array $context
2335 * Array with components:
2336 * - 'cid': Comment ID. Required if $comment is not given.
2340 function comment_publish_action($comment, $context = array()) {
2341 if (isset($comment->subject
)) {
2342 $subject = $comment->subject
;
2343 $comment->status
= COMMENT_PUBLISHED
;
2346 $cid = $context['cid'];
2347 $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
2348 db_update('comment')
2349 ->fields(array('status' => COMMENT_PUBLISHED
))
2350 ->condition('cid', $cid)
2353 watchdog('action', 'Published comment %subject.', array('%subject' => $subject));
2357 * Unpublishes a comment.
2360 * An optional comment object.
2361 * @param array $context
2362 * Array with components:
2363 * - 'cid': Comment ID. Required if $comment is not given.
2367 function comment_unpublish_action($comment, $context = array()) {
2368 if (isset($comment->subject
)) {
2369 $subject = $comment->subject
;
2370 $comment->status
= COMMENT_NOT_PUBLISHED
;
2373 $cid = $context['cid'];
2374 $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
2375 db_update('comment')
2376 ->fields(array('status' => COMMENT_NOT_PUBLISHED
))
2377 ->condition('cid', $cid)
2380 watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject));
2384 * Unpublishes a comment if it contains certain keywords.
2387 * Comment object to modify.
2388 * @param array $context
2389 * Array with components:
2390 * - 'keywords': Keywords to look for. If the comment contains at least one
2391 * of the keywords, it is unpublished.
2394 * @see comment_unpublish_by_keyword_action_form()
2395 * @see comment_unpublish_by_keyword_action_submit()
2397 function comment_unpublish_by_keyword_action($comment, $context) {
2398 foreach ($context['keywords'] as
$keyword) {
2399 $text = drupal_render($comment);
2400 if (strpos($text, $keyword) !== FALSE
) {
2401 $comment->status
= COMMENT_NOT_PUBLISHED
;
2402 watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject
));
2409 * Form constructor for the blacklisted keywords form.
2412 * @see comment_unpublish_by_keyword_action()
2413 * @see comment_unpublish_by_keyword_action_submit()
2415 function comment_unpublish_by_keyword_action_form($context) {
2416 $form['keywords'] = array(
2417 '#title' => t('Keywords'),
2418 '#type' => 'textarea',
2419 '#description' => t('The comment will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
2420 '#default_value' => isset($context['keywords']) ?
drupal_implode_tags($context['keywords']) : '',
2427 * Form submission handler for comment_unpublish_by_keyword_action_form().
2429 * @see comment_unpublish_by_keyword_action()
2431 function comment_unpublish_by_keyword_action_submit($form, $form_state) {
2432 return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
2440 function comment_save_action($comment) {
2441 comment_save($comment);
2443 watchdog('action', 'Saved comment %title', array('%title' => $comment->subject
));
2447 * Implements hook_ranking().
2449 function comment_ranking() {
2451 'comments' => array(
2452 'title' => t('Number of comments'),
2455 'table' => 'node_comment_statistics',
2456 'alias' => 'node_comment_statistics',
2457 'on' => 'node_comment_statistics.nid = i.sid',
2459 // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
2460 'score' => '2.0 - 2.0 / (1.0 + node_comment_statistics.comment_count * CAST(:scale AS DECIMAL))',
2461 'arguments' => array(':scale' => variable_get('node_cron_comments_scale', 0)),
2467 * Implements hook_rdf_mapping().
2469 function comment_rdf_mapping() {
2472 'type' => 'comment',
2473 'bundle' => RDF_DEFAULT_BUNDLE
,
2475 'rdftype' => array('sioc:Post', 'sioct:Comment'),
2477 'predicates' => array('dc:title'),
2480 'predicates' => array('dc:date', 'dc:created'),
2481 'datatype' => 'xsd:dateTime',
2482 'callback' => 'date_iso8601',
2485 'predicates' => array('dc:modified'),
2486 'datatype' => 'xsd:dateTime',
2487 'callback' => 'date_iso8601',
2489 'comment_body' => array(
2490 'predicates' => array('content:encoded'),
2493 'predicates' => array('sioc:reply_of'),
2497 'predicates' => array('sioc:has_creator'),
2501 'predicates' => array('foaf:name'),
2509 * Implements hook_file_download_access().
2511 function comment_file_download_access($field, $entity_type, $entity) {
2512 if ($entity_type == 'comment') {
2513 if (user_access('access comments') && $entity->status
== COMMENT_PUBLISHED
|| user_access('administer comments')) {
2514 $node = node_load($entity->nid
);
2515 return node_access('view', $node);