'. 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 .= '
'; $last_depth++; } else { while ($comment->depth < $last_depth) { $divs--; $output .= '
'; $last_depth--; } } } if ($mode == COMMENT_MODE_FLAT_COLLAPSED) { $output .= theme('comment_flat_collapsed', $comment); } else if ($mode == COMMENT_MODE_FLAT_EXPANDED) { $output .= theme('comment_flat_expanded', $comment); } else if ($mode == COMMENT_MODE_THREADED_COLLAPSED) { $output .= theme('comment_thread_collapsed', $comment); } else if ($mode == COMMENT_MODE_THREADED_EXPANDED) { $output .= theme('comment_thread_expanded', $comment); } } for ($i = 0; $i < $divs; $i++) { $output .= ''; } $output .= theme('pager', NULL, $comments_per_page, 0); if (db_num_rows($result) && (variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_BELOW || variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_ABOVE_BELOW)) { $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page); } return $output; } /** * Return a current comment display setting * * $setting can be one of these: 'mode', 'sort', 'comments_per_page' */ function _comment_get_display_setting($setting) { global $user; if (isset($_GET[$setting])) { $value = $_GET[$setting]; } else { // get the setting's site default switch ($setting) { case 'mode': $default = variable_get('comment_default_mode', COMMENT_MODE_THREADED_EXPANDED); break; case 'sort': $default = variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST); break; case 'comments_per_page': $default = variable_get('comment_default_per_page', '50'); } if (variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_HIDDEN) { // if comment controls are disabled use site default $value = $default; } else { // otherwise use the user's setting if set if ($user->$setting) { $value = $user->$setting; } else if ($_SESSION['comment_'. $setting]) { $value = $_SESSION['comment_'. $setting]; } else { $value = $default; } } } return $value; } /** * Generate vancode. * * Consists of a leading character indicating length, followed by N digits * with a numerical value in base 36. Vancodes can be sorted as strings * without messing up numerical order. * * It goes: * 00, 01, 02, ..., 0y, 0z, * 110, 111, ... , 1zy, 1zz, * 2100, 2101, ..., 2zzy, 2zzz, * 31000, 31001, ... */ function int2vancode($i = 0) { $num = base_convert((int)$i, 10, 36); $length = strlen($num); return chr($length + ord('0') - 1) . $num; } /** * Decode vancode back to an integer. */ function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } function comment_form_alter(&$form, $form_state, $form_id) { if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { $form['workflow']['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 administer comments permission will be able to override this setting.'), ); } elseif (isset($form['type']) && isset($form['#node'])) { if ($form['type']['#value'] .'_node_form' == $form_id) { $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')), ); } } } /** * Implementation of hook_nodeapi(). * */ function comment_nodeapi(&$node, $op, $arg = 0) { switch ($op) { case 'load': return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid)); break; case 'prepare': if (!isset($node->comment)) { $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE); } break; case 'insert': db_query('INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) VALUES (%d, %d, NULL, %d, 0)', $node->nid, $node->created, $node->uid); break; case 'delete': db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid); db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid); break; case 'update index': $text = ''; $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED); while ($comment = db_fetch_object($comments)) { $text .= '

'. check_plain($comment->subject) .'

'. check_markup($comment->comment, $comment->format, FALSE); } return $text; case 'search result': $comments = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $node->nid)); return format_plural($comments, '1 comment', '@count comments'); case 'rss item': if ($node->comment != COMMENT_NODE_DISABLED) { return array(array('key' => 'comments', 'value' => url('node/'. $node->nid, array('fragment' => 'comments', 'absolute' => TRUE)))); } else { return array(); } } } /** * Implementation of hook_user(). */ function comment_user($type, $edit, &$user, $category = NULL) { if ($type == 'delete') { db_query('UPDATE {comments} SET uid = 0 WHERE uid = %d', $user->uid); db_query('UPDATE {node_comment_statistics} SET last_comment_uid = 0 WHERE last_comment_uid = %d', $user->uid); } } /** * Updates the comment statistics for a given node. This should be called any * time a comment is added, deleted, or updated. * * The following fields are contained in the node_comment_statistics table. * - last_comment_timestamp: the timestamp of the last comment for this node or the node create stamp if no comments exist for the node. * - last_comment_name: the name of the anonymous poster for the last comment * - last_comment_uid: the uid of the poster for the last comment for this node or the node authors uid if no comments exists for the node. * - comment_count: the total number of approved/published comments on this node. */ function _comment_update_node_statistics($nid) { $count = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE nid = %d AND status = %d', $nid, COMMENT_PUBLISHED)); // comments exist if ($count > 0) { $last_reply = db_fetch_object(db_query_range('SELECT cid, name, timestamp, uid FROM {comments} WHERE nid = %d AND status = %d ORDER BY cid DESC', $nid, COMMENT_PUBLISHED, 0, 1)); db_query("UPDATE {node_comment_statistics} SET comment_count = %d, last_comment_timestamp = %d, last_comment_name = '%s', last_comment_uid = %d WHERE nid = %d", $count, $last_reply->timestamp, $last_reply->uid ? '' : $last_reply->name, $last_reply->uid, $nid); } // no comments else { $node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE nid = %d", $nid)); db_query("UPDATE {node_comment_statistics} SET comment_count = 0, last_comment_timestamp = %d, last_comment_name = '', last_comment_uid = %d WHERE nid = %d", $node->created, $node->uid, $nid); } } /** * Invoke a hook_comment() operation in all modules. * * @param &$comment * A comment object. * @param $op * A string containing the name of the comment operation. * @return * The returned value of the invoked hooks. */ function comment_invoke_comment(&$comment, $op) { $return = array(); foreach (module_implements('comment') as $name) { $function = $name .'_comment'; $result = $function($comment, $op); if (isset($result) && is_array($result)) { $return = array_merge($return, $result); } else if (isset($result)) { $return[] = $result; } } return $return; } /** * Menu callback; presents the comment settings page. */ function comment_admin_settings() { $form['viewing_options'] = array( '#type' => 'fieldset', '#title' => t('Viewing options'), '#collapsible' => TRUE, ); $form['viewing_options']['comment_default_mode'] = array( '#type' => 'radios', '#title' => t('Default display mode'), '#default_value' => variable_get('comment_default_mode', COMMENT_MODE_THREADED_EXPANDED), '#options' => _comment_get_modes(), '#description' => t('The default view for comments. Expanded views display the body of the comment. Threaded views keep replies together.'), ); $form['viewing_options']['comment_default_order'] = array( '#type' => 'radios', '#title' => t('Default display order'), '#default_value' => variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST), '#options' => _comment_get_orders(), '#description' => t('The default sorting for new users and anonymous users while viewing comments. These users may change their view using the comment control panel. For registered users, this change is remembered as a persistent user preference.'), ); $form['viewing_options']['comment_default_per_page'] = array( '#type' => 'select', '#title' => t('Default comments per page'), '#default_value' => variable_get('comment_default_per_page', 50), '#options' => _comment_per_page(), '#description' => t('Default number of comments for each page: more comments are distributed in several pages.'), ); $form['viewing_options']['comment_controls'] = array( '#type' => 'radios', '#title' => t('Comment controls'), '#default_value' => variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN), '#options' => array( t('Display above the comments'), t('Display below the comments'), t('Display above and below the comments'), t('Do not display')), '#description' => t('Position of the comment controls box. The comment controls let the user change the default display mode and display order of comments.'), ); $form['posting_settings'] = array( '#type' => 'fieldset', '#title' => t('Posting settings'), '#collapsible' => TRUE, ); $form['posting_settings']['comment_anonymous'] = array( '#type' => 'radios', '#title' => t('Anonymous commenting'), '#default_value' => variable_get('comment_anonymous', 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 permissions page.', array('@url' => url('admin/user/access', array('fragment' => 'module-comment')))), ); if (!user_access('post comments', user_load(array('uid' => 0)))) { $form['posting_settings']['comment_anonymous']['#disabled'] = TRUE; } $form['posting_settings']['comment_subject_field'] = array( '#type' => 'radios', '#title' => t('Comment subject field'), '#default_value' => variable_get('comment_subject_field', 1), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Can users provide a unique subject for their comments?'), ); $form['posting_settings']['comment_preview'] = array( '#type' => 'radios', '#title' => t('Preview comment'), '#default_value' => variable_get('comment_preview', COMMENT_PREVIEW_REQUIRED), '#options' => array(t('Optional'), t('Required')), ); $form['posting_settings']['comment_form_location'] = array( '#type' => 'radios', '#title' => t('Location of comment submission form'), '#default_value' => variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE), '#options' => array(t('Display on separate page'), t('Display below post or comments')), ); return system_settings_form($form); } function comment_admin_overview() { // Build an 'Update options' form $form['options'] = array( '#type' => 'fieldset', '#title' => t('Update options'), '#prefix' => '
', '#suffix' => '
' ); $options = array(); foreach (comment_operations($arg == 'approval' ? 'publish' : 'unpublish') as $key => $value) { $options[$key] = $value[0]; } $form['options']['operation'] = array( '#type' => 'select', '#options' => $options, '#default_value' => 'publish' ); $form['options']['submit'] = array( '#type' => 'submit', '#value' => t('Update') ); // load the comments that we want to display $status = ($type == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED; $form['header'] = array( '#type' => 'value', '#value' => array( theme('table_select_header_cell'), array('data' => t('Subject'), 'field' => 'subject'), array('data' => t('Author'), 'field' => 'name'), array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'), array('data' => t('Operations')) )); $result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.status = %d'. tablesort_sql($form['header']['#value']), 50, 0, NULL, $status); // build a table listing the appropriate comments $destination = drupal_get_destination(); while ($comment = db_fetch_object($result)) { $comments[$comment->cid] = ''; $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $form['subject'][$comment->cid] = array('#value' => l($comment->subject, 'node/'. $comment->nid, array('title' => truncate_utf8($comment->comment, 128), 'fragment' => 'comment-'. $comment->cid))); $form['username'][$comment->cid] = array('#value' => theme('username', $comment)); $form['timestamp'][$comment->cid] = array('#value' => format_date($comment->timestamp, 'small')); $form['operations'][$comment->cid] = array('#value' => l(t('edit'), 'comment/edit/'. $comment->cid, array('query' => $destination))); } $form['comments'] = array('#type' => 'checkboxes', '#options' => isset($comments) ? $comments: array()); $form['pager'] = array('#value' => theme('pager', NULL, 50, 0)); return $form; } /** * We can't execute any 'Update options' if no comments were selected. */ function comment_admin_overview_validate($form, &$form_state) { $form_state['values']['comments'] = array_diff($form_state['values']['comments'], array(0)); if (count($form_state['values']['comments']) == 0) { form_set_error('', t('Please select one or more comments to perform the update on.')); drupal_goto('admin/content/comment'); } } /** * Execute the chosen 'Update option' on the selected comments, such as * publishing, unpublishing or deleting. */ function comment_admin_overview_submit($form, &$form_state) { $operations = comment_operations(); if ($operations[$form_state['values']['operation']][1]) { // extract the appropriate database query operation $query = $operations[$form_state['values']['operation']][1]; foreach ($form_state['values']['comments'] as $cid => $value) { if ($value) { // perform the update action, then refresh node statistics db_query($query, $cid); $comment = _comment_load($cid); _comment_update_node_statistics($comment->nid); // Allow modules to respond to the updating of a comment. comment_invoke_comment($comment, $form_state['values']['operation']); // Add an entry to the watchdog log. watchdog('content', 'Comment: updated %subject.', array('%subject' => $comment->subject), WATCHDOG_NOTICE, l(t('view'), 'node/'. $comment->nid, array('fragment' => 'comment-'. $comment->cid))); } } cache_clear_all(); drupal_set_message(t('The update has been performed.')); drupal_goto('admin/content/comment'); } } function theme_comment_admin_overview($form) { $output = drupal_render($form['options']); if (isset($form['subject']) && is_array($form['subject'])) { foreach (element_children($form['subject']) as $key) { $row = array(); $row[] = drupal_render($form['comments'][$key]); $row[] = drupal_render($form['subject'][$key]); $row[] = drupal_render($form['username'][$key]); $row[] = drupal_render($form['timestamp'][$key]); $row[] = drupal_render($form['operations'][$key]); $rows[] = $row; } } else { $rows[] = array(array('data' => t('No comments available.'), 'colspan' => '6')); } $output .= theme('table', $form['header']['#value'], $rows); if ($form['pager']['#value']) { $output .= drupal_render($form['pager']); } $output .= drupal_render($form); return $output; } /** * Menu callback; present an administrative comment listing. */ function comment_admin($type = 'new') { $edit = $_POST; if (isset($edit['operation']) && ($edit['operation'] == 'delete') && $edit['comments']) { return drupal_get_form('comment_multiple_delete_confirm'); } else { return drupal_get_form('comment_admin_overview', $type, arg(4)); } } /** * Menu callback; presents the comment settings page. */ function comment_admin_settings() { $form['viewing_options'] = array( '#type' => 'fieldset', '#title' => t('Viewing options'), '#collapsible' => TRUE, ); $form['viewing_options']['comment_default_mode'] = array( '#type' => 'radios', '#title' => t('Default display mode'), '#default_value' => variable_get('comment_default_mode', COMMENT_MODE_THREADED_EXPANDED), '#options' => _comment_get_modes(), '#description' => t('The default view for comments. Expanded views display the body of the comment. Threaded views keep replies together.'), ); $form['viewing_options']['comment_default_order'] = array( '#type' => 'radios', '#title' => t('Default display order'), '#default_value' => variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST), '#options' => _comment_get_orders(), '#description' => t('The default sorting for new users and anonymous users while viewing comments. These users may change their view using the comment control panel. For registered users, this change is remembered as a persistent user preference.'), ); $form['viewing_options']['comment_default_per_page'] = array( '#type' => 'select', '#title' => t('Default comments per page'), '#default_value' => variable_get('comment_default_per_page', 50), '#options' => _comment_per_page(), '#description' => t('Default number of comments for each page: more comments are distributed in several pages.'), ); $form['viewing_options']['comment_controls'] = array( '#type' => 'radios', '#title' => t('Comment controls'), '#default_value' => variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN), '#options' => array( t('Display above the comments'), t('Display below the comments'), t('Display above and below the comments'), t('Do not display')), '#description' => t('Position of the comment controls box. The comment controls let the user change the default display mode and display order of comments.'), ); $form['posting_settings'] = array( '#type' => 'fieldset', '#title' => t('Posting settings'), '#collapsible' => TRUE, ); $form['posting_settings']['comment_anonymous'] = array( '#type' => 'radios', '#title' => t('Anonymous commenting'), '#default_value' => variable_get('comment_anonymous', 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 permissions page.', array('@url' => url('admin/user/access', array('fragment' => 'module-comment')))), ); if (!user_access('post comments', user_load(array('uid' => 0)))) { $form['posting_settings']['comment_anonymous']['#disabled'] = TRUE; } $form['posting_settings']['comment_subject_field'] = array( '#type' => 'radios', '#title' => t('Comment subject field'), '#default_value' => variable_get('comment_subject_field', 1), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Can users provide a unique subject for their comments?'), ); $form['posting_settings']['comment_preview'] = array( '#type' => 'radios', '#title' => t('Preview comment'), '#default_value' => variable_get('comment_preview', COMMENT_PREVIEW_REQUIRED), '#options' => array(t('Optional'), t('Required')), ); $form['posting_settings']['comment_form_location'] = array( '#type' => 'radios', '#title' => t('Location of comment submission form'), '#default_value' => variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE), '#options' => array(t('Display on separate page'), t('Display below post or comments')), ); return system_settings_form($form); } /** * List the selected comments and verify that the admin really wants to delete * them. */ function comment_multiple_delete_confirm(&$form_state) { $edit = $form_state['post']; $form['comments'] = array('#prefix' => '', '#tree' => TRUE); // array_filter() returns only elements with actual values $comment_counter = 0; foreach (array_filter($edit['comments']) as $cid => $value) { $comment = _comment_load($cid); if (is_object($comment) && is_numeric($comment->cid)) { $subject = db_result(db_query('SELECT subject FROM {comments} WHERE cid = %d', $cid)); $form['comments'][$cid] = array('#type' => 'hidden', '#value' => $cid, '#prefix' => '
  • ', '#suffix' => check_plain($subject) .'
  • '); $comment_counter++; } } $form['operation'] = array('#type' => 'hidden', '#value' => 'delete'); if (!$comment_counter) { drupal_set_message(t('There do not appear to be any comments to delete or your selected comment was deleted by another administrator.')); drupal_goto('admin/content/comment'); } else { return confirm_form($form, t('Are you sure you want to delete these comments and all their children?'), 'admin/content/comment', t('This action cannot be undone.'), t('Delete comments'), t('Cancel')); } } /** * Perform the actual comment deletion. */ function comment_multiple_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { foreach ($form_state['values']['comments'] as $cid => $value) { $comment = _comment_load($cid); _comment_delete_thread($comment); _comment_update_node_statistics($comment->nid); } cache_clear_all(); drupal_set_message(t('The comments have been deleted.')); } drupal_goto('admin/content/comment'); }