Skip to content
comment.module 78.4 KiB
Newer Older
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
Dries Buytaert committed
/**
 * @file
Dries Buytaert's avatar
 
Dries Buytaert committed
 * Enables users to comment on published content.
Dries Buytaert's avatar
Dries Buytaert committed
 *
 * When enabled, the Drupal comment module creates a discussion
 * board for each Drupal node. Users can post comments to discuss
 * a forum topic, weblog post, story, collaborative book page, etc.
 */

/**
 * Comment is published.
define('COMMENT_PUBLISHED', 0);

/**
 * Comment is awaiting approval.
 */
define('COMMENT_NOT_PUBLISHED', 1);

/**
 * Comments are displayed in a flat list - collapsed.
define('COMMENT_MODE_FLAT_COLLAPSED', 1);

/**
 * Comments are displayed in a flat list - expanded.
 */
define('COMMENT_MODE_FLAT_EXPANDED', 2);

/**
 * Comments are displayed as a threaded list - collapsed.
 */
define('COMMENT_MODE_THREADED_COLLAPSED', 3);

/**
 * Comments are displayed as a threaded list - expanded.
 */
define('COMMENT_MODE_THREADED_EXPANDED', 4);
 * Comments are ordered by date - newest first.
define('COMMENT_ORDER_NEWEST_FIRST', 1);

/**
 * Comments are ordered by date - oldest first.
 */
define('COMMENT_ORDER_OLDEST_FIRST', 2);
 * Comment controls should be shown above the comment list.
 */
define('COMMENT_CONTROLS_ABOVE', 0);

/**
 * Comment controls should be shown below the comment list.
 */
define('COMMENT_CONTROLS_BELOW', 1);

/**
 * Comment controls should be shown both above and below the comment list.
 */
define('COMMENT_CONTROLS_ABOVE_BELOW', 2);

/**
 * Comment controls are hidden.
 */
define('COMMENT_CONTROLS_HIDDEN', 3);

/**
 * Anonymous posters may not enter their contact information.
 */
define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);

/**
 * Anonymous posters may leave their contact information.
 */
define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);

/**
 * Anonymous posters must leave their contact information.
 */
define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);

/**
 * Comment form should be displayed on a separate page.
 */
define('COMMENT_FORM_SEPARATE_PAGE', 0);

/**
 * Comment form should be shown below post or list of comments.
 */
define('COMMENT_FORM_BELOW', 1);

/**
 * Comments for this node are disabled.
 */
define('COMMENT_NODE_DISABLED', 0);

/**
 * Comments for this node are locked.
 */
define('COMMENT_NODE_READ_ONLY', 1);

/**
 * Comments are enabled on this node.
 */
define('COMMENT_NODE_READ_WRITE', 2);
 * Comment preview is optional.

/**
 * Comment preview is required.
 */
/**
 * Implementation of hook_help().
 */
function comment_help($path, $arg) {
  switch ($path) {
Dries Buytaert's avatar
 
Dries Buytaert committed
    case 'admin/help#comment':
      $output = '<p>'. 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.') .'</p>';
      $output .= '<p>'. 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.') .'</p>';
      $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@comment">Comment page</a>.', array('@comment' => 'http://drupal.org/handbook/modules/comment/')) .'</p>';
      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/list/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>';
      return '<p>'. 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.") .'</p>';
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * 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, 'node' => NULL, 'links' => array(), 'visible' => 1),
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
    ),
    'comment_controls' => array(
      'arguments' => array('form' => NULL),
    ),
    'comment' => array(
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array()),
      'arguments' => array('comment' => NULL),
    ),
    'comment_flat_collapsed' => array(
      'arguments' => array('comment' => NULL, 'node' => NULL),
      'arguments' => array('comment' => NULL, 'node' => NULL),
      'arguments' => array('comment' => NULL, 'node' => NULL),
      'arguments' => array('comment' => NULL, 'node' => NULL),
    ),
    'comment_post_forbidden' => array(
      'arguments' => array('nid' => NULL),
    ),
    'comment_wrapper' => array(
      'arguments' => array('content' => NULL, 'node' => NULL),
    'comment_submitted' => array(
      'arguments' => array('comment' => NULL),
    ),
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * 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'),
  );
