You are here

comment.module in Drupal 4

Enables users to comment on published content.

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.

File

modules/comment.module
View source
<?php

/**
 * @file
 * Enables users to comment on published content.
 *
 * 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.
 */

/*
 * Constants to define a comment's published state
 */
define('COMMENT_PUBLISHED', 0);
define('COMMENT_NOT_PUBLISHED', 1);

/**
 * Constants to define the viewing modes for comment listings
 */
define('COMMENT_MODE_FLAT_COLLAPSED', 1);
define('COMMENT_MODE_FLAT_EXPANDED', 2);
define('COMMENT_MODE_THREADED_COLLAPSED', 3);
define('COMMENT_MODE_THREADED_EXPANDED', 4);

/**
 * Constants to define the viewing orders for comment listings
 */
define('COMMENT_ORDER_NEWEST_FIRST', 1);
define('COMMENT_ORDER_OLDEST_FIRST', 2);

/**
 * Constants to define the position of the comment controls
 */
define('COMMENT_CONTROLS_ABOVE', 0);
define('COMMENT_CONTROLS_BELOW', 1);
define('COMMENT_CONTROLS_ABOVE_BELOW', 2);
define('COMMENT_CONTROLS_HIDDEN', 3);

/**
 * Constants to define the anonymous poster contact handling
 */
define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);
define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);
define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);

/**
 * Constants to define the comment form location
 */
define('COMMENT_FORM_SEPARATE_PAGE', 0);
define('COMMENT_FORM_BELOW', 1);

/**
 * Constants to define a node's comment state
 */
define('COMMENT_NODE_DISABLED', 0);
define('COMMENT_NODE_READ_ONLY', 1);
define('COMMENT_NODE_READ_WRITE', 2);

/**
 * Constants to define if comment preview is optional or required
 */
define('COMMENT_PREVIEW_OPTIONAL', 0);
define('COMMENT_PREVIEW_REQUIRED', 1);

/**
 * Implementation of hook_help().
 */
function comment_help($section) {
  switch ($section) {
    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 .= t('<p>You can</p>
<ul>
<li>control access for various comment module functions through access permissions <a href="%admin-access">administer &gt;&gt; access control</a>.</li>
<li>administer comments <a href="%admin-comment-configure"> administer &gt;&gt; comments &gt;&gt; configure</a>.</li>
</ul>
', array(
        '%admin-access' => url('admin/access'),
        '%admin-comment-configure' => url('admin/comment/configure'),
      ));
      $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 $output;
    case 'admin/modules#description':
      return t('Allows users to comment on and discuss published content.');
    case 'admin/comment':
    case 'admin/comment/new':
      return t("<p>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/comment/approval':
      return t("<p>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>");
    case 'admin/comment/configure':
    case 'admin/comment/configure/settings':
      return t("<p>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>");
  }
}

/**
 * Implementation of hook_menu().
 */
function comment_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $access = user_access('administer comments');
    $items[] = array(
      'path' => 'admin/comment',
      'title' => t('comments'),
      'callback' => 'comment_admin_overview',
      'access' => $access,
    );

    // Tabs:
    $items[] = array(
      'path' => 'admin/comment/list',
      'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/comment/configure',
      'title' => t('configure'),
      'callback' => 'comment_configure',
      'access' => $access,
      'type' => MENU_LOCAL_TASK,
    );

    // Subtabs:
    $items[] = array(
      'path' => 'admin/comment/list/new',
      'title' => t('published comments'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/comment/list/approval',
      'title' => t('approval queue'),
      'callback' => 'comment_admin_overview',
      'access' => $access,
      'callback arguments' => array(
        'approval',
      ),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/comment/configure/settings',
      'title' => t('settings'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'comment/delete',
      'title' => t('delete comment'),
      'callback' => 'comment_delete',
      'access' => $access,
      'type' => MENU_CALLBACK,
    );
    $access = user_access('post comments');
    $items[] = array(
      'path' => 'comment/edit',
      'title' => t('edit comment'),
      'callback' => 'comment_edit',
      'access' => $access,
      'type' => MENU_CALLBACK,
    );
  }
  else {
    if (arg(0) == 'comment' && arg(1) == 'reply' && is_numeric(arg(2))) {
      $node = node_load(arg(2));
      if ($node->nid) {
        $items[] = array(
          'path' => 'comment/reply',
          'title' => t('reply to comment'),
          'callback' => 'comment_reply',
          'access' => node_access('view', $node),
          'type' => MENU_CALLBACK,
        );
      }
    }
    if (arg(0) == 'node' && is_numeric(arg(1)) && is_numeric(arg(2))) {
      $items[] = array(
        'path' => 'node/' . arg(1) . '/' . arg(2),
        'title' => t('view'),
        'callback' => 'node_page',
        'type' => MENU_CALLBACK,
      );
    }
  }
  return $items;
}

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

/**
 * 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')) {
      $block['subject'] = t('Recent comments');
      $block['content'] = theme('comment_block');
      return $block;
    }
  }
}
function theme_comment_block() {
  $result = db_query_range(db_rewrite_sql('SELECT c.nid, c.subject, c.cid, c.timestamp FROM {comments} c INNER JOIN {node} n ON n.nid = c.nid WHERE n.status = 1 AND c.status = %d ORDER BY c.timestamp DESC', 'c'), COMMENT_PUBLISHED, 0, 10);
  $items = array();
  while ($comment = db_fetch_object($result)) {
    $items[] = l($comment->subject, 'node/' . $comment->nid, NULL, NULL, 'comment-' . $comment->cid) . '<br />' . t('%time ago', array(
      '%time' => format_interval(time() - $comment->timestamp),
    ));
  }
  return theme('item_list', $items);
}

/**
 * Implementation of hook_link().
 */
