comment.module 67.6 KB
Newer Older
1
<?php
2
// $Id$
Dries's avatar
 
Dries committed
3

Dries's avatar
Dries committed
4 5
/**
 * @file
Dries's avatar
 
Dries committed
6
 * Enables users to comment on published content.
Dries's avatar
Dries committed
7 8 9 10 11 12
 *
 * 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.
 */

13
/**
14
 * Comment is awaiting approval.
15
 */
16
define('COMMENT_NOT_PUBLISHED', 0);
17 18

/**
19
 * Comment is published.
20
 */
21
define('COMMENT_PUBLISHED', 1);
Dries's avatar
Dries committed
22 23

/**
24
 * Comments are displayed in a flat list - collapsed.
Dries's avatar
Dries committed
25
 */
26
define('COMMENT_MODE_FLAT_COLLAPSED', 1);
27 28 29 30

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

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

/**
 * Comments are displayed as a threaded list - expanded.
 */
41
define('COMMENT_MODE_THREADED_EXPANDED', 4);
Dries's avatar
Dries committed
42 43

/**
44
 * Anonymous posters cannot enter their contact information.
Dries's avatar
Dries committed
45 46
 */
define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);
47 48 49 50

/**
 * Anonymous posters may leave their contact information.
 */
Dries's avatar
Dries committed
51
define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);
52 53

/**
54
 * Anonymous posters are required to leave their contact information.
55
 */
Dries's avatar
Dries committed
56 57 58
define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);

/**
59
 * Comment form should be displayed on a separate page.
Dries's avatar
Dries committed
60 61
 */
define('COMMENT_FORM_SEPARATE_PAGE', 0);
62 63 64 65

/**
 * Comment form should be shown below post or list of comments.
 */
Dries's avatar
Dries committed
66 67 68
define('COMMENT_FORM_BELOW', 1);

/**
69
 * Comments for this node are disabled.
Dries's avatar
Dries committed
70 71
 */
define('COMMENT_NODE_DISABLED', 0);
72 73 74 75

/**
 * Comments for this node are locked.
 */
Dries's avatar
Dries committed
76
define('COMMENT_NODE_READ_ONLY', 1);
77 78 79 80

/**
 * Comments are enabled on this node.
 */
Dries's avatar
Dries committed
81
define('COMMENT_NODE_READ_WRITE', 2);
82

83
/**
84
 * Comment preview is optional.
85 86
 */
define('COMMENT_PREVIEW_OPTIONAL', 0);
87 88 89 90

/**
 * Comment preview is required.
 */
91 92
define('COMMENT_PREVIEW_REQUIRED', 1);

93 94 95
/**
 * Implementation of hook_help().
 */
96 97
function comment_help($path, $arg) {
  switch ($path) {
Dries's avatar
 
Dries committed
98
    case 'admin/help#comment':
99
      $output  = '<p>' . t('The comment module allows visitors to comment on your posts, creating ad hoc discussion boards. Any <a href="@content-type">content type</a> may have its <em>Default comment setting</em> set to <em>Read/Write</em> to allow comments, or <em>Disabled</em>, to prevent comments. Comment display settings and other controls may also be customized for each content type.', array('@content-type' => url('admin/build/types'))) . '</p>';
100 101
      $output .= '<p>' . t('Comment permissions are assigned to user roles, and are used to determine whether anonymous users (or other roles) are allowed to comment on posts. If anonymous users are allowed to comment, their individual contact information may be retained in cookies stored on their local computer for use in later comment submissions. When a comment has no replies, it may be (optionally) edited by its author. The comment module uses the same input formats and HTML tags available when creating other forms of content.') . '</p>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@comment">Comment module</a>.', array('@comment' => 'http://drupal.org/handbook/modules/comment/')) . '</p>';
102

103
      return $output;
104

105
    case 'admin/content/comment':
106
      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>';
107

108
    case 'admin/content/comment/approval':
109
      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>';
110
  }
Dries's avatar
 
Dries committed
111 112
}

113
/**
114
 * Implementation of hook_theme().
115 116 117 118 119 120 121 122 123 124
 */