Dries Buytaert's avatar
 
Dries Buytaert committed

  // Tabs:
  $items['admin/content/comment/list'] = array(
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
Dries Buytaert's avatar
 
Dries Buytaert committed

  // Subtabs:
  $items['admin/content/comment/list/new'] = array(
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/content/comment/list/approval'] = array(
    'page arguments' => array('approval'),
    'type' => MENU_LOCAL_TASK,
  );

  $items['admin/content/comment/settings'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array('comment_admin_settings'),
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
  );

  $items['comment/delete'] = array(
    'access arguments' => array('administer comments'),
    'type' => MENU_CALLBACK,
  );

  $items['comment/edit'] = array(
    'page callback' => 'comment_edit',
    'access arguments' => array('post comments'),
    'type' => MENU_CALLBACK,
  );
  $items['comment/reply/%node'] = array(
    'page arguments' => array(2),
    'access callback' => 'node_access',
    'access arguments' => array('view', 2),
    'type' => MENU_CALLBACK,
  );
Dries Buytaert's avatar
 
Dries Buytaert committed

  return $items;
}

/**
 * Implementation of hook_perm().
 */
function comment_perm() {
  return array('access comments', 'post comments', 'administer comments', 'post comments without approval');
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * Implementation of hook_block().
 *
 * Generates a block with the most recent comments.
 */
function comment_block($op = 'list', $delta = 0) {
  if ($op == 'list') {
    $blocks[0]['info'] = t('Recent comments');
    return $blocks;
  }
  else if ($op == 'view' && user_access('access comments')) {
Dries Buytaert's avatar
 
Dries Buytaert committed
    $block['subject'] = t('Recent comments');
    $block['content'] = theme('comment_block');
Dries Buytaert's avatar
 
Dries Buytaert committed
    return $block;
  }
}

/**
 * Find a number of recent comments. This is done in two steps.
 *   1. Find the n (specified by $number) nodes that have the most recent
 *      comments.  This is done by querying node_comment_statistics which has
 *      an index on last_comment_timestamp, and is thus a fast query.
 *   2. Loading the information from the comments table based on the nids found
 *      in step 1.
 *
 * @param $number (optional) The maximum number of comments to find.
 * @return $comments An array of comment objects each containing a nid,
 *   subject, cid, and timestamp, or an empty array if there are no recent
 *   comments visible to the current user.
 */
function comment_get_recent($number = 10) {
  // Select the $number nodes (visible to the current user) with the most
  // recent comments. This is efficient due to the index on
  // last_comment_timestamp.
  $result = db_query_range(db_rewrite_sql("SELECT nc.nid FROM {node_comment_statistics} nc WHERE nc.comment_count > 0 ORDER BY nc.last_comment_timestamp DESC", 'nc'), 0, $number);

  $nids = array();
  while ($row = db_fetch_object($result)) {
    $nids[] = $row->nid;
  }

  $comments = array();
  if (!empty($nids)) {
    // From among the comments on the nodes selected in the first query,
    // find the $number most recent comments.
    $result = db_query_range('SELECT c.nid, c.subject, c.cid, c.timestamp FROM {comments} c INNER JOIN {node} n ON n.nid = c.nid WHERE c.nid IN ('. implode(',', $nids) .') AND n.status = 1 AND c.status = %d ORDER BY c.timestamp DESC', COMMENT_PUBLISHED, 0, $number);
    while ($comment = db_fetch_object($result)) {
      $comments[] = $comment;
    }
  }

  return $comments;
}

/**
 * Returns a formatted list of recent comments to be displayed in the comment
 * block.
 *
 * @ingroup themeable
 */
function theme_comment_block() {
  $items = array();
  foreach (comment_get_recent() as $comment) {
    $items[] = l($comment->subject, 'node/'. $comment->nid, array('fragment' => 'comment-'. $comment->cid)) .'<br />'. t('@time ago', array('@time' => format_interval(time() - $comment->timestamp)));
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Implementation of hook_link().
 */
function comment_link($type, $node = NULL, $teaser = FALSE) {
Dries Buytaert's avatar
 
Dries Buytaert committed
  $links = array();

  if ($type == 'node' && $node->comment) {

Dries Buytaert's avatar
 
Dries Buytaert committed
      // Main page: display the number of comments that have been posted.

      if (user_access('access comments')) {
        $all = comment_num_all($node->nid);

        if ($all) {
            'title' => format_plural($all, '1 comment', '@count comments'),
            'href' => "node/$node->nid",
            'attributes' => array('title' => t('Jump to the first comment of this posting.')),
            'fragment' => 'comments'
Dries Buytaert's avatar
 
Dries Buytaert committed

          if ($new) {
              'title' => format_plural($new, '1 new comment', '@count new comments'),
              'href' => "node/$node->nid",
              'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
              'fragment' => 'new'
Dries Buytaert's avatar
 
Dries Buytaert committed
          }
        }
        else {
          if ($node->comment == COMMENT_NODE_READ_WRITE) {
Dries Buytaert's avatar
 
Dries Buytaert committed
            if (user_access('post comments')) {
                'title' => t('Add new comment'),
                'href' => "comment/reply/$node->nid",
                'attributes' => array('title' => t('Add a new comment to this page.')),
                'fragment' => 'comment-form'
Dries Buytaert's avatar
 
Dries Buytaert committed
            }
            else {
              $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node->nid);
Dries Buytaert's avatar
 
Dries Buytaert committed
            }
          }
        }
      }
    }
    else {
      // Node page: add a "post comment" link if the user is allowed to
      // post comments, if this node is not read-only, and if the comment form isn't already shown

      if ($node->comment == COMMENT_NODE_READ_WRITE) {
Dries Buytaert's avatar
 
Dries Buytaert committed
        if (user_access('post comments')) {
          if (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
              'href' => "comment/reply/$node->nid",
              'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
              'fragment' => 'comment-form'
Dries Buytaert's avatar
 
Dries Buytaert committed
        }
        else {
          $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node->nid);
Dries Buytaert's avatar
 
Dries Buytaert committed
        }
      }
    }
  }

  if ($type == 'comment') {
    $links = comment_links($node, $teaser);
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
  if (isset($links['comment_forbidden'])) {
    $links['comment_forbidden']['html'] = TRUE;
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

  return $links;
}

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 <em>administer comments</em> permission will be able to override this setting.'),
    );
  }
  elseif (isset($form['type']) && isset($form['#node'])) {
    if ($form['type']['#value'] .'_node_form' == $form_id) {
      $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')),
      );
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Implementation of hook_nodeapi().
Dries Buytaert's avatar
 
Dries Buytaert committed
 *
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function comment_nodeapi(&$node, $op, $arg = 0) {
  switch ($op) {
Dries Buytaert's avatar
 
Dries Buytaert committed
    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);
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
      break;
      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->changed, $node->uid);
      db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
      db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
    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 .= '<h2>'. check_plain($comment->subject) .'</h2>'. 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');
Steven Wittens's avatar
Steven Wittens committed
    case 'rss item':
      if ($node->comment != COMMENT_NODE_DISABLED) {
        return array(array('key' => 'comments', 'value' => url('node/'. $node->nid, array('fragment' => 'comments', 'absolute' => TRUE))));
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
}

/**
 * Implementation of hook_user().
 */
function comment_user($type, $edit, &$user, $category = NULL) {
    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);
  }
Dries Buytaert's avatar
 
Dries Buytaert committed
}