function comment_link($type, $node = 0, $main = 0) {
  $links = array();
  if ($type == 'node' && $node->comment) {
    if ($main) {

      // Main page: display the number of comments that have been posted.
      if (user_access('access comments')) {
        $all = comment_num_all($node->nid);
        if ($all) {
          $links[] = l(format_plural($all, '1 comment', '%count comments'), "node/{$node->nid}", array(
            'title' => t('Jump to the first comment of this posting.'),
          ), NULL, 'comment');
          $new = comment_num_new($node->nid);
          if ($new) {
            $links[] = l(format_plural($new, '1 new comment', '%count new comments'), "node/{$node->nid}", array(
              'title' => t('Jump to the first new comment of this posting.'),
            ), NULL, 'new');
          }
        }
        else {
          if ($node->comment == COMMENT_NODE_READ_WRITE) {
            if (user_access('post comments')) {
              $links[] = l(t('add new comment'), "comment/reply/{$node->nid}", array(
                'title' => t('Add a new comment to this page.'),
              ), NULL, 'comment_form');
            }
            else {
              $links[] = theme('comment_post_forbidden', $node->nid);
            }
          }
        }
      }
    }
    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) {
        if (user_access('post comments')) {
          if (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
            $links[] = l(t('add new comment'), "comment/reply/{$node->nid}", array(
              'title' => t('Share your thoughts and opinions related to this posting.'),
            ), NULL, 'comment_form');
          }
        }
        else {
          $links[] = theme('comment_post_forbidden', $node->nid);
        }
      }
    }
  }
  if ($type == 'comment') {
    $links = comment_links($node, $main);
  }
  return $links;
}
function comment_form_alter($form_id, &$form) {
  if (isset($form['type'])) {
    if ($form['type']['#value'] . '_node_settings' == $form_id) {
      $form['workflow']['comment_' . $form['type']['#value']] = array(
        '#type' => 'radios',
        '#title' => t('Default comment setting'),
        '#default_value' => variable_get('comment_' . $form['type']['#value'], 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.'),
      );
    }
    if ($form['type']['#value'] . '_node_form' == $form_id) {
      $node = $form['#node'];
      if (user_access('administer comments')) {
        $form['comment_settings'] = array(
          '#type' => 'fieldset',
          '#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'),
          ),
        );
      }
      else {
        $form['comment_settings']['comment'] = array(
          '#type' => 'value',
          '#value' => $node->comment,
        );
      }
    }
  }
}

/**
 * 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 .= '<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');
    case 'rss item':
      return array(
        array(
          'key' => 'comments',
          'value' => url('node/' . $node->nid, NULL, 'comment', TRUE),
        ),
      );
  }
}

/**
 * Implementation of hook_user().
 *
 * Provides signature customization for the user's comments.
 */
function comment_user($type, $edit, &$user, $category = NULL) {
  if ($type == 'form' && $category == 'account') {

    // when user tries to edit his own data
    $form['comment_settings'] = array(
      '#type' => 'fieldset',
      '#title' => t('Comment settings'),
      '#collapsible' => TRUE,
      '#weight' => 4,
    );
    $form['comment_settings']['signature'] = array(
      '#type' => 'textarea',
      '#title' => t('Signature'),
      '#default_value' => $edit['signature'],
      '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
    );
    return $form;
  }
  elseif ($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);
  }
}

/**
 * Menu callback; presents the comment settings page.
 */
function comment_configure() {
  $form['viewing_options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Viewing options'),
    '#collapsible' => TRUE,
    '#collapsed' => 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,
    '#collapsed' => 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 <a href="%url">permissions page</a>.', array(
      '%url' => url('admin/access'),
    )),
  );
  if (!user_access('post comments', user_load(array(
    'uid' => 0,
  )))) {
    $form['posting_settings']['comment_anonymous']['#attributes'] = array(
      'disabled' => 'disabled',
    );
  }
  $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('comment_settings_form', $form);
}

/**
 * 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.
 */
function comment_access($op, $comment) {
  global $user;
  if ($op == 'edit') {
    return $user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0 || user_access('administer comments');
  }
}
function comment_node_url() {
  return arg(0) . '/' . arg(1);
}
function comment_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));
  $comment = drupal_unpack($comment);
  $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
  if (comment_access('edit', $comment)) {
    return comment_form((array) $comment);
  }
  else {
    drupal_access_denied();
  }
}
function comment_reply($nid, $pid = NULL) {

  // set the breadcrumb trail
  $node = node_load($nid);
  menu_set_location(array(
    array(
      'path' => "node/{$nid}",
      'title' => $node->title,
    ),
    array(
      'path' => "comment/reply/{$nid}",
    ),
  ));
  $op = isset($_POST['op']) ? $_POST['op'] : '';
  $output = '';

  // or are we merely showing the form?
  if (user_access('access comments')) {
    if ($op == t('Preview comment')) {
      if (user_access('post comments')) {
        $output .= comment_form(array(
          'pid' => $pid,
          'nid' => $nid,
        ), NULL);
      }
      else {
        drupal_set_message(t('You are not authorized to post comments.'), 'error');
        drupal_goto("node/{$nid}");
      }
    }
    else {

      // if this is a reply to another comment, show that comment first
      // else, we'll just show the user the node they're commenting on.
      if ($pid) {
        if ($comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, 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 ($comment->nid != $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/{$nid}");
          }
          $comment = drupal_unpack($comment);
          $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
          $output .= theme('comment_view', $comment);
        }
        else {
          drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
          drupal_goto("node/{$nid}");
        }
      }
      else {
        if (user_access('access content')) {
          $output .= node_view($node);
        }
      }

      // should we show the reply box?
      if (node_comment_mode($nid) != COMMENT_NODE_READ_WRITE) {
        drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error');
        drupal_goto("node/{$nid}");
      }
      else {
        if (user_access('post comments')) {
          $output .= comment_form(array(
            'pid' => $pid,
            'nid' => $nid,
          ), t('Reply'));
        }
        else {
          drupal_set_message(t('You are not authorized to post comments.'), 'error');
          drupal_goto("node/{$nid}");
        }
      }
    }
  }
  else {
    drupal_set_message(t('You are not authorized to view comments.'), 'error');
    drupal_goto("node/{$nid}");
  }
  return $output;
}