function comment_theme() {
  return array(
    'comment_block' => array(
      'arguments' => array(),
    ),
    'comment_admin_overview' => array(
      'arguments' => array('form' => NULL),
    ),
    'comment_preview' => array(
125
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
126 127
    ),
    'comment_view' => array(
128
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
129 130
    ),
    'comment' => array(
131
      'template' => 'comment',
132
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array()),
133 134
    ),
    'comment_folded' => array(
135
      'template' => 'comment-folded',
136 137 138
      'arguments' => array('comment' => NULL),
    ),
    'comment_flat_collapsed' => array(
139
      'arguments' => array('comment' => NULL, 'node' => NULL),
140 141
    ),
    'comment_flat_expanded' => array(
142
      'arguments' => array('comment' => NULL, 'node' => NULL),
143 144
    ),
    'comment_thread_collapsed' => array(
145
      'arguments' => array('comment' => NULL, 'node' => NULL),
146 147
    ),
    'comment_thread_expanded' => array(
148
      'arguments' => array('comment' => NULL, 'node' => NULL),
149 150 151 152 153
    ),
    'comment_post_forbidden' => array(
      'arguments' => array('nid' => NULL),
    ),
    'comment_wrapper' => array(
154
      'template' => 'comment-wrapper',
155
      'arguments' => array('content' => NULL, 'node' => NULL),
156
    ),
157 158 159
    'comment_submitted' => array(
      'arguments' => array('comment' => NULL),
    ),
160 161 162
  );
}

Dries's avatar
 
Dries committed
163 164 165
/**
 * Implementation of hook_menu().
 */
166 167
function comment_menu() {
  $items['admin/content/comment'] = array(
168 169
    'title' => 'Comments',
    'description' => 'List and edit site comments and the comment moderation queue.',
170 171 172
    'page callback' => 'comment_admin',
    'access arguments' => array('administer comments'),
  );
173
  // Tabs begin here.
174
  $items['admin/content/comment/new'] = array(
175
    'title' => 'Published comments',
176 177 178
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
179
  $items['admin/content/comment/approval'] = array(
180
    'title' => 'Approval queue',
181
    'page arguments' => array('approval'),
182
    'access arguments' => array('administer comments'),
183 184 185
    'type' => MENU_LOCAL_TASK,
  );
  $items['comment/delete'] = array(
186
    'title' => 'Delete comment',
187
    'page callback' => 'comment_delete',
188 189 190 191
    'access arguments' => array('administer comments'),
    'type' => MENU_CALLBACK,
  );
  $items['comment/edit'] = array(
192
    'title' => 'Edit comment',
193 194 195 196
    'page callback' => 'comment_edit',
    'access arguments' => array('post comments'),
    'type' => MENU_CALLBACK,
  );
197
  $items['comment/reply/%node'] = array(
198
    'title' => 'Reply to comment',
199
    'page callback' => 'comment_reply',
200
    'page arguments' => array(2),
201 202 203 204
    'access callback' => 'node_access',
    'access arguments' => array('view', 2),
    'type' => MENU_CALLBACK,
  );
205
  $items['comment/approve'] = array(
206
    'title' => 'Approve a comment',
207 208 209 210 211
    'page callback' => 'comment_approve',
    'page arguments' => array(2),
    'access arguments' => array('administer comments'),
    'type' => MENU_CALLBACK,
  );
Dries's avatar
 
Dries committed
212 213 214 215

  return $items;
}

216 217 218 219 220 221 222 223 224 225 226 227 228
/**
 * Implementation of hook_node_type().
 */
function comment_node_type($op, $info) {
  $settings = array(
    'comment',
    'comment_default_mode',
    'comment_default_per_page',
    'comment_anonymous',
    'comment_subject_field',
    'comment_preview',
    'comment_form_location',
  );
229

230 231 232
  switch ($op) {
    case 'delete':
      foreach ($settings as $setting) {
233
        variable_del($setting . '_' . $info->type);
234 235 236 237 238
      }
      break;
  }
}

Dries's avatar
 
Dries committed
239 240 241 242
/**
 * Implementation of hook_perm().
 */
function comment_perm() {
243 244 245 246 247 248
  return array(
    'access comments' => t('View comments attached to content.'),
    'post comments' => t('Add comments to content (approval required).'),
    'post comments without approval' => t('Add comments to content (no approval required).'),
    'administer comments' => t('Manage and approve comments, and configure comment administration settings.'),
  );
Dries's avatar
 
Dries committed
249 250 251 252 253 254 255
}

/**
 * Implementation of hook_block().
 *
 * Generates a block with the most recent comments.
 */
256
function comment_block($op = 'list', $delta = '', $edit = array()) {
257 258
  switch ($op) {
    case 'list':
259
      $blocks['recent']['info'] = t('Recent comments');
260

261 262 263 264 265 266 267 268
      return $blocks;

    case 'configure':
      $form['comment_block_count'] = array(
        '#type' => 'select',
        '#title' => t('Number of recent comments'),
        '#default_value' => variable_get('comment_block_count', 10),
        '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
269
        '#description' => t('Number of comments displayed in the <em>Recent comments</em> block.'),
270
      );
271

272 273 274 275 276 277 278 279 280 281
      return $form;

    case 'save':
      variable_set('comment_block_count', (int)$edit['comment_block_count']);
      break;

    case 'view':
      if (user_access('access comments')) {
        $block['subject'] = t('Recent comments');
        $block['content'] = theme('comment_block');
282

283 284
        return $block;
      }
Dries's avatar
 
Dries committed
285 286 287
  }
}

288
/**
289 290 291 292 293 294 295
 * Find the most recent comments that are available to the current user.
 *
 * This is done in two steps:
 *   1. Query the {node_comment_statistics} table to find n number of nodes that
 *      have the most recent comments. This table is indexed on
 *      last_comment_timestamp, thus making it a fast query.
 *   2. Load the information from the comments table based on the nids found
296 297
 *      in step 1.
 *
298
 * @param integer $number
299 300 301
 *   (optional) The maximum number of comments to find.
 * @return
 *   An array of comment objects each containing a nid,
Dries's avatar
Dries committed
302
 *   subject, cid, and timestamp, or an empty array if there are no recent
303 304 305
 *   comments visible to the current user.
 */
function comment_get_recent($number = 10) {
306 307
  // Step 1: Select a $number of nodes which have new comments,
  //         and are visible to the current user.
308
  $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);
309 310 311 312 313 314 315
  $nids = array();
  while ($row = db_fetch_object($result)) {
    $nids[] = $row->nid;
  }

  $comments = array();
  if (!empty($nids)) {
316 317
    // Step 2: From among the comments on the nodes selected in the first query,
    //         find the $number of most recent comments.
318
    $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.cid DESC', COMMENT_PUBLISHED, 0, $number);
319 320 321 322 323 324 325 326
    while ($comment = db_fetch_object($result)) {
      $comments[] = $comment;
    }
  }

  return $comments;
}