Dries Buytaert's avatar
 
Dries Buytaert committed
 * Menu callback; presents the comment settings page.
Dries Buytaert's avatar
Dries Buytaert committed
function comment_admin_settings() {
  $form['viewing_options'] = array(
    '#type' => 'fieldset',
Dries Buytaert's avatar
 
Dries Buytaert committed

  $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.'),
Dries Buytaert's avatar
 
Dries Buytaert committed

  $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',
  $form['posting_settings']['comment_anonymous'] = array(
    '#type' => 'radios',
    '#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 <a href="@url">permissions page</a>.', 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')),
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * This is *not* a hook_access() implementation. This function is called
 * to determine whether the current user has access to a particular comment.
 *
 * Authenticated users can edit their comments as long they have not been
 * replied to. This prevents people from changing or revising their
 * statements based on the replies to their posts.
Dries Buytaert's avatar
 
Dries Buytaert committed
function comment_access($op, $comment) {
Dries Buytaert's avatar
 
Dries Buytaert committed
  global $user;

  if ($op == 'edit') {
    return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments');
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
}
Dries Buytaert's avatar
 
Dries Buytaert committed
function comment_node_url() {
Dries Buytaert's avatar
Dries Buytaert committed
  return arg(0) .'/'. arg(1);
Dries Buytaert's avatar
 
Dries Buytaert committed
}
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
function comment_edit($cid) {
  global $user;

  $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d', $cid));
Dries Buytaert's avatar
 
Dries Buytaert committed
  $comment = drupal_unpack($comment);
  $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
  if (comment_access('edit', $comment)) {
    return comment_form_box((array)$comment);
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
}

/**
 * This function is responsible for generating a comment reply form.
 * There are several cases that have to be handled, including:
 *   - replies to comments
 *   - replies to nodes
 *   - attempts to reply to nodes that can no longer accept comments
 *   - respecting access permissions ('access comments', 'post comments', etc.)
 *
 * The node or comment that is being replied to must appear above the comment
 * form to provide the user context while authoring the comment.
 *
 * @param $node
 *   Every comment belongs to a node. This is that node.
 * @param $pid
 *   Some comments are replies to other comments. In those cases, $pid is the parent
 *   comment's cid.
 *
 * @return $output
 *   The rendered parent node or comment plus the new comment form.
 */
function comment_reply($node, $pid = NULL) {
  // Set the breadcrumb trail.
  menu_set_location(array(array('path' => "node/$node->nid", 'title' => $node->title), array('path' => "comment/reply/$node->nid")));
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
Dries Buytaert committed
  if (user_access('access comments')) {
    // The user is previewing a comment prior to submitting it.
    if ($op == t('Preview comment')) {
      if (user_access('post comments')) {
        $output .= comment_form_box(array('pid' => $pid, 'nid' => $node->nid), NULL);
      }
      else {
        drupal_set_message(t('You are not authorized to post comments.'), 'error');
        drupal_goto("node/$node->nid");
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
    else {
      // $pid indicates that this is a reply to a comment.
        // load the comment whose cid = $pid
        if ($comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d', $pid, COMMENT_PUBLISHED))) {
          // If that comment exists, make sure that the current comment and the parent comment both
          // belong to the same parent node.
          if ($comment->nid != $node->nid) {
            // Attempting to reply to a comment not belonging to the current nid.
            drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
            drupal_goto("node/$node->nid");
          // Display the parent comment
          $comment = drupal_unpack($comment);
          $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
          $output .= theme('comment_view', $comment, $node);
        }
        else {
          drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
          drupal_goto("node/$node->nid");
      // This is the case where the comment is in response to a node. Display the node.
      else if (user_access('access content')) {
        $output .= node_view($node);
      }

      // Should we show the reply box?
      if (node_comment_mode($node->nid) != COMMENT_NODE_READ_WRITE) {
        drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error');
        drupal_goto("node/$node->nid");
        $output .= comment_form_box(array('pid' => $pid, 'nid' => $node->nid), t('Reply'));
      }
      else {
        drupal_set_message(t('You are not authorized to post comments.'), 'error');
        drupal_goto("node/$node->nid");
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
  else {
    drupal_set_message(t('You are not authorized to view comments.'), 'error');
    drupal_goto("node/$node->nid");
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
  return $output;
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * Accepts a submission of new or changed comment content.
 *
 * @param $edit
 *   A comment array.
 *
 * @return
 *   If the comment is successfully saved the comment ID is returned. If the comment
 *   is not saved, FALSE is returned.
 */
function comment_save($edit) {
Dries Buytaert's avatar
 
Dries Buytaert committed
  global $user;
  if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
Dries Buytaert's avatar
 
Dries Buytaert committed
    if (!form_get_errors()) {
        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", $edit['status'], $edit['timestamp'], $edit['subject'], $edit['comment'], $edit['format'], $edit['uid'], $edit['name'], $edit['mail'], $edit['homepage'], $edit['cid']);
Dries Buytaert's avatar
 
Dries Buytaert committed

        // Allow modules to respond to the updating of a comment.
        comment_invoke_comment($edit, 'update');

        // Add an entry to the watchdog log.
        watchdog('content', 'Comment: updated %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], array('fragment' => 'comment-'. $edit['cid'])));
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
      else {
        // Add the comment to database.
        $status = user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
Dries Buytaert's avatar
 
Dries Buytaert committed

        // Here we are building the thread field. See the documentation for
        // comment_render().
        if ($edit['pid'] == 0) {
          // 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', $edit['nid']));
Dries Buytaert's avatar
 
Dries Buytaert committed

          // Strip the "/" from the end of the thread.
          $max = rtrim($max, '/');
Dries Buytaert's avatar
 
Dries Buytaert committed

          // Finally, build the thread field for this new comment.
          $thread = int2vancode(vancode2int($max) + 1) .'/';
Dries Buytaert's avatar
 
Dries Buytaert committed
        }
        else {
          // This is comment with a parent comment: we increase
          // the part of the thread value at the proper depth.
Dries Buytaert's avatar
 
Dries Buytaert committed

          // Get the parent comment:
          $parent = _comment_load($edit['pid']);
Dries Buytaert's avatar
 
Dries Buytaert committed

          // Strip the "/" from the end of the parent thread.
          $parent->thread = (string) rtrim((string) $parent->thread, '/');
Dries Buytaert's avatar
 
Dries Buytaert committed

          // Get the max value in _this_ thread.
Dries Buytaert's avatar
 
Dries Buytaert committed
          $max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $edit['nid']));
Dries Buytaert's avatar
 
Dries Buytaert committed

          if ($max == '') {
            // First child of this parent.
            $thread = $parent->thread .'.'. int2vancode(0) .'/';
Dries Buytaert's avatar
 
Dries Buytaert committed
          }
          else {
            // Strip the "/" at the end of the thread.
            $max = rtrim($max, '/');
Dries Buytaert's avatar
 
Dries Buytaert committed

            // We need to get the value at the correct depth.
            $parts = explode('.', $max);
            $parent_depth = count(explode('.', $parent->thread));
Dries Buytaert's avatar
 
Dries Buytaert committed
            $last = $parts[$parent_depth];

            // Finally, build the thread field for this new comment.
            $thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/';
Dries Buytaert's avatar
 
Dries Buytaert committed
          }
        }

Dries Buytaert's avatar
 
Dries Buytaert committed
        $edit['timestamp'] = time();

        if ($edit['uid'] === $user->uid) { // '===' because we want to modify anonymous users too
Dries Buytaert's avatar
 
Dries Buytaert committed
          $edit['name'] = $user->name;
        }

        $edit += array('mail' => '', 'homepage' => '');
        db_query("INSERT INTO {comments} (nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $edit['nid'], $edit['pid'], $edit['uid'], $edit['subject'], $edit['comment'], $edit['format'], ip_address(), $edit['timestamp'], $status, $thread, $edit['name'], $edit['mail'], $edit['homepage']);
        $edit['cid'] = db_last_insert_id('comments', 'cid');
Dries Buytaert's avatar
 
Dries Buytaert committed

        // Tell the other modules a new comment has been submitted.
        comment_invoke_comment($edit, 'insert');
Dries Buytaert's avatar
 
Dries Buytaert committed

        // Add an entry to the watchdog log.
        watchdog('content', 'Comment: added %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], array('fragment' => 'comment-'. $edit['cid'])));
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
      _comment_update_node_statistics($edit['nid']);
Dries Buytaert's avatar
 
Dries Buytaert committed

      // Clear the cache so an anonymous user can see his comment being added.
Dries Buytaert's avatar
 
Dries Buytaert committed
      cache_clear_all();
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
      // Explain the approval queue if necessary, and then
Dries Buytaert's avatar
 
Dries Buytaert committed
      // redirect the user to the node he's commenting on.
Dries Buytaert's avatar
 
Dries Buytaert committed
        drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
    else {
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
  }
Dries Buytaert's avatar
 
Dries Buytaert committed
  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');
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
}

function comment_links($comment, $return = 1) {
Dries Buytaert's avatar
 
Dries Buytaert committed
  global $user;
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
  $links = array();
Dries Buytaert's avatar
 
Dries Buytaert committed

  // If we are viewing just this comment, we link back to the node.
Dries Buytaert's avatar
 
Dries Buytaert committed
  if ($return) {
      'title' => t('parent'),
      'href' => comment_node_url(),
      'fragment' => "comment-$comment->cid"
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
    if (user_access('administer comments') && user_access('post comments')) {
        'title' => t('delete'),
        'href' => "comment/delete/$comment->cid"
        'title' => t('edit'),
        'href' => "comment/edit/$comment->cid"
        'title' => t('reply'),
        'href' => "comment/reply/$comment->nid/$comment->cid"
    else if (user_access('post comments')) {
      if (comment_access('edit', $comment)) {
          'title' => t('edit'),
          'href' => "comment/edit/$comment->cid"
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
        'title' => t('reply'),
        'href' => "comment/reply/$comment->nid/$comment->cid"
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
    else {
      $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $comment->nid);
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
Dries Buytaert's avatar
 
Dries Buytaert committed
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
  return $links;
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * Renders comment(s).
 *
 * @param $node
 *   The node which comment(s) needs rendering.
 * @param $cid
 *   Optional, if given, only one comment is rendered.
 *
 * 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.
 */
Dries Buytaert's avatar
 
Dries Buytaert committed
function comment_render($node, $cid = 0) {
Dries Buytaert's avatar
 
Dries Buytaert committed
  global $user;

Dries Buytaert's avatar
 
Dries Buytaert committed

  if (user_access('access comments')) {
    // Pre-process variables.
Dries Buytaert's avatar
 
Dries Buytaert committed
    $nid = $node->nid;
Dries Buytaert's avatar
 
Dries Buytaert committed
    if (empty($nid)) {
      $nid = 0;
Dries Buytaert's avatar
 
Dries Buytaert committed
    }

    $mode = _comment_get_display_setting('mode');
    $order = _comment_get_display_setting('sort');
    $comments_per_page = _comment_get_display_setting('comments_per_page');
Dries Buytaert's avatar
 
Dries Buytaert committed

      // Single comment view.
      $query = 'SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.signature, u.picture, u.data, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d';
      $query_args = array($cid);
      if (!user_access('administer comments')) {
        $query .= ' AND c.status = %d';
        $query_args[] = COMMENT_PUBLISHED;
      }
      $query = db_rewrite_sql($query, 'c', 'cid');
      $result = db_query($query, $query_args);
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
      if ($comment = db_fetch_object($result)) {
        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
        $links = module_invoke_all('link', 'comment', $comment, 1);
        drupal_alter('link', $links, $node);
        $output .= theme('comment_view', $comment, $node, $links);
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
Dries Buytaert's avatar
 
Dries Buytaert committed
    else {
      // Multiple comment view
      $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.name AS registered_name, u.signature, u.picture, u.data, 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;
      }
Dries Buytaert's avatar
 
Dries Buytaert committed

      if ($order == COMMENT_ORDER_NEWEST_FIRST) {
        if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
          $query .= ' ORDER BY c.timestamp DESC';
Dries Buytaert's avatar
 
Dries Buytaert committed
        }
        else {
          $query .= ' ORDER BY c.thread DESC';
Dries Buytaert's avatar
 
Dries Buytaert committed
        }
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
      else if ($order == COMMENT_ORDER_OLDEST_FIRST) {
        if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
          $query .= ' ORDER BY c.timestamp';
Dries Buytaert's avatar
 
Dries Buytaert committed
        }
        else {