/**
 * 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) {
  global $user;
  if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
    if (!form_get_errors()) {
      if ($edit['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", $edit['status'], $edit['timestamp'], $edit['subject'], $edit['comment'], $edit['format'], $edit['uid'], $edit['name'], $edit['mail'], $edit['homepage'], $edit['cid']);
        _comment_update_node_statistics($edit['nid']);

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

        // Add an entry to the watchdog log.
        watchdog('content', t('Comment: updated %subject.', array(
          '%subject' => theme('placeholder', $edit['subject']),
        )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'], NULL, NULL, 'comment-' . $edit['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'", $edit['pid'], $edit['nid'], $edit['subject'], $edit['comment']), 0);
        if ($duplicate != 0) {
          watchdog('content', t('Comment: duplicate %subject.', array(
            '%subject' => theme('placeholder', $edit['subject']),
          )), WATCHDOG_WARNING);
        }

        // Add the comment to database.
        $edit['status'] = user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
        $roles = variable_get('comment_roles', array());
        $score = 0;
        foreach (array_intersect(array_keys($roles), array_keys($user->roles)) as $rid) {
          $score = max($roles[$rid], $score);
        }
        $users = serialize(array(
          0 => $score,
        ));

        // Here we are building the thread field.  See the comment
        // in 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']));

          // 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) . '/';
        }
        else {

          // 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($edit['pid']);

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

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

            // First child of this parent.
            $thread = $parent->thread . '.' . int2vancode(0) . '/';
          }
          else {

            // 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) . '/';
          }
        }
        $edit['cid'] = db_next_id('{comments}_cid');
        $edit['timestamp'] = time();
        if ($edit['uid'] == $user->uid) {
          $edit['name'] = $user->name;
        }
        db_query("INSERT INTO {comments} (cid, nid, pid, uid, subject, comment, format, hostname, timestamp, status, score, users, thread, name, mail, homepage) VALUES (%d, %d, %d, %d, '%s', '%s', %d, '%s', %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", $edit['cid'], $edit['nid'], $edit['pid'], $edit['uid'], $edit['subject'], $edit['comment'], $edit['format'], $_SERVER['REMOTE_ADDR'], $edit['timestamp'], $edit['status'], $score, $users, $thread, $edit['name'], $edit['mail'], $edit['homepage']);
        _comment_update_node_statistics($edit['nid']);

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

        // Add an entry to the watchdog log.
        watchdog('content', t('Comment: added %subject.', array(
          '%subject' => theme('placeholder', $edit['subject']),
        )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'], NULL, NULL, 'comment-' . $edit['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 ($edit['status'] == COMMENT_NOT_PUBLISHED) {
        drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
      }
      return $edit['cid'];
    }
    else {
      return FALSE;
    }
  }
  else {
    $txt = t('Comment: unauthorized comment submitted or comment submitted to a closed node %subject.', array(
      '%subject' => theme('placeholder', $edit['subject']),
    ));
    watchdog('content', $txt, WATCHDOG_WARNING);
    drupal_set_message($txt, 'error');
    return FALSE;
  }
}
function comment_links($comment, $return = 1) {
  global $user;
  $links = array();

  // If we are viewing just this comment, we link back to the node.
  if ($return) {
    $links[] = l(t('parent'), comment_node_url(), NULL, NULL, "comment-{$comment->cid}");
  }
  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
    if (user_access('administer comments') && user_access('post comments')) {
      $links[] = l(t('delete'), "comment/delete/{$comment->cid}");
      $links[] = l(t('edit'), "comment/edit/{$comment->cid}");
      $links[] = l(t('reply'), "comment/reply/{$comment->nid}/{$comment->cid}");
    }
    else {
      if (user_access('post comments')) {
        if (comment_access('edit', $comment)) {
          $links[] = l(t('edit'), "comment/edit/{$comment->cid}");
        }
        $links[] = l(t('reply'), "comment/reply/{$comment->nid}/{$comment->cid}");
      }
      else {
        $links[] = theme('comment_post_forbidden', $comment->nid);
      }
    }
  }
  return $links;
}
function comment_render($node, $cid = 0) {
  global $user;
  $output = '';
  if (user_access('access comments')) {

    // Pre-process variables.
    $nid = $node->nid;
    if (empty($nid)) {
      $nid = 0;
    }
    $mode = _comment_get_display_setting('mode');
    $order = _comment_get_display_setting('sort');
    $comments_per_page = _comment_get_display_setting('comments_per_page');
    $output .= "<a id=\"comment\"></a>\n";
    if ($cid) {

      // 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.picture, u.data, c.score, c.users, 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;
      }
      $result = db_query($query, $query_args);
      if ($comment = db_fetch_object($result)) {
        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
        $output .= theme('comment_view', $comment, module_invoke_all('link', 'comment', $comment, 1));
      }
    }
    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.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;
      }

      /*
       ** We want to use the standard pager, but threads would need every
       ** comment to build the thread structure, so we need to store some
       ** extra info.
       **
       ** We use a "thread" field to store this extra info. The basic idea
       ** is to store a value and to order by that value. The "thread" 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.  Try
       ** it, it works ;).  However this would spoil the "ORDER BY thread ASC"
       ** Here, we do not need to consider the trailing "/" so we use a
       ** substring only.
       */
      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 .= comment_controls($mode, $order, $comments_per_page);
      }
      while ($comment = db_fetch_object($result)) {
        $comment = drupal_unpack($comment);
        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
        $comment->depth = count(explode('.', $comment->thread)) - 1;
        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);
              }
            }
          }
        }
      }
      $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 .= comment_controls($mode, $order, $comments_per_page);
      }
    }

    // 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) {
      $output .= comment_form(array(
        'nid' => $nid,
      ), t('Post new comment'));
    }
  }
  return $output;
}

/**
 * Menu callback; delete a comment.
 */