327 328
/**
 * Calculate page number for first new comment.
329 330 331 332 333 334 335 336 337
 *
 * @param $num_comments
 *   Number of comments.
 * @param $new_replies
 *   Number of new replies.
 * @param $node
 *   The first new comment node.
 * @return
 *   "page=X" if the page number is greater than zero; empty string otherwise.
338
 */
339 340 341
function comment_new_page_count($num_comments, $new_replies, $node) {
  $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
  $mode = _comment_get_display_setting('mode', $node);
342
  $pagenum = NULL;
343
  $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
344 345
  if ($num_comments <= $comments_per_page) {
    // Only one page of comments.
346
    $pageno = 0;
347
  }
348 349 350 351 352
  elseif ($flat) {
    // Flat comments.
    $count = $num_comments - $new_replies;
    $pageno =  $count / $comments_per_page;
  }
353
  else {
354 355 356 357 358 359
    // Threaded comments.
    // Find the first thread with a new comment.
    $result = db_query('(SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1)) LIMIT 1', $node->nid, $new_replies);
    $thread = substr(db_result($result), 0, -1);
    $result_count = db_query("SELECT COUNT(*) FROM {comments} WHERE nid = %d AND status = 0 AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < '" . $thread . "'", $node->nid);
    $count = db_result($result_count);
360 361
    $pageno =  $count / $comments_per_page;
  }
362

363
  if ($pageno >= 1) {
364
    $pagenum = "page=" . intval($pageno);
365
  }
366

367 368 369
  return $pagenum;
}

370
/**
371
 * Returns a formatted list of recent comments to be displayed in the comment block.
372
 *
373 374
 * @return
 *   The comment list HTML.
375 376
 * @ingroup themeable
 */
