comment.module
<?php
define('COMMENT_NOT_PUBLISHED', 0);
define('COMMENT_PUBLISHED', 1);
define('COMMENT_MODE_FLAT_COLLAPSED', 1);
define('COMMENT_MODE_FLAT_EXPANDED', 2);
define('COMMENT_MODE_THREADED_COLLAPSED', 3);
define('COMMENT_MODE_THREADED_EXPANDED', 4);
define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);
define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);
define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);
define('COMMENT_FORM_SEPARATE_PAGE', 0);
define('COMMENT_FORM_BELOW', 1);
define('COMMENT_NODE_DISABLED', 0);
define('COMMENT_NODE_READ_ONLY', 1);
define('COMMENT_NODE_READ_WRITE', 2);
define('COMMENT_PREVIEW_OPTIONAL', 0);
define('COMMENT_PREVIEW_REQUIRED', 1);
function comment_help($path, $arg) {
switch ($path) {
case 'admin/help#comment':
$output = '<p>' . t('The comment module allows visitors to comment on your posts, creating ad hoc discussion boards. Any <a href="@content-type">content type</a> may have its <em>Default comment setting</em> set to <em>Read/Write</em> to allow comments, or <em>Disabled</em>, to prevent comments. Comment display settings and other controls may also be customized for each content type.', array('@content-type' => url('admin/build/types'))) . '</p>';
$output .= '<p>' . t('Comment permissions are assigned to user roles, and are used to determine whether anonymous users (or other roles) are allowed to comment on posts. If anonymous users are allowed to comment, their individual contact information may be retained in cookies stored on their local computer for use in later comment submissions. When a comment has no replies, it may be (optionally) edited by its author. The comment module uses the same input formats and HTML tags available when creating other forms of content.') . '</p>';
$output .= '<p>' . t('For more information, see the online handbook entry for <a href="@comment">Comment module</a>.', array('@comment' => 'http://drupal.org/handbook/modules/comment/')) . '</p>';
return $output;
case 'admin/content/comment':
return '<p>' . t("Below is a list of the latest comments posted to your site. Click on a subject to see the comment, the author's name to edit the author's user information, 'edit' to modify the text, and 'delete' to remove their submission.") . '</p>';
case 'admin/content/comment/approval':
return '<p>' . t("Below is a list of the comments posted to your site that need approval. To approve a comment, click on 'edit' and then change its 'moderation status' to Approved. Click on a subject to see the comment, the author's name to edit the author's user information, 'edit' to modify the text, and 'delete' to remove their submission.") . '</p>';
}
}
function comment_theme() {
return array(
'comment_block' => array(
'arguments' => array(),
),
'comment_admin_overview' => array(
'arguments' => array('form' => NULL),
),
'comment_preview' => array(
'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
),
'comment_view' => array(
'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
),
'comment' => array(
'template' => 'comment',
'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array()),
),
'comment_folded' => array(
'template' => 'comment-folded',
'arguments' => array('comment' => NULL),
),
'comment_flat_collapsed' => array(
'arguments' => array('comment' => NULL, 'node' => NULL),
),
'comment_flat_expanded' => array(
'arguments' => array('comment' => NULL, 'node' => NULL),
),
'comment_thread_collapsed' => array(
'arguments' => array('comment' => NULL, 'node' => NULL),
),
'comment_thread_expanded' => array(
'arguments' => array('comment' => NULL, 'node' => NULL),
),
'comment_post_forbidden' => array(
'arguments' => array('nid' => NULL),
),
'comment_wrapper' => array(
'template' => 'comment-wrapper',
'arguments' => array('content' => NULL, 'node' => NULL),
),
'comment_submitted' => array(
'arguments' => array('comment' => NULL),
),
);
}
function comment_menu() {
$items['admin/content/comment'] = array(
'title' => 'Comments',
'description' => 'List and edit site comments and the comment moderation queue.',
'page callback' => 'comment_admin',
'access arguments' => array('administer comments'),
);
$items['admin/content/comment/new'] = array(
'title' => 'Published comments',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['admin/content/comment/approval'] = array(
'title' => 'Approval queue',
'page arguments' => array('approval'),
'access arguments' => array('administer comments'),
'type' => MENU_LOCAL_TASK,
);
$items['comment/delete'] = array(
'title' => 'Delete comment',
'page callback' => 'comment_delete',
'access arguments' => array('administer comments'),
'type' => MENU_CALLBACK,
);
$items['comment/edit'] = array(
'title' => 'Edit comment',
'page callback' => 'comment_edit',
'access arguments' => array('post comments'),
'type' => MENU_CALLBACK,
);
$items['comment/reply/%node'] = array(
'title' => 'Reply to comment',
'page callback' => 'comment_reply',
'page arguments' => array(2),
'access callback' => 'node_access',
'access arguments' => array('view', 2),
'type' => MENU_CALLBACK,
);
$items['comment/approve'] = array(
'title' => 'Approve a comment',
'page callback' => 'comment_approve',
'page arguments' => array(2),
'access arguments' => array('administer comments'),
'type' => MENU_CALLBACK,
);
return $items;
}
function comment_node_type($op, $info) {
$settings = array(
'comment',
'comment_default_mode',
'comment_default_per_page',
'comment_anonymous',
'comment_subject_field',
'comment_preview',
'comment_form_location',
);
switch ($op) {
case 'delete':
foreach ($settings as $setting) {
variable_del($setting . '_' . $info->type);
}
break;
}
}
function comment_perm() {
return array(
'administer comments' => array(
'title' => t('Administer comments'),
'description' => t('Manage and approve comments, and configure comment administration settings.'),
),
'access comments' => array(
'title' => t('Access comments'),
'description' => t('View comments attached to content.'),
),
'post comments' => array(
'title' => t('Post comments'),
'description' => t('Add comments to content (approval required).'),
),
'post comments without approval' => array(
'title' => t('Post comments without approval'),
'description' => t('Add comments to content (no approval required).'),
),
);
}
function comment_block($op = 'list', $delta = '', $edit = array()) {
switch ($op) {
case 'list':
$blocks['recent']['info'] = t('Recent comments');
return $blocks;
case 'configure':
$form['comment_block_count'] = array(
'#type' => 'select',
'#title' => t('Number of recent comments'),
'#default_value' => variable_get('comment_block_count', 10),
'#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)),
'#description' => t('Number of comments displayed in the <em>Recent comments</em> block.'),
);
return $form;
case 'save':
variable_set('comment_block_count', (int)$edit['comment_block_count']);
break;
case 'view':
if (user_access('access comments')) {
$block['subject'] = t('Recent comments');
$block['content'] = theme('comment_block');
return $block;
}
}
}
function comment_get_recent($number = 10) {
$nids = db_query_range("SELECT nc.nid FROM {node_comment_statistics} nc WHERE nc.comment_count > 0 ORDER BY nc.last_comment_timestamp DESC", 0, $number)->fetchCol();
$comments = array();
if (!empty($nids)) {
$result = db_select('comments', 'c')
->fields('c', array('nid', 'subject', 'cid', 'timestamp') )
->innerJoin('node', 'n', 'n.nid = c.nid')
->condition('c.nid', $nids, 'IN')
->condition('c.status', COMMENT_PUBLISHED)
->condition('n.status', 1)
->orderBy('c.cid', 'DESC')
->range(0, $number)
->execute();
foreach ($result as $comment) {
$comments[] = $comment;
}
}
return $comments;
}
function comment_new_page_count($num_comments, $new_replies, $node) {
$comments_per_page = _comment_get_display_setting('comments_per_page', $node);
$mode = _comment_get_display_setting('mode', $node);
$pagenum = NULL;
$flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
if ($num_comments <= $comments_per_page) {
$pageno = 0;
}
elseif ($flat) {
$count = $num_comments - $new_replies;
$pageno = $count / $comments_per_page;
}
else {
$result = db_query_range('(SELECT thread
FROM {comments}
WHERE nid = :nid
AND status = 0
ORDER BY timestamp DESC)
ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1))', array(':nid' => $node->nid), 0, $new_replies)
->fetchField();
$thread = substr($result, 0, -1);
$count = db_query('SELECT COUNT(*) FROM {comments} WHERE nid = :nid AND status = 0 AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array(
':nid' => $node->nid,
':thread' => $thread))
->fetchField();
$pageno = $count / $comments_per_page;
}
if ($pageno >= 1) {
$pagenum = "page=" . intval($pageno);
}
return $pagenum;
}
function theme_comment_block() {
$items = array();
$number = variable_get('comment_block_count', 10);
foreach (comment_get_recent($number) as $comment) {
$items[] = l($comment->subject, 'node/' . $comment->nid, array('fragment' => 'comment-' . $comment->cid)) . '<br />' . t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->timestamp)));
}
if ($items) {
return theme('item_list', $items);
}
}
function comment_link($type, $node = NULL, $teaser = FALSE) {
$links = array();
if ($type == 'node' && $node->comment) {
if ($teaser) {
if (user_access('access comments')) {
if ($node->comment_count) {
$links['comment_comments'] = array(
'title' => format_plural($node->comment_count, '1 comment', '@count comments'),
'href' => "node/$node->nid",
'attributes' => array('title' => t('Jump to the first comment of this posting.')),
'fragment' => 'comments'
);
$new = comment_num_new($node->nid);
if ($new) {
$links['comment_new_comments'] = array(
'title' => format_plural($new, '1 new comment', '@count new comments'),
'href' => "node/$node->nid",
'query' => comment_new_page_count($node->comment_count, $new, $node),
'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
'fragment' => 'new'
);
}
}
else {
if ($node->comment == COMMENT_NODE_READ_WRITE) {
if (user_access('post comments')) {
$links['comment_add'] = array(
'title' => t('Add new comment'),
'href' => "comment/reply/$node->nid",
'attributes' => array('title' => t('Add a new comment to this page.')),
'fragment' => 'comment-form'
);
}
else {
$links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
}
}
}
}
}
else {
if ($node->comment == COMMENT_NODE_READ_WRITE) {
if (user_access('post comments')) {
if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
$links['comment_add'] = array(
'title' => t('Add new comment'),
'href' => "comment/reply/$node->nid",
'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
'fragment' => 'comment-form'
);
}
}
else {
$links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
}
}
}
}
if ($type == 'comment') {
$links = comment_links($node, $teaser);
}
if (isset($links['comment_forbidden'])) {
$links['comment_forbidden']['html'] = TRUE;
}
return $links;
}
function comment_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
$form['comment'] = array(
'#type' => 'fieldset',
'#title' => t('Comment settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['comment']['comment'] = array(
'#type' => 'radios',
'#title' => t('Default comment setting'),
'#default_value' => variable_get('comment_' . $form['#node_type']->type, COMMENT_NODE_READ_WRITE),
'#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
'#description' => t('Users with the <em>administer comments</em> permission will be able to override this setting.'),
);
$form['comment']['comment_default_mode'] = array(
'#type' => 'radios',
'#title' => t('Default display mode'),
'#default_value' => variable_get('comment_default_mode_' . $form['#node_type']->type, COMMENT_MODE_THREADED_EXPANDED),
'#options' => _comment_get_modes(),
'#description' => t('Expanded views display the body of the comment. Threaded views keep replies together.'),
);
$form['comment']['comment_default_per_page'] = array(
'#type' => 'select',
'#title' => t('Comments per page'),
'#default_value' => variable_get('comment_default_per_page_' . $form['#node_type']->type, 50),
'#options' => _comment_per_page(),
'#description' => t('Additional comments will be displayed on separate pages.'),
);
$form['comment']['comment_anonymous'] = array(
'#type' => 'radios',
'#title' => t('Anonymous commenting'),
'#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT),
'#options' => array(
COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'),
COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'),
COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information')),
'#description' => t('This option is enabled when anonymous users have permission to post comments on the <a href="@url">permissions page</a>.', array('@url' => url('admin/user/permissions', array('fragment' => 'module-comment')))),
);
if (!user_access('post comments', drupal_anonymous_user())) {
$form['comment']['comment_anonymous']['#disabled'] = TRUE;
}
$form['comment']['comment_subject_field'] = array(
'#type' => 'radios',
'#title' => t('Comment subject field'),
'#default_value' => variable_get('comment_subject_field_' . $form['#node_type']->type, 1),
'#options' => array(t('Disabled'), t('Enabled')),
'#description' => t('Can users provide a unique subject for their comments?'),
);
$form['comment']['comment_preview'] = array(
'#type' => 'radios',
'#title' => t('Preview comment'),
'#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, COMMENT_PREVIEW_REQUIRED),
'#options' => array(t('Optional'), t('Required')),
'#description' => t("Forces a user to look at their comment by clicking on a 'Preview' button before they can actually add the comment"),
);
$form['comment']['comment_form_location'] = array(
'#type' => 'radios',
'#title' => t('Location of comment submission form'),
'#default_value' => variable_get('comment_form_location_' . $form['#node_type']->type, COMMENT_FORM_SEPARATE_PAGE),
'#options' => array(t('Display on separate page'), t('Display below post or comments')),
);
}
elseif (!empty($form['#node_edit_form'])) {
$node = $form['#node'];
$form['comment_settings'] = array(
'#type' => 'fieldset',
'#access' => user_access('administer comments'),
'#title' => t('Comment settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => 30,
);
$form['comment_settings']['comment'] = array(
'#type' => 'radios',
'#parents' => array('comment'),
'#default_value' => $node->comment,
'#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
);
}
}
function comment_nodeapi_load(&$node, $arg = 0) {
if ($node->comment != COMMENT_NODE_DISABLED) {
return db_query('SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = :nid', array(':nid' => $node->nid))->fetchAssoc();
}
return array('last_comment_timestamp' => $node->created, 'last_comment_name' => '', 'comment_count' => 0);
}
function comment_nodeapi_prepare(&$node, $arg = 0) {
if (!isset($node->comment)) {
$node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
}
}
function comment_nodeapi_insert(&$node, $arg = 0) {
db_insert('node_comment_statistics')
->fields(array(
'nid' => $node->nid,
'last_comment_timestamp' => $node->changed,
'last_comment_name' => NULL,
'last_comment_uid' => $node->uid,
'comment_count' => 0 ))
->execute();
}
function comment_nodeapi_delete(&$node, $arg = 0) {
db_delete('comments')
->condition('nid', $node->nid)
->execute();
db_delete('node_comment_statistics')
->condition('nid', $node->nid)
->execute();
}
function comment_nodeapi_update_index(&$node, $arg = 0) {
$text = '';
$comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = :nid AND status = :status', array(':nid' => $node->nid, ':status' => COMMENT_PUBLISHED));
foreach ($comments as $comment) {
$text .= '<h2>' . check_plain($comment->subject) . '</h2>' . check_markup($comment->comment, $comment->format, FALSE);
}
return $text;
}
function comment_nodeapi_search_result(&$node, $arg = 0) {
$comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField();
return format_plural($comments, '1 comment', '@count comments');
}
function comment_nodeapi_rss_item(&$node, $arg = 0) {
if ($node->comment != COMMENT_NODE_DISABLED) {
return array(array('key' => 'comments', 'value' => url('node/' . $node->nid, array('fragment' => 'comments', 'absolute' => TRUE))));
}
else {
return array();
}
}
function comment_user_delete(&$edit, &$user, $category = NULL) {
db_update('comments')
->fields(array('uid' => 0))
->condition('uid', $user->uid)
->execute();
db_update('node_comment_statistics')
->fields(array('last_comment_uid' => 0))
->condition('last_comment_uid', $user->uid)
->execute();
}
function comment_access($op, $comment) {
global $user;
if ($op == 'edit') {
return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments');
}
}
function comment_node_url() {
return arg(0) . '/' . arg(1);
}
function comment_save($edit) {
global $user;
if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
if (!form_get_errors()) {
$edit += array(
'mail' => '',
'homepage' => '',
'name' => '',
'status' => user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED,
);
if ($edit['cid']) {
db_update('comments')
->fields(array(
'status' => $edit['status'],
'timestamp' => $edit['timestamp'],
'subject' => $edit['subject'],
'comment' => $edit['comment'],
'format' => $edit['comment_format'],
'uid' => $edit['uid'],
'name' => $edit['name'],
'mail' => $edit['mail'],
'homepage' => $edit['homepage']
))
->condition('cid', $edit['cid'])
->execute();
comment_invoke_comment($edit, 'update');
watchdog('content', 'Comment: updated %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'], array('fragment' => 'comment-' . $edit['cid'])));
}
else {
if ($edit['pid'] == 0) {
$max = db_query('SELECT MAX(thread) FROM {comments} WHERE nid = :nid', array(':nid' => $edit['nid']))->fetchField();
$max = rtrim($max, '/');
$thread = int2vancode(vancode2int($max) + 1) . '/';
}
else {
$parent = comment_load($edit['pid']);
$parent->thread = (string) rtrim((string) $parent->thread, '/');
$max = db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE :thread AND nid = :nid", array(
':thread' => $parent->thread .'.%',
':nid' => $edit['nid']))
->fetchField();
if ($max == '') {
$thread = $parent->thread . '.' . int2vancode(0) . '/';
}
else {
$max = rtrim($max, '/');
$parts = explode('.', $max);
$parent_depth = count(explode('.', $parent->thread));
$last = $parts[$parent_depth];
$thread = $parent->thread . '.' . int2vancode(vancode2int($last) + 1) . '/';
}
}
if (empty($edit['timestamp'])) {
$edit['timestamp'] = REQUEST_TIME;
}
if ($edit['uid'] === $user->uid) { $edit['name'] = $user->name;
}
$edit['cid'] = db_insert('comments')
->fields(array(
'nid' => $edit['nid'],
'pid' => empty($edit['pid']) ? 0 : $edit['pid'],
'uid' => empty($edit['uid']) ? 0 : $edit['uid'],
'subject' => $edit['subject'],
'comment' => $edit['comment'],
'format' => $edit['comment_format'],
'hostname' => ip_address(),
'timestamp' => $edit['timestamp'],
'status' => $edit['status'],
'thread' => $thread,
'name' => $edit['name'],
'mail' => $edit['mail'],
'homepage' => $edit['homepage']
))
->execute();
comment_invoke_comment($edit, 'insert');
watchdog('content', 'Comment: added %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'], array('fragment' => 'comment-' . $edit['cid'])));
}
_comment_update_node_statistics($edit['nid']);
cache_clear_all();
if ($edit['status'] == COMMENT_NOT_PUBLISHED) {
drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
}
else {
drupal_set_message(t('Your comment has been posted.'));
comment_invoke_comment($edit, 'publish');
}
return $edit['cid'];
}
else {
return FALSE;
}
}
else {
watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject']), WATCHDOG_WARNING);
drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject'])), 'error');
return FALSE;
}
}
function comment_links($comment, $return = 1) {
global $user;
$links = array();
if ($return) {
$links['comment_parent'] = array(
'title' => t('parent'),
'href' => comment_node_url(),
'fragment' => "comment-$comment->cid"
);
}
if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
if (user_access('administer comments') && user_access('post comments')) {
$links['comment_delete'] = array(
'title' => t('delete'),
'href' => "comment/delete/$comment->cid"
);
$links['comment_edit'] = array(
'title' => t('edit'),
'href' => "comment/edit/$comment->cid"
);
$links['comment_reply'] = array(
'title' => t('reply'),
'href' => "comment/reply/$comment->nid/$comment->cid"
);
if ($comment->status == COMMENT_NOT_PUBLISHED) {
$links['comment_approve'] = array(
'title' => t('approve'),
'href' => "comment/approve/$comment->cid"
);
}
}
elseif (user_access('post comments')) {
if (comment_access('edit', $comment)) {
$links['comment_edit'] = array(
'title' => t('edit'),
'href' => "comment/edit/$comment->cid"
);
}
$links['comment_reply'] = array(
'title' => t('reply'),
'href' => "comment/reply/$comment->nid/$comment->cid"
);
}
else {
$node = node_load($comment->nid);
$links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
}
}
return $links;
}
function comment_render($node, $cid = 0) {
global $user;
$output = '';
if (user_access('access comments')) {
$nid = $node->nid;
if (empty($nid)) {
$nid = 0;
}
$mode = _comment_get_display_setting('mode', $node);
$comments_per_page = _comment_get_display_setting('comments_per_page', $node);
if ($cid && is_numeric($cid)) {
$query =