function comment_delete($cid) {
  $comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid));
  $output = '';
  if (is_object($comment) && is_numeric($comment->cid)) {
    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
    $form = array();
    $form['comment'] = array(
      '#type' => 'value',
      '#value' => $comment,
    );
    $output = confirm_form('comment_confirm_delete', $form, t('Are you sure you want to delete the comment %title?', array(
      '%title' => theme('placeholder', $comment->subject),
    )), 'node/' . $comment->nid, t('Any replies to this comment will be lost. This action cannot be undone.'), t('Delete'), t('Cancel'));
  }
  else {
    drupal_set_message(t('The comment no longer exists.'));
  }
  return $output;
}
function comment_confirm_delete_submit($form_id, $form_values) {
  $comment = $form_values['comment'];

  // Delete comment and its replies.
  _comment_delete_thread($comment);
  _comment_update_node_statistics($comment->nid);

  // Clear the cache so an anonymous user sees that his comment was deleted.
  cache_clear_all();
  drupal_set_message(t('The comment and all its replies have been deleted.'));
  return "node/{$comment->nid}";
}

/**
 * Comment operations.  We offer different update operations depending on
 * which comment administration page we're on.
 */
function comment_operations($action = NULL) {
  if ($action == 'publish') {
    $operations = array(
      'publish' => array(
        t('Publish the selected comments'),
        'UPDATE {comments} SET status = ' . COMMENT_PUBLISHED . ' WHERE cid = %d',
      ),
      'delete' => array(
        t('Delete the selected comments'),
        '',
      ),
    );
  }
  else {
    if ($action == 'unpublish') {
      $operations = array(
        'unpublish' => array(
          t('Unpublish the selected comments'),
          'UPDATE {comments} SET status = ' . COMMENT_NOT_PUBLISHED . ' WHERE cid = %d',
        ),
        'delete' => array(
          t('Delete the selected comments'),
          '',
        ),
      );
    }
    else {
      $operations = array(
        'publish' => array(
          t('Publish the selected comments'),
          'UPDATE {comments} SET status = ' . COMMENT_PUBLISHED . ' WHERE cid = %d',
        ),
        'unpublish' => array(
          t('Unpublish the selected comments'),
          'UPDATE {comments} SET status = ' . COMMENT_NOT_PUBLISHED . ' WHERE cid = %d',
        ),
        'delete' => array(
          t('Delete the selected comments'),
          '',
        ),
      );
    }
  }
  return $operations;
}

/**
 * Menu callback; present an administrative comment listing.
 */
function comment_admin_overview($type = 'new') {
  $edit = $_POST['edit'];
  if ($edit['operation'] == 'delete') {
    return comment_multiple_delete_confirm();
  }

  // build an 'Update options' form
  $form['options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Update options'),
    '#prefix' => '<div class="container-inline">',
    '#suffix' => '</div>',
  );
  $options = array();
  foreach (comment_operations(arg(3) == '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(
      NULL,
      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),
      ), NULL, '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(), $destination),
    );
  }
  $form['comments'] = array(
    '#type' => 'checkboxes',
    '#options' => $comments,
  );
  $form['pager'] = array(
    '#value' => theme('pager', NULL, 50, 0),
  );
  return drupal_get_form('comment_admin_overview', $form);
}

/**
 * We can't execute any 'Update options' if no comments were selected.
 */
function comment_admin_overview_validate($form_id, $edit) {
  $edit['comments'] = array_diff($edit['comments'], array(
    0,
  ));
  if (count($edit['comments']) == 0) {
    form_set_error('', t('Please select one or more comments to perform the update on.'));
    drupal_goto('admin/comment');
  }
}

/**
 * Execute the chosen 'Update option' on the selected comments, such as
 * publishing, unpublishing or deleting.
 */
function comment_admin_overview_submit($form_id, $edit) {
  $operations = comment_operations();
  if ($operations[$edit['operation']][1]) {

    // extract the appropriate database query operation
    $query = $operations[$edit['operation']][1];
    foreach ($edit['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, $edit['operation']);

        // Add an entry to the watchdog log.
        watchdog('content', t('Comment: updated %subject.', array(
          '%subject' => theme('placeholder', $comment->subject),
        )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $comment->nid, NULL, NULL, 'comment-' . $comment->cid));
      }
    }
    cache_clear_all();
    drupal_set_message(t('The update has been performed.'));
    drupal_goto('admin/comment');
  }
}
function theme_comment_admin_overview($form) {
  $output = form_render($form['options']);
  if (isset($form['subject']) && is_array($form['subject'])) {
    foreach (element_children($form['subject']) as $key) {
      $row = array();
      $row[] = form_render($form['comments'][$key]);
      $row[] = form_render($form['subject'][$key]);
      $row[] = form_render($form['username'][$key]);
      $row[] = form_render($form['timestamp'][$key]);
      $row[] = form_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 .= form_render($form['pager']);
  }
  $output .= form_render($form);
  return $output;
}

/**
 * List the selected comments and verify that the admin really wants to delete
 * them.
 */
function comment_multiple_delete_confirm() {
  $edit = $_POST['edit'];
  $form['comments'] = array(
    '#prefix' => '<ul>',
    '#suffix' => '</ul>',
    '#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' => '<li>',
        '#suffix' => check_plain($subject) . '</li>',
      );
      $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/comment');
  }
  else {
    return confirm_form('comment_multiple_delete_confirm', $form, t('Are you sure you want to delete these comments and all their children?'), 'admin/comment', t('This action cannot be undone.'), t('Delete comments'), t('Cancel'));
  }
}

/**
 * Perform the actual comment deletion.
 */
function comment_multiple_delete_confirm_submit($form_id, $edit) {
  if ($edit['confirm']) {
    foreach ($edit['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/comment');
}

/**
*** misc functions: helpers, privates, history
**/

/**
 * Load the entire comment by cid.
 */
function _comment_load($cid) {
  return db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $cid));
}
function comment_num_all($nid) {
  static $cache;
  if (!isset($cache[$nid])) {
    $cache[$nid] = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $nid));
  }
  return $cache[$nid];
}
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];
}