377 378
function theme_comment_block() {
  $items = array();
379 380
  $number = variable_get('comment_block_count', 10);
  foreach (comment_get_recent($number) as $comment) {
381
    $items[] = l($comment->subject, 'node/' . $comment->nid, array('fragment' => 'comment-' . $comment->cid)) . '<br />' . t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->timestamp)));
382
  }
383

384 385 386
  if ($items) {
    return theme('item_list', $items);
  }
387 388
}

Dries's avatar
 
Dries committed
389 390 391
/**
 * Implementation of hook_link().
 */
392
function comment_link($type, $node = NULL, $teaser = FALSE) {
Dries's avatar
 
Dries committed
393 394 395
  $links = array();

  if ($type == 'node' && $node->comment) {
396
    if ($teaser) {
Dries's avatar
 
Dries committed
397 398
      // Main page: display the number of comments that have been posted.
      if (user_access('access comments')) {
399
        if ($node->comment_count) {
400
          $links['comment_comments'] = array(
401
            'title' => format_plural($node->comment_count, '1 comment', '@count comments'),
402 403
            'href' => "node/$node->nid",
            'attributes' => array('title' => t('Jump to the first comment of this posting.')),
404
            'fragment' => 'comments'
405
          );
406 407

          $new = comment_num_new($node->nid);
Dries's avatar
 
Dries committed
408
          if ($new) {
409
            $links['comment_new_comments'] = array(
410
              'title' => format_plural($new, '1 new comment', '@count new comments'),
411
              'href' => "node/$node->nid",
412
              'query' => comment_new_page_count($node->comment_count, $new, $node),
413 414
              'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
              'fragment' => 'new'
415
            );
Dries's avatar
 
Dries committed
416 417 418
          }
        }
        else {
Dries's avatar
Dries committed
419
          if ($node->comment == COMMENT_NODE_READ_WRITE) {
Dries's avatar
 
Dries committed
420
            if (user_access('post comments')) {
421
              $links['comment_add'] = array(
422
                'title' => t('Add new comment'),
423 424
                'href' => "comment/reply/$node->nid",
                'attributes' => array('title' => t('Add a new comment to this page.')),
425
                'fragment' => 'comment-form'
426
              );
Dries's avatar
 
Dries committed
427 428
            }
            else {
429
              $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
Dries's avatar
 
Dries committed
430 431 432 433 434 435
            }
          }
        }
      }
    }
    else {
436 437
      // 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.
438
      if ($node->comment == COMMENT_NODE_READ_WRITE) {
Dries's avatar
 
Dries committed
439
        if (user_access('post comments')) {
440
          if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
441
            $links['comment_add'] = array(
442
              'title' => t('Add new comment'),
443 444
              'href' => "comment/reply/$node->nid",
              'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
445
              'fragment' => 'comment-form'
446
            );
447
          }
Dries's avatar
 
Dries committed
448 449
        }
        else {
450
          $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
Dries's avatar
 
Dries committed
451 452 453 454 455 456
        }
      }
    }
  }

  if ($type == 'comment') {
457
    $links = comment_links($node, $teaser);
Dries's avatar
 
Dries committed
458
  }
459

460 461 462
  if (isset($links['comment_forbidden'])) {
    $links['comment_forbidden']['html'] = TRUE;
  }
Dries's avatar
 
Dries committed
463 464 465 466

  return $links;
}

467 468 469
/**
 * Implementation of hook_form_alter().
 */
470
function comment_form_alter(&$form, $form_state, $form_id) {
471
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
472 473 474 475
    $form['comment'] = array(
      '#type' => 'fieldset',
      '#title' => t('Comment settings'),
      '#collapsible' => TRUE,
476
      '#collapsed' => TRUE,
477 478
    );
    $form['comment']['comment'] = array(
479 480
      '#type' => 'radios',
      '#title' => t('Default comment setting'),
481
      '#default_value' => variable_get('comment_' . $form['#node_type']->type, COMMENT_NODE_READ_WRITE),
482 483 484
      '#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.'),
    );
