'. t('The comment module creates a discussion board for each post. Users can post comments to discuss a forum topic, weblog post, story, collaborative book page, etc. The ability to comment is an important part of involving members in a community dialogue.') .'
'; $output .= ''. t('An administrator can give comment permissions to user groups, and users can (optionally) edit their last comment, assuming no others have been posted since. Attached to each comment board is a control panel for customizing the way that comments are displayed. Users can control the chronological ordering of posts (newest or oldest first) and the number of posts to display on each page. Comments behave like other user submissions. Filters, smileys and HTML that work in nodes will also work with comments. The comment module provides specific features to inform site members when new comments have been posted.') .'
'; $output .= ''. t('For more information please read the configuration and customization handbook Comment page.', array('@comment' => 'http://drupal.org/handbook/modules/comment/')) .'
'; return $output; case 'admin/content/comment': case 'admin/content/comment/new': return ''. 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.") .'
'; case 'admin/content/comment/approval': return ''. 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.") .'
'; case 'admin/content/comment/settings': return ''. t("Comments can be attached to any node, and their settings are below. The display comes in two types: a 'flat list' where everything is flush to the left side, and comments come in chronological order, and a 'threaded list' where replies to other comments are placed immediately below and slightly indented, forming an outline. They also come in two styles: 'expanded', where you see both the title and the contents, and 'collapsed' where you only see the title. Preview comment forces a user to look at their comment by clicking on a 'Preview' button before they can actually add the comment.") .'
'; } } /** * Implementation of hook_theme() */ 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, 'links' => array(), 'visible' => 1), ), 'comment_view' => array( 'arguments' => array('comment' => NULL, 'links' => array(), 'visible' => 1), ), 'comment_controls' => array( 'arguments' => array('form' => NULL), ), 'comment' => array( 'arguments' => array('comment' => NULL, 'links' => array()), 'file' => 'comment.tpl.php', ), 'comment_folded' => array( 'arguments' => array('comment' => NULL), ), 'comment_flat_collapsed' => array( 'arguments' => array('comment' => NULL), ), 'comment_flat_expanded' => array( 'arguments' => array('comment' => NULL), ), 'comment_thread_collapsed' => array( 'arguments' => array('comment' => NULL), ), 'comment_thread_expanded' => array( 'arguments' => array('comment' => NULL), ), 'comment_post_forbidden' => array( 'arguments' => array('nid' => NULL), ), 'comment_wrapper' => array( 'arguments' => array('content' => NULL), ), ); } function _comment_view_access($node, $cid) { return $node && $cid; } /** * Implementation of hook_menu(). */ 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'), ); // Tabs: $items['admin/content/comment/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); // Subtabs: $items['admin/content/comment/list/new'] = array( 'title' => 'Published comments', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/content/comment/list/approval'] = array( 'title' => 'Approval queue', 'page arguments' => array('approval'), 'type' => MENU_LOCAL_TASK, ); $items['admin/content/comment/settings'] = array( 'title' => 'Settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('comment_admin_settings'), 'weight' => 10, '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' => 'drupal_get_form', 'page arguments' => array('comment_form', 2), 'access arguments' => array('post comments'), 'type' => MENU_CALLBACK, ); $items['comment/reply/%'] = array( 'title' => 'Reply to comment', 'page callback' => 'drupal_get_form', 'page arguments' => array('comment_form', FALSE, 2), 'access callback' => 'node_access', 'access arguments' => array('view', 2), 'type' => MENU_CALLBACK, ); $items['comment/reply/%/%'] = array( 'title' => 'Reply to comment', 'page callback' => 'drupal_get_form', 'page arguments' => array('comment_form', FALSE, 2, 3), 'access callback' => 'node_access', 'access arguments' => array('view', 2), 'type' => MENU_CALLBACK, ); $items['node/%node/%'] = array( 'title' => 'View', 'page callback' => 'node_page_view', 'page arguments' => array(1, 2), 'access callback' => '_comment_view_access', 'access arguments' => array(1, 2), 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_perm(). */ function comment_perm() { return array('access comments', 'post comments', 'administer comments', 'post comments without approval'); } function comment_form($form_state, $cid = 0, $nid = 0, $pid = 0) { global $user; $access = user_access('administer comments'); if ($cid) { $comment = (array) comment_load($cid); $access = ($user->uid && $user->uid == $comment['uid'] && comment_num_replies($comment->cid) == 0); $nid = $comment->nid; } else { $comment = array( 'cid' => 0, 'pid' => $pid, 'nid' => $nid, 'uid' => $user->uid, 'status' => COMMENT_PUBLISHED, 'name' => $user->uid ? $user->name : variable_get('anonymous', t('Anonymous')), 'mail' => '', 'homepage' => '', 'timestamp' => time(), 'subject' => '', 'comment' => '', 'format' => FILTER_FORMAT_DEFAULT, ); $access = user_access('post comments') || user_access('post comments without approval'); } // We need to return a warning if the user is not administrator and does // not have the right to post a new comment or modify the comment. if (!user_access('administer comments') && (!$access || node_comment_mode($comment['nid']) != COMMENT_NODE_READ_WRITE)) { $form['warning'] = array('#value' => $cid ? t('You do not have access to modify this comment') : t('You are not authorized to post comments')); return $form; } $account = user_load($comment['uid']); if (isset($form_state['comment'])) { $comment = $form_state['comment'] + $comment; } if (isset($form_state['comment_preview'])) { $form['#prefix'] = $form_state['comment_preview']; } $form['#token'] = 'comment'. $comment['nid'] . $comment['pid']; if (!$user->uid && variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) != COMMENT_ANONYMOUS_MAYNOT_CONTACT) { drupal_add_js(drupal_get_path('module', 'comment') . '/comment.js'); } if (user_access('administer comments')) { $form['meta'] = array( '#type' => 'fieldset', '#title' => t('Administration'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => -2, ); } $required = (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT); $form['meta']['anon']['#access'] = !$user->uid || ($cid && !$comment['uid'] && user_access('administer comments')); $form['meta']['anon']['name'] = array( '#type' => 'textfield', '#title' => t('Authored by'), '#size' => 30, '#maxlength' => 60, '#default_value' => $comment['name'], '#weight' => -1, '#required' => $required, ); $form['meta']['anon']['mail'] = array( '#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $comment['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.'), '#required' => $required, ); $form['meta']['anon']['homepage'] = array( '#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $comment['homepage'], '#required' => $required, ); $form['meta']['admin']['#access'] = user_access('administer comments'); $form['meta']['admin']['author'] = array( '#type' => 'textfield', '#title' => t('Authored by'), '#size' => 30, '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', '#default_value' => $comment['name'], '#weight' => -1, '#access' => $comment['uid'], ); $form['meta']['admin']['date'] = array( '#type' => 'textfield', '#title' => t('Authored on'), '#size' => 20, '#maxlength' => 25, '#default_value' => format_date($comment['timestamp'], 'custom', 'Y-m-d H:i O'), '#weight' => -1, ); $form['meta']['admin']['status'] = array( '#type' => 'radios', '#title' => t('Status'), '#default_value' => $comment['status'], '#options' => array(t('Published'), t('Not published')), '#weight' => -1, ); $form['author'] = array( '#type' => 'item', '#title' => t('Your name'), '#value' => theme('username', $account), '#access' => $cid && !user_access('administer comments'), ); $form['subject'] = array( '#type' => 'textfield', '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => $comment['subject'], '#access' => variable_get('comment_subject_field', 1), ); $form['comment_filter']['comment'] = array( '#type' => 'textarea', '#title' => t('Comment'), '#rows' => 15, '#default_value' => $comment['comment'], '#required' => TRUE, ); $form['comment_filter']['format'] = filter_form($comment['format']); foreach (array('cid', 'pid', 'nid', 'uid') as $key) { $form[$key] = array('#type' => 'value', '#value' => $comment[$key]); } $form['preview'] = array( '#type' => 'submit', '#value' => t('Preview comment'), '#submit' => array('comment_form_build_preview'), '#weight' => 19, ); if ((variable_get('comment_preview', COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL) || isset($form_state['comment_preview'])) { $form['submit'] = array( '#type' => 'submit', '#value' => t('Post comment'), '#weight' => 20, ); } return $form; } /** * Accepts a submission of new or changed comment content. */ function comment_form_submit($form, $form_state, $form_values) { if ($form['meta']['anon']['#access']) { $name = $form_values['name']; $uid = 0; } else { $account = user_load(array('name' => $form_values['author'])); $name = $account->name; $uid = $account->uid; } if ($form['meta']['admin']['status']['#access']) { $status = $form_values['status']; } elseif (user_access('post comments without approval')) { $status = COMMENT_PUBLISHED; } else { $status = COMMENT_NOT_PUBLISHED; } if ($form_values['cid']) { // Update the comment in the database. db_query("UPDATE {comments} SET status = %d, timestamp = %d, subject = '%s', comment = '%s', format = %d, uid = %d, name = '%s', mail = '%s', homepage = '%s' WHERE cid = %d", $status, $form_values['timestamp'], $form_values['subject'], $form_values['comment'], $form_values['format'], $uid, $name, $form_values['mail'], $form_values['homepage'], $form_values['cid']); _comment_update_node_statistics($form_values['nid']); // Allow modules to respond to the updating of a comment. comment_invoke_comment($form_values, 'update'); // Add an entry to the watchdog log. watchdog('content', 'Comment: updated %subject.', array('%subject' => $form_values['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $form_values['nid'], array('fragment' => 'comment-'. $form_values['cid']))); } else { // Check for duplicate comments. Note that we have to use the // validated/filtered data to perform such check. $duplicate = db_result(db_query("SELECT COUNT(cid) FROM {comments} WHERE pid = %d AND nid = %d AND subject = '%s' AND comment = '%s'", $form_values['pid'], $form_values['nid'], $form_values['subject'], $form_values['comment']), 0); if (!$duplicate) { watchdog('content', 'Comment: duplicate %subject.', array('%subject' => $form_values['subject']), WATCHDOG_WARNING); } $thread = _comment_build_thread($form_values['pid'], $form_values['nid']); $form_values['cid'] = db_next_id('{comments}_cid'); $form_values['timestamp'] = time(); db_query("INSERT INTO {comments} (cid, nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $form_values['cid'], $form_values['nid'], $form_values['pid'], $uid, $form_values['subject'], $form_values['comment'], $form_values['format'], ip_address(), $form_values['timestamp'], $status, $thread, $name, $form_values['mail'], $form_values['homepage']); _comment_update_node_statistics($form_values['nid']); // Tell the other modules a new comment has been submitted. comment_invoke_comment($form_values, 'insert'); // Add an entry to the watchdog log. watchdog('content', 'Comment: added %subject.', array('%subject' => $form_values['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $form_values['nid'], array('fragment' => 'comment-'. $form_values['cid']))); } // Clear the cache so an anonymous user can see his comment being added. cache_clear_all(); // Explain the approval queue if necessary, and then // redirect the user to the node he's commenting on. if ($status == COMMENT_NOT_PUBLISHED) { drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.')); } return $form_values['cid']; } /* } else { watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed node %subject.', array('%subject' => $edit['subject']), WATCHDOG_WARNING); drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed node %subject.', array('%subject' => $edit['subject'])), 'error'); return FALSE; } } */ /** * * To display threaded comments in the correct order we keep a 'thread' field * and order by that value. This field keeps this data in * a way which is easy to update and convenient to use. * * A "thread" value starts at "1". If we add a child (A) to this comment, * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next * brother of (A) will get "1.2". Next brother of the parent of (A) will get * "2" and so on. * * First of all note that the thread field stores the depth of the comment: * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc. * * Now to get the ordering right, consider this example: * * 1 * 1.1 * 1.1.1 * 1.2 * 2 * * If we "ORDER BY thread ASC" we get the above result, and this is the * natural order sorted by time. However, if we "ORDER BY thread DESC" * we get: * * 2 * 1.2 * 1.1.1 * 1.1 * 1 * * Clearly, this is not a natural way to see a thread, and users will get * confused. The natural order to show a thread by time desc would be: * * 2 * 1 * 1.2 * 1.1 * 1.1.1 * * which is what we already did before the standard pager patch. To achieve * this we simply add a "/" at the end of each "thread" value. This way out * thread fields will look like depicted below: * * 1/ * 1.1/ * 1.1.1/ * 1.2/ * 2/ * * we add "/" since this char is, in ASCII, higher than every number, so if * now we "ORDER BY thread DESC" we get the correct order. However this would * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need * to consider the trailing "/" so we use a substring only. */ function _comment_build_thread($pid, $nid) { if ($pid) { // This is comment with a parent comment: we increase // the part of the thread value at the proper depth. // Get the parent comment: $parent = comment_load($pid); // Strip the "/" from the end of the parent thread. $parent->thread = (string) rtrim((string) $parent->thread, '/'); // Get the max value in _this_ thread. if ($max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $nid))) { // Strip the "/" at the end of the thread. $max = rtrim($max, '/'); // We need to get the value at the correct depth. $parts = explode('.', $max); $parent_depth = count(explode('.', $parent->thread)); $last = $parts[$parent_depth]; // Finally, build the thread field for this new comment. $thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/'; } else { // First child of this parent. $thread = $parent->thread .'.'. int2vancode(0) .'/'; } } else { // This is a comment with no parent comment (depth 0): we start // by retrieving the maximum thread level. $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $form_values['nid'])); // Strip the "/" from the end of the thread. $max = rtrim($max, '/'); // Finally, build the thread field for this new comment. $thread = int2vancode(vancode2int($max) + 1) .'/'; } return $thread; } function comment_form_build_preview($form, &$form_state, $form_values) { global $user; $output = ''; $comment = (object)$form_values; // We do not want to execute button level handlers, we want the form level // handlers to go in and change the submitted values. unset($form_state['submit_handlers']); form_execute_handlers('submit', $form, $form_state); $form_state['rebuild'] = TRUE; $form_state['node'] = $form_state['values']; $form_state['comment_preview'] = comment_preview((object)$form_state['values']); } function comment_preview($comment) { $output = theme('comment_view', $comment); if ($edit['pid']) { $output .= theme('comment_view', comment_load($edit['pid'])); } return $output; } function comment_num_replies($pid) { static $cache; if (!isset($cache[$pid])) { $cache[$pid] = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE pid = %d AND status = %d', $pid, COMMENT_PUBLISHED)); } return $cache[$pid]; } function comment_load($cid, $published_only = FALSE) { $args = array($cid); $sql = 'SELECT c.*, u.uid, u.signature, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d'; if ($published_only) { $sql .= ' AND c.status = %d'; $args[] = COMMENT_PUBLISHED; } $comment = db_fetch_object(db_query($sql, $args)); return drupal_unpack($comment); } //if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) { /** * Renders comment(s). * * @param $node * The node which comment(s) needs rendering. * @param $cid * Optional, if given, only one comment is rendered. */ function comment_render($node, $cid = 0) { global $user; $output = ''; if (user_access('access comments')) { // Pre-process variables. $nid = $node->nid; if ($cid) { // Single comment view. if ($comment = comment_load($cid, user_access('administer comments'))) { $links = module_invoke_all('link', 'comment', $comment, 1); drupal_alter('link', $links, $node); $output .= theme('comment_view', $comment, $links); } } else { $output .= _comment_render_multiple($nid); } // If enabled, show new comment form. if (user_access('post comments') && node_comment_mode($nid) == COMMENT_NODE_READ_WRITE && (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_BELOW)) { $form = drupal_get_form('comment_form', array(), 0, $nid); $output .= theme('box', t('Post new comment'), $form); } return theme('comment_wrapper', $output); } } function _comment_render_multiple($nid) { $output = ''; $mode = _comment_get_display_setting('mode'); $order = _comment_get_display_setting('sort'); $comments_per_page = _comment_get_display_setting('comments_per_page'); $query_count = 'SELECT COUNT(*) FROM {comments} WHERE nid = %d'; $query = 'SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.signature, u.picture, u.data, c.score, c.users, c.thread, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d'; $query_args = array($nid); if (!user_access('administer comments')) { $query .= ' AND c.status = %d'; $query_count .= ' AND status = %d'; $query_args[] = COMMENT_PUBLISHED; } if ($order == COMMENT_ORDER_NEWEST_FIRST) { if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) { $query .= ' ORDER BY c.timestamp DESC'; } else { $query .= ' ORDER BY c.thread DESC'; } } else if ($order == COMMENT_ORDER_OLDEST_FIRST) { if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) { $query .= ' ORDER BY c.timestamp'; } else { // See comment above. Analysis learns that this doesn't cost too much. // It scales much much better than having the whole comment structure. $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))'; } } // Start a form, for use with comment control. $result = pager_query($query, $comments_per_page, 0, $query_count, $query_args); if (db_num_rows($result) && (variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_ABOVE || variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_ABOVE_BELOW)) { $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page); } $divs = 0; $last_depth = 0; drupal_add_css(drupal_get_path('module', 'comment') .'/comment.css'); while ($comment = db_fetch_object($result)) { $comment = drupal_unpack($comment); $comment->depth = count(explode('.', $comment->thread)) - 1; if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) { if ($comment->depth > $last_depth) { $divs++; $output .= '