/**
 * get number of new comments for current user and specified node
 *
 * @param $nid node-id to count comments for
 * @param $timestamp time to count from (defaults to time of last user access
 *   to node)
 */
function comment_num_new($nid, $timestamp = 0) {
  global $user;
  if ($user->uid) {

    // Retrieve the timestamp at which the current user last viewed the
    // specified node.
    if (!$timestamp) {
      $timestamp = node_last_viewed($nid);
    }
    $timestamp = $timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT;

    // Use the timestamp to retrieve the number of new comments.
    $result = db_result(db_query('SELECT COUNT(c.cid) FROM {node} n INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = %d AND timestamp > %d AND c.status = %d', $nid, $timestamp, COMMENT_PUBLISHED));
    return $result;
  }
  else {
    return 0;
  }
}
function comment_validate($edit) {
  global $user;

  // Invoke other validation handlers
  comment_invoke_comment($edit, 'validate');
  if (isset($edit['date'])) {

    // As of PHP 5.1.0, strtotime returns FALSE upon failure instead of -1.
    if (strtotime($edit['date']) <= 0) {
      form_set_error('date', t('You have to specify a valid date.'));
    }
  }
  if (isset($edit['author']) && !($account = user_load(array(
    'name' => $edit['author'],
  )))) {
    form_set_error('author', t('You have to specify a valid author.'));
  }

  // Check validity of name, mail and homepage (if given)
  if (!$user->uid || isset($edit['is_anonymous'])) {
    if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) > COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
      if ($edit['name']) {
        $taken = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE LOWER(name) = '%s'", $edit['name']), 0);
        if ($taken != 0) {
          form_set_error('name', t('The name you used belongs to a registered user.'));
        }
      }
      else {
        if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
          form_set_error('name', t('You have to leave your name.'));
        }
      }
      if ($edit['mail']) {
        if (!valid_email_address($edit['mail'])) {
          form_set_error('mail', t('The e-mail address you specified is not valid.'));
        }
      }
      else {
        if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
          form_set_error('mail', t('You have to leave an e-mail address.'));
        }
      }
      if ($edit['homepage']) {
        if (!valid_url($edit['homepage'], TRUE)) {
          form_set_error('homepage', t('The URL of your homepage is not valid.  Remember that it must be fully qualified, i.e. of the form <code>http://example.com/directory</code>.'));
        }
      }
    }
  }
  return $edit;
}