485 486 487
    $form['comment']['comment_default_mode'] = array(
      '#type' => 'radios',
      '#title' => t('Default display mode'),
488
      '#default_value' => variable_get('comment_default_mode_' . $form['#node_type']->type, COMMENT_MODE_THREADED_EXPANDED),
489
      '#options' => _comment_get_modes(),
490
      '#description' => t('Expanded views display the body of the comment. Threaded views keep replies together.'),
491 492 493
    );
    $form['comment']['comment_default_per_page'] = array(
      '#type' => 'select',
494
      '#title' => t('Comments per page'),
495
      '#default_value' => variable_get('comment_default_per_page_' . $form['#node_type']->type, 50),
496
      '#options' => _comment_per_page(),
497
      '#description' => t('Additional comments will be displayed on separate pages.'),
498 499 500 501
    );
    $form['comment']['comment_anonymous'] = array(
      '#type' => 'radios',
      '#title' => t('Anonymous commenting'),
502
      '#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT),
503 504 505 506
      '#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')),
507
      '#description' => t('This option is enabled when anonymous users have permission to post comments on the <a href="@url">permissions page</a>.', array('@url' => url('admin/user/permissions', array('fragment' => 'module-comment')))),
508
    );
509

510
    if (!user_access('post comments', drupal_anonymous_user())) {
511 512
      $form['comment']['comment_anonymous']['#disabled'] = TRUE;
    }
513

514 515 516
    $form['comment']['comment_subject_field'] = array(
      '#type' => 'radios',
      '#title' => t('Comment subject field'),
517
      '#default_value' => variable_get('comment_subject_field_' . $form['#node_type']->type, 1),
518 519 520 521 522 523
      '#options' => array(t('Disabled'), t('Enabled')),
      '#description' => t('Can users provide a unique subject for their comments?'),
    );
    $form['comment']['comment_preview'] = array(
      '#type' => 'radios',
      '#title' => t('Preview comment'),
524
      '#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, COMMENT_PREVIEW_REQUIRED),
525 526 527 528 529 530
      '#options' => array(t('Optional'), t('Required')),
      '#description' => t("Forces a user to look at their comment by clicking on a 'Preview' button before they can actually add the comment"),
    );
    $form['comment']['comment_form_location'] = array(
      '#type' => 'radios',
      '#title' => t('Location of comment submission form'),
531
      '#default_value' => variable_get('comment_form_location_' . $form['#node_type']->type, COMMENT_FORM_SEPARATE_PAGE),
532 533
      '#options' => array(t('Display on separate page'), t('Display below post or comments')),
    );
534
  }
535
  elseif (isset($form['type']) && isset($form['#node'])) {
536
    if ($form['type']['#value'] . '_node_form' == $form_id) {
537
      $node = $form['#node'];
538 539 540 541 542 543 544 545 546 547 548 549 550 551
      $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')),
      );
552
    }
553 554 555
  }
}

Dries's avatar
 
Dries committed
556 557 558 559 560
/**
 * Implementation of hook_nodeapi().
 */
function comment_nodeapi(&$node, $op, $arg = 0) {
  switch ($op) {
Dries's avatar
 
Dries committed
561
    case 'load':
562 563 564
      if ($node->comment != COMMENT_NODE_DISABLED) {
        return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid));
      }
565
      return array('last_comment_timestamp' => $node->created, 'last_comment_name' => '', 'comment_count' => 0);
566 567 568

    case 'prepare':
      if (!isset($node->comment)) {
Dries's avatar
Dries committed
569
        $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
Dries's avatar
 
Dries committed
570 571
      }
      break;
572

573
    case 'insert':
574
      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);
575 576 577
      break;

    case 'delete':
578 579
      db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
      db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
580 581
      break;

Dries's avatar
Dries committed
582 583
    case 'update index':
      $text = '';
584
      $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED);
Dries's avatar
Dries committed
585
      while ($comment = db_fetch_object($comments)) {
586
        $text .= '<h2>' . check_plain($comment->subject) . '</h2>' . check_markup($comment->comment, $comment->format, FALSE);
Dries's avatar
Dries committed
587 588
      }
      return $text;
589

Dries's avatar
Dries committed
590 591
    case 'search result':
      $comments = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $node->nid));
592
      return format_plural($comments, '1 comment', '@count comments');
593

Steven Wittens's avatar
- Typo  
Steven Wittens committed
594
    case 'rss item':
595
      if ($node->comment != COMMENT_NODE_DISABLED) {
596
        return array(array('key' => 'comments', 'value' => url('node/' . $node->nid, array('fragment' => 'comments', 'absolute' => TRUE))));
597 598 599 600
      }
      else {
        return array();
      }
Dries's avatar
 
Dries committed
601 602 603 604 605 606 607
  }
}

/**
 * Implementation of hook_user().
 */
function comment_user($type, $edit, &$user, $category = NULL) {
608
  if ($type == 'delete') {
609 610 611
    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's avatar
 
Dries committed
612 613
}

614 615 616 617 618 619
/**
 * 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
620
 * statements based on the replies to their posts.
621 622 623 624 625 626 627
 *
 * @param $op
 *   The operation that is to be performed on the comment. Only 'edit' is recognized now.
 * @param $comment
 *   The comment object.
 * @return
 *   TRUE if the current user has acces to the comment, FALSE otherwise.
628
 */
Dries's avatar
 
Dries committed
629
function comment_access($op, $comment) {
Dries's avatar
 
Dries committed
630 631
  global $user;

632
  if ($op == 'edit') {
633
    return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments');
Dries's avatar
 
Dries committed
634 635
  }
}
636

637 638 639 640 641 642
/**
 * A simple helper function.
 *
 * @return
 *   The 0th and the 1st path components joined by a slash.
 */
Dries's avatar
 
Dries committed
643
function comment_node_url() {
644
  return arg(0) . '/' . arg(1);
Dries's avatar
 
Dries committed
645
}
Dries's avatar
 
Dries committed
646

647 648 649 650 651 652 653
/**
 * Accepts a submission of new or changed comment content.
 *
 * @param $edit
 *   A comment array.
 *
 * @return
654
 *   If the comment is successfully saved the comment ID is returned. If the comment
655 656 657
 *   is not saved, FALSE is returned.
 */
function comment_save($edit) {
Dries's avatar
 
Dries committed
658
  global $user;
659
  if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
Dries's avatar
 
Dries committed
660
    if (!form_get_errors()) {
661 662 663 664 665 666
      $edit += array(
        'mail' => '',
        'homepage' => '',
        'name' => '',
        'status' => user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED,
      );
667
      if ($edit['cid']) {
668
        // Update the comment in the database.
669
        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']);
670
        // Allow modules to respond to the updating of a comment.
671
        comment_invoke_comment($edit, 'update');
Dries's avatar
Dries committed
672
        // Add an entry to the watchdog log.
673
        watchdog('content', 'Comment: updated %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'], array('fragment' => 'comment-' . $edit['cid'])));
Dries's avatar
 
Dries committed
674 675
      }
      else {
676 677
        // Add the comment to database. This next section builds the thread field.
        // Also see the documentation for comment_render().
678
        if ($edit['pid'] == 0) {
679 680
          // This is a comment with no parent comment (depth 0): we start
          // by retrieving the maximum thread level.
681 682 683 684
          $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.
685
          $thread = int2vancode(vancode2int($max) + 1) . '/';
Dries's avatar
 
Dries committed
686 687
        }
        else {
688
          // This is a comment with a parent comment, so increase
689
          // the part of the thread value at the proper depth.
Dries's avatar
 
Dries committed
690 691

          // Get the parent comment:
692
          $parent = comment_load($edit['pid']);
693
          // Strip the "/" from the end of the parent thread.
694
          $parent->thread = (string) rtrim((string) $parent->thread, '/');
695
          // Get the max value in *this* thread.
696
          $max = db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE :thread AND nid = :nid", array(':thread' => $parent->thread .'%', ':nid' => $edit['nid']))->fetchField();
Dries's avatar
 
Dries committed
697

698 699
          if ($max == '') {
            // First child of this parent.
700
            $thread = $parent->thread . '.' . int2vancode(0) . '/';
Dries's avatar
 
Dries committed
701 702
          }
          else {
703 704
            // Strip the "/" at the end of the thread.
            $max = rtrim($max, '/');
705
            // Get the value at the correct depth.
706 707
            $parts = explode('.', $max);
            $parent_depth = count(explode('.', $parent->thread));
Dries's avatar
 
Dries committed
708
            $last = $parts[$parent_depth];
709
            // Finally, build the thread field for this new comment.
710
            $thread = $parent->thread . '.' . int2vancode(vancode2int($last) + 1) . '/';
Dries's avatar
 
Dries committed
711 712 713
          }
        }

714
        if (empty($edit['timestamp'])) {
715
          $edit['timestamp'] = REQUEST_TIME;
716 717
        }

718
        if ($edit['uid'] === $user->uid) { // '===' Need to modify anonymous users as well.
Dries's avatar
 
Dries committed
719 720
          $edit['name'] = $user->name;
        }
721

722
        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'], $edit['status'], $thread, $edit['name'], $edit['mail'], $edit['homepage']);
723
        $edit['cid'] = db_last_insert_id('comments', 'cid');
724
        // Tell the other modules a new comment has been submitted.
725
        comment_invoke_comment($edit, 'insert');
726
        // Add an entry to the watchdog log.
727
        watchdog('content', 'Comment: added %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'], array('fragment' => 'comment-' . $edit['cid'])));