/*
** Generate the basic commenting form, for appending to a node or display on a separate page.
** This is rendered by theme_comment_form.
*/
function comment_form($edit, $title = NULL) {
  global $user;
  $op = isset($_POST['op']) ? $_POST['op'] : '';
  if ($user->uid) {
    if ($edit['cid'] && user_access('administer comments')) {
      if ($edit['author']) {
        $author = $edit['author'];
      }
      elseif ($edit['name']) {
        $author = $edit['name'];
      }
      else {
        $author = $edit['registered_name'];
      }
      if ($edit['status']) {
        $status = $edit['status'];
      }
      else {
        $status = 0;
      }
      if ($edit['date']) {
        $date = $edit['date'];
      }
      else {
        $date = format_date($edit['timestamp'], 'custom', 'Y-m-d H:i O');
      }
      $form['admin'] = array(
        '#type' => 'fieldset',
        '#title' => t('Administration'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => -2,
      );
      if ($edit['registered_name'] != '') {

        // The comment is by a registered user
        $form['admin']['author'] = array(
          '#type' => 'textfield',
          '#title' => t('Authored by'),
          '#size' => 30,
          '#maxlength' => 60,
          '#autocomplete_path' => 'user/autocomplete',
          '#default_value' => $author,
          '#weight' => -1,
        );
      }
      else {

        // The comment is by an anonymous user
        $form['is_anonymous'] = array(
          '#type' => 'value',
          '#value' => TRUE,
        );
        $form['admin']['name'] = array(
          '#type' => 'textfield',
          '#title' => t('Authored by'),
          '#size' => 30,
          '#maxlength' => 60,
          '#default_value' => $author,
          '#weight' => -1,
        );
        $form['admin']['mail'] = array(
          '#type' => 'textfield',
          '#title' => t('E-mail'),
          '#maxlength' => 64,
          '#size' => 30,
          '#default_value' => $edit['mail'],
          '#description' => t('The content of this field is kept private and will not be shown publicly.'),
        );
        $form['admin']['homepage'] = array(
          '#type' => 'textfield',
          '#title' => t('Homepage'),
          '#maxlength' => 255,
          '#size' => 30,
          '#default_value' => $edit['homepage'],
        );
      }
      $form['admin']['date'] = array(
        '#type' => 'textfield',
        '#parents' => array(
          'date',
        ),
        '#title' => t('Authored on'),
        '#size' => 20,
        '#maxlength' => 25,
        '#default_value' => $date,
        '#weight' => -1,
      );
      $form['admin']['status'] = array(
        '#type' => 'radios',
        '#parents' => array(
          'status',
        ),
        '#title' => t('Status'),
        '#default_value' => $status,
        '#options' => array(
          t('Published'),
          t('Not published'),
        ),
        '#weight' => -1,
      );
    }
    else {
      $form['_author'] = array(
        '#type' => 'item',
        '#title' => t('Your name'),
        '#value' => theme('username', $user),
      );
      $form['author'] = array(
        '#type' => 'value',
        '#value' => $user->name,
      );
    }
  }
  else {
    if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MAY_CONTACT) {
      $form['name'] = array(
        '#type' => 'textfield',
        '#title' => t('Your name'),
        '#maxlength' => 60,
        '#size' => 30,
        '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', 'Anonymous'),
      );
      $form['mail'] = array(
        '#type' => 'textfield',
        '#title' => t('E-mail'),
        '#maxlength' => 64,
        '#size' => 30,
        '#default_value' => $edit['mail'],
        '#description' => t('The content of this field is kept private and will not be shown publicly.'),
      );
      $form['homepage'] = array(
        '#type' => 'textfield',
        '#title' => t('Homepage'),
        '#maxlength' => 255,
        '#size' => 30,
        '#default_value' => $edit['homepage'],
      );
    }
    else {
      if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
        $form['name'] = array(
          '#type' => 'textfield',
          '#title' => t('Your name'),
          '#maxlength' => 60,
          '#size' => 30,
          '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', 'Anonymous'),
          '#required' => TRUE,
        );
        $form['mail'] = array(
          '#type' => 'textfield',
          '#title' => t('E-mail'),
          '#maxlength' => 64,
          '#size' => 30,
          '#default_value' => $edit['mail'],
          '#description' => t('The content of this field is kept private and will not be shown publicly.'),
          '#required' => TRUE,
        );
        $form['homepage'] = array(
          '#type' => 'textfield',
          '#title' => t('Homepage'),
          '#maxlength' => 255,
          '#size' => 30,
          '#default_value' => $edit['homepage'],
        );
      }
    }
  }
  if (variable_get('comment_subject_field', 1) == 1) {
    $form['subject'] = array(
      '#type' => 'textfield',
      '#title' => t('Subject'),
      '#maxlength' => 64,
      '#default_value' => $edit['subject'],
    );
  }
  $form['comment_filter']['comment'] = array(
    '#type' => 'textarea',
    '#title' => t('Comment'),
    '#rows' => 15,
    '#default_value' => $edit['comment'] ? $edit['comment'] : $user->signature,
    '#required' => TRUE,
  );
  $form['comment_filter']['format'] = filter_form($edit['format']);
  $form['cid'] = array(
    '#type' => 'value',
    '#value' => $edit['cid'],
  );
  $form['pid'] = array(
    '#type' => 'value',
    '#value' => $edit['pid'],
  );
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $edit['nid'],
  );
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $edit['uid'],
  );
  $form['preview'] = array(
    '#type' => 'button',
    '#value' => t('Preview comment'),
    '#weight' => 19,
  );
  $form['#token'] = 'comment' . $edit['nid'] . $edit['pid'];

  // Only show post button if preview is optional or if we are in preview mode.
  // We show the post button in preview mode even if there are form errors so that
  // optional form elements (e.g., captcha) can be updated in preview mode.
  if (!form_get_errors() && (variable_get('comment_preview', COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL || $op == t('Preview comment') || $op == t('Post comment'))) {
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Post comment'),
      '#weight' => 20,
    );
  }
  if ($op == t('Preview comment')) {
    $form['#after_build'] = array(
      'comment_form_add_preview',
    );
  }
  if ($_REQUEST['destination']) {
    $form['#attributes']['destination'] = $_REQUEST['destination'];
  }
  if (empty($edit['cid']) && empty($edit['pid'])) {
    $form['#action'] = url('comment/reply/' . $edit['nid']);
  }

  // Graft in extra form additions
  $form = array_merge($form, comment_invoke_comment($form, 'form'));
  return theme('box', $title, drupal_get_form('comment_form', $form));
}
function comment_form_add_preview($form, $edit) {
  global $user;
  drupal_set_title(t('Preview comment'));
  $output = '';

  // Invoke full validation for the form, to protect against cross site
  // request forgeries (CSRF) and setting arbitrary values for fields such as
  // the input format. Preview the comment only when form validation does not
  // set any errors.
  drupal_validate_form($form['form_id']['#value'], $form);
  if (!form_get_errors()) {
    $comment = (object) _comment_form_submit($edit);

    // Attach the user and time information.
    if ($edit['author']) {
      $account = user_load(array(
        'name' => $edit['author'],
      ));
    }
    elseif ($user->uid && !isset($edit['is_anonymous'])) {
      $account = $user;
    }
    if ($account) {
      $comment->uid = $account->uid;
      $comment->name = check_plain($account->name);
    }
    $comment->timestamp = $edit['timestamp'] ? $edit['timestamp'] : time();
    $output .= theme('comment_view', $comment);
  }
  $form['comment_preview'] = array(
    '#value' => $output,
    '#weight' => -100,
    '#prefix' => '<div class="preview">',
    '#suffix' => '</div>',
  );
  $output = '';
  if ($edit['pid']) {
    $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d', $edit['pid'], COMMENT_PUBLISHED));
    $comment = drupal_unpack($comment);
    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
    $output .= theme('comment_view', $comment);
  }
  else {
    $form['#suffix'] = node_view(node_load($edit['nid']));
    $edit['pid'] = 0;
  }
  $form['comment_preview_below'] = array(
    '#value' => $output,
    '#weight' => 100,
  );
  return $form;
}
function comment_form_validate($form_id, $form_values) {
  comment_validate($form_values);
}
function _comment_form_submit($form_values) {
  if (!isset($form_values['date'])) {
    $form_values['date'] = 'now';
  }
  $form_values['timestamp'] = strtotime($form_values['date']);
  if (isset($form_values['author'])) {
    $account = user_load(array(
      'name' => $form_values['author'],
    ));
    $form_values['uid'] = $account->uid;
    $form_values['name'] = $form_values['author'];
  }

  // Validate the comment's subject.  If not specified, extract
  // one from the comment's body.
  if (trim($form_values['subject']) == '') {

    // The body may be in any format, so we:
    // 1) Filter it into HTML
    // 2) Strip out all HTML tags
    // 3) Convert entities back to plain-text.
    // Note: format is checked by check_markup().
    $form_values['subject'] = trim(truncate_utf8(decode_entities(strip_tags(check_markup($form_values['comment'], $form_values['format']))), 29, TRUE));

    // Edge cases where the comment body is populated only by HTML tags will
    // require a default subject.
    if ($form_values['subject'] == '') {
      $form_values['subject'] = t('(No subject)');
    }
  }
  return $form_values;
}
function comment_form_submit($form_id, $form_values) {
  $form_values = _comment_form_submit($form_values);
  if ($cid = comment_save($form_values)) {
    return array(
      'node/' . $form_values['nid'],
      NULL,
      "comment-{$cid}",
    );
  }
}

/*
** Renderer or visualization functions this can be optionally
** overridden by themes.
*/
function theme_comment_preview($comment, $links = array(), $visible = 1) {
  $output = '<div class="preview">';
  $output .= theme('comment_view', $comment, $links, $visible);
  $output .= '</div>';
  return $output;
}
function theme_comment_view($comment, $links = array(), $visible = 1) {
  static $first_new = TRUE;
  $output = '';
  $comment->new = node_mark($comment->nid, $comment->timestamp);
  if ($first_new && $comment->new != MARK_READ) {

    // Assign the anchor only for the first new comment. This avoids duplicate
    // id attributes on a page.
    $first_new = FALSE;
    $output .= "<a id=\"new\"></a>\n";
  }
  $output .= "<a id=\"comment-{$comment->cid}\"></a>\n";

  // Switch to folded/unfolded view of the comment
  if ($visible) {
    $comment->comment = check_markup($comment->comment, $comment->format, FALSE);

    // Comment API hook
    comment_invoke_comment($comment, 'view');
    $output .= theme('comment', $comment, $links);
  }
  else {
    $output .= theme('comment_folded', $comment);
  }
  return $output;
}
function comment_controls($mode = COMMENT_MODE_THREADED_EXPANDED, $order = COMMENT_ORDER_NEWEST_FIRST, $comments_per_page = 50) {
  $form['mode'] = array(
    '#type' => 'select',
    '#default_value' => $mode,
    '#options' => _comment_get_modes(),
    '#weight' => 1,
  );
  $form['order'] = array(
    '#type' => 'select',
    '#default_value' => $order,
    '#options' => _comment_get_orders(),
    '#weight' => 2,
  );
  foreach (_comment_per_page() as $i) {
    $options[$i] = t('%a comments per page', array(
      '%a' => $i,
    ));
  }
  $form['comments_per_page'] = array(
    '#type' => 'select',
    '#default_value' => $comments_per_page,
    '#options' => $options,
    '#weight' => 3,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save settings'),
    '#weight' => 20,
  );
  return drupal_get_form('comment_controls', $form);
}
function theme_comment_controls($form) {
  $output .= '<div class="container-inline">';
  $output .= form_render($form);
  $output .= '</div>';
  $output .= '<div class="description">' . t('Select your preferred way to display the comments and click "Save settings" to activate your changes.') . '</div>';
  return theme('box', t('Comment viewing options'), $output);
}
function comment_controls_submit($form_id, $form_values) {
  global $user;
  $mode = $form_values['mode'];
  $order = $form_values['order'];
  $comments_per_page = $form_values['comments_per_page'];
  if ($user->uid) {
    $user = user_save($user, array(
      'mode' => $mode,
      'sort' => $order,
      'comments_per_page' => $comments_per_page,
    ));
  }
  else {
    $_SESSION['comment_mode'] = $mode;
    $_SESSION['comment_sort'] = $order;
    $_SESSION['comment_comments_per_page'] = $comments_per_page;
  }
}
function theme_comment($comment, $links = array()) {
  $output = '<div class="comment' . ($comment->status == COMMENT_NOT_PUBLISHED ? ' comment-unpublished' : '') . '">';
  $output .= '<div class="subject">' . l($comment->subject, $_GET['q'], NULL, NULL, "comment-{$comment->cid}") . ' ' . theme('mark', $comment->new) . "</div>\n";
  $output .= '<div class="credit">' . t('by %a on %b', array(
    '%a' => theme('username', $comment),
    '%b' => format_date($comment->timestamp),
  )) . "</div>\n";
  $output .= '<div class="body">' . $comment->comment . '</div>';
  $output .= '<div class="links">' . theme('links', $links) . '</div>';
  $output .= '</div>';
  return $output;
}
function theme_comment_folded($comment) {
  $output = "<div class=\"comment-folded\">\n";
  $output .= ' <span class="subject">' . l($comment->subject, comment_node_url() . '/' . $comment->cid, NULL, NULL, "comment-{$comment->cid}") . ' ' . theme('mark', $comment->new) . '</span> ';
  $output .= '<span class="credit">' . t('by') . ' ' . theme('username', $comment) . "</span>\n";
  $output .= "</div>\n";
  return $output;
}
function theme_comment_flat_collapsed($comment) {
  return theme('comment_view', $comment, '', 0);
  return '';
}
function theme_comment_flat_expanded($comment) {
  return theme('comment_view', $comment, module_invoke_all('link', 'comment', $comment, 0));
}
function theme_comment_thread_collapsed($comment) {
  $output = '<div style="margin-left:' . $comment->depth * 25 . "px;\">\n";
  $output .= theme('comment_view', $comment, '', 0);
  $output .= "</div>\n";
  return $output;
}
function theme_comment_thread_expanded($comment) {
  $output = '';
  if ($comment->depth) {
    $output .= '<div style="margin-left:' . $comment->depth * 25 . "px;\">\n";
  }
  $output .= theme('comment_view', $comment, module_invoke_all('link', 'comment', $comment, 0));
  if ($comment->depth) {
    $output .= "</div>\n";
  }
  return $output;
}
function theme_comment_post_forbidden($nid) {
  global $user;
  if ($user->uid) {
    return t("you can't post comments");
  }
  else {

    // we cannot use drupal_get_destination() because these links sometimes appear on /node and taxo listing pages
    if (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
      $destination = "destination=" . drupal_urlencode("comment/reply/{$nid}#comment_form");
    }
    else {
      $destination = "destination=" . drupal_urlencode("node/{$nid}#comment_form");
    }
    if (variable_get('user_register', 1)) {
      return t('<a href="%login">login</a> or <a href="%register">register</a> to post comments', array(
        '%login' => check_url(url('user/login', $destination)),
        '%register' => check_url(url('user/register', $destination)),
      ));
    }
    else {
      return t('<a href="%login">login</a> to post comments', array(
        '%login' => check_url(url('user/login', $destination)),
      ));
    }
  }
}
function _comment_delete_thread($comment) {
  if (!is_object($comment) || !is_numeric($comment->cid)) {
    watchdog('content', t('Can not delete non-existent comment.'), WATCHDOG_WARNING);
    return;
  }

  // Delete the comment:
  db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid);
  watchdog('content', t('Comment: deleted %subject.', array(
    '%subject' => theme('placeholder', $comment->subject),
  )));
  comment_invoke_comment($comment, 'delete');

  // Delete the comment's replies
  $result = db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE pid = %d', $comment->cid);
  while ($comment = db_fetch_object($result)) {
    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
    _comment_delete_thread($comment);
  }
}

/**
 * Return an array of viewing modes for comment listings.
 *
 * We can't use a global variable array because the locale system
 * is not initialized yet when the comment module is loaded.
 */
function _comment_get_modes() {
  return array(
    COMMENT_MODE_FLAT_COLLAPSED => t('Flat list - collapsed'),
    COMMENT_MODE_FLAT_EXPANDED => t('Flat list - expanded'),
    COMMENT_MODE_THREADED_COLLAPSED => t('Threaded list - collapsed'),
    COMMENT_MODE_THREADED_EXPANDED => t('Threaded list - expanded'),
  );
}

/**
 * Return an array of viewing orders for comment listings.
 *
 * We can't use a global variable array because the locale system
 * is not initialized yet when the comment module is loaded.
 */
function _comment_get_orders() {
  return array(
    COMMENT_ORDER_NEWEST_FIRST => t('Date - newest first'),
    COMMENT_ORDER_OLDEST_FIRST => t('Date - oldest first'),
  );
}

/**
 * Return an array of "comments per page" settings from which the user
 * can choose.
 */
function _comment_per_page() {
  return drupal_map_assoc(array(
    10,
    30,
    50,
    70,
    90,
    150,
    200,
    250,
    300,
  ));
}

/**
 * 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 ($_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;
}

/**
 * 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);
  }
  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;
}

/**
 * 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);
}

Functions

Namesort descending Description
comment_access This is *not* a hook_access() implementation. This function is called to determine whether the current user has access to a particular comment.
comment_admin_overview Menu callback; present an administrative comment listing.
comment_admin_overview_submit Execute the chosen 'Update option' on the selected comments, such as publishing, unpublishing or deleting.
comment_admin_overview_validate We can't execute any 'Update options' if no comments were selected.
comment_block Implementation of hook_block().
comment_configure Menu callback; presents the comment settings page.
comment_confirm_delete_submit
comment_controls
comment_controls_submit
comment_delete Menu callback; delete a comment.
comment_edit
comment_form
comment_form_add_preview
comment_form_alter
comment_form_submit
comment_form_validate
comment_help Implementation of hook_help().
comment_invoke_comment Invoke a hook_comment() operation in all modules.
comment_link Implementation of hook_link().
comment_links
comment_menu Implementation of hook_menu().
comment_multiple_delete_confirm List the selected comments and verify that the admin really wants to delete them.
comment_multiple_delete_confirm_submit Perform the actual comment deletion.
comment_nodeapi Implementation of hook_nodeapi().
comment_node_url
comment_num_all
comment_num_new get number of new comments for current user and specified node
comment_num_replies
comment_operations Comment operations. We offer different update operations depending on which comment administration page we're on.
comment_perm Implementation of hook_perm().
comment_render
comment_reply
comment_save Accepts a submission of new or changed comment content.
comment_user Implementation of hook_user().
comment_validate
int2vancode Generate vancode.
theme_comment
theme_comment_admin_overview
theme_comment_block
theme_comment_controls
theme_comment_flat_collapsed
theme_comment_flat_expanded
theme_comment_folded
theme_comment_post_forbidden
theme_comment_preview
theme_comment_thread_collapsed
theme_comment_thread_expanded
theme_comment_view
vancode2int Decode vancode back to an integer.
_comment_delete_thread
_comment_form_submit
_comment_get_display_setting Return a current comment display setting
_comment_get_modes Return an array of viewing modes for comment listings.
_comment_get_orders Return an array of viewing orders for comment listings.
_comment_load Load the entire comment by cid.
_comment_per_page Return an array of "comments per page" settings from which the user can choose.
_comment_update_node_statistics Updates the comment statistics for a given node. This should be called any time a comment is added, deleted, or updated.

Constants

Namesort descending Description
COMMENT_ANONYMOUS_MAYNOT_CONTACT Constants to define the anonymous poster contact handling
COMMENT_ANONYMOUS_MAY_CONTACT
COMMENT_ANONYMOUS_MUST_CONTACT
COMMENT_CONTROLS_ABOVE Constants to define the position of the comment controls
COMMENT_CONTROLS_ABOVE_BELOW
COMMENT_CONTROLS_BELOW
COMMENT_CONTROLS_HIDDEN
COMMENT_FORM_BELOW
COMMENT_FORM_SEPARATE_PAGE Constants to define the comment form location
COMMENT_MODE_FLAT_COLLAPSED Constants to define the viewing modes for comment listings
COMMENT_MODE_FLAT_EXPANDED
COMMENT_MODE_THREADED_COLLAPSED
COMMENT_MODE_THREADED_EXPANDED
COMMENT_NODE_DISABLED Constants to define a node's comment state
COMMENT_NODE_READ_ONLY
COMMENT_NODE_READ_WRITE
COMMENT_NOT_PUBLISHED
COMMENT_ORDER_NEWEST_FIRST Constants to define the viewing orders for comment listings
COMMENT_ORDER_OLDEST_FIRST
COMMENT_PREVIEW_OPTIONAL Constants to define if comment preview is optional or required
COMMENT_PREVIEW_REQUIRED
COMMENT_PUBLISHED