Dries's avatar
 
Dries committed
728
      }
729
      _comment_update_node_statistics($edit['nid']);
730
      // Clear the cache so an anonymous user can see his comment being added.
Dries's avatar
 
Dries committed
731
      cache_clear_all();
Dries's avatar
 
Dries committed
732

Dries's avatar
 
Dries committed
733
      // Explain the approval queue if necessary, and then
Dries's avatar
 
Dries committed
734
      // redirect the user to the node he's commenting on.
735
      if ($edit['status'] == COMMENT_NOT_PUBLISHED) {
Dries's avatar
 
Dries committed
736
        drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
Dries's avatar
 
Dries committed
737
      }
738
      else {
739
        drupal_set_message(t('Your comment has been posted.'));
740
        comment_invoke_comment($edit, 'publish');
741
      }
742

743
      return $edit['cid'];
Dries's avatar
 
Dries committed
744 745
    }
    else {
746
      return FALSE;
Dries's avatar
 
Dries committed
747 748
    }
  }
Dries's avatar
 
Dries committed
749
  else {
750 751
    watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject']), WATCHDOG_WARNING);
    drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject'])), 'error');
752

753
    return FALSE;
Dries's avatar
 
Dries committed
754 755 756
  }
}

757 758 759 760 761 762 763 764 765 766
/**
 * Build command links for a comment (e.g.\ edit, reply, delete) with respect to the current user's access permissions.
 *
 * @param $comment
 *   The comment to which the links will be related.
 * @param $return
 *   Not used.
 * @return
 *   An associative array containing the links.
 */
Dries's avatar
 
Dries committed
767
function comment_links($comment, $return = 1) {
Dries's avatar
 
Dries committed
768
  global $user;
Dries's avatar
 
Dries committed
769
  $links = array();
Dries's avatar
 
Dries committed
770

771
  // If viewing just this comment, link back to the node.
Dries's avatar
 
Dries committed
772
  if ($return) {
773
    $links['comment_parent'] = array(
774 775 776
      'title' => t('parent'),
      'href' => comment_node_url(),
      'fragment' => "comment-$comment->cid"
777
    );
Dries's avatar
 
Dries committed
778
  }
Dries's avatar
 
Dries committed
779

780
  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
781
    if (user_access('administer comments') && user_access('post comments')) {
782
      $links['comment_delete'] = array(
783 784
        'title' => t('delete'),
        'href' => "comment/delete/$comment->cid"
785 786
      );
      $links['comment_edit'] = array(
787 788
        'title' => t('edit'),
        'href' => "comment/edit/$comment->cid"
789 790
      );
      $links['comment_reply'] = array(
791 792
        'title' => t('reply'),
        'href' => "comment/reply/$comment->nid/$comment->cid"
793
      );
794 795 796 797 798 799
      if ($comment->status == COMMENT_NOT_PUBLISHED) {
        $links['comment_approve'] = array(
          'title' => t('approve'),
          'href' => "comment/approve/$comment->cid"
        );
      }
800
    }
801
    elseif (user_access('post comments')) {
802
      if (comment_access('edit', $comment)) {
803
        $links['comment_edit'] = array(
804 805
          'title' => t('edit'),
          'href' => "comment/edit/$comment->cid"
806
        );
Dries's avatar
 
Dries committed
807
      }
808
      $links['comment_reply'] = array(
809 810
        'title' => t('reply'),
        'href' => "comment/reply/$comment->nid/$comment->cid"
811
      );
Dries's avatar
 
Dries committed
812 813
    }
    else {
814 815
      $node = node_load($comment->nid);
      $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
Dries's avatar