comment.module 69.3 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
/*
Dries's avatar
Dries committed
14
 * Constants to define a comment's published state
15
 */
Dries's avatar
Dries committed
16 17 18 19 20 21
define('COMMENT_PUBLISHED', 0);
define('COMMENT_NOT_PUBLISHED', 1);

/**
 * Constants to define the viewing modes for comment listings
 */
22 23 24 25
define('COMMENT_MODE_FLAT_COLLAPSED', 1);
define('COMMENT_MODE_FLAT_EXPANDED', 2);
define('COMMENT_MODE_THREADED_COLLAPSED', 3);
define('COMMENT_MODE_THREADED_EXPANDED', 4);
Dries's avatar
Dries committed
26 27 28 29

/**
 * Constants to define the viewing orders for comment listings
 */
30 31
define('COMMENT_ORDER_NEWEST_FIRST', 1);
define('COMMENT_ORDER_OLDEST_FIRST', 2);
Dries's avatar
Dries committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

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

61 62 63 64 65 66
/**
 * Constants to define if comment preview is optional or required
 */
define('COMMENT_PREVIEW_OPTIONAL', 0);
define('COMMENT_PREVIEW_REQUIRED', 1);

67 68 69
/**
 * Implementation of hook_help().
 */
70
function comment_help($section) {
Dries's avatar
 
Dries committed
71
  switch ($section) {
Dries's avatar
 
Dries committed
72
    case 'admin/help#comment':
73
      $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>';
74
      $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>';
75 76 77 78 79
      $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>
Dries's avatar
Dries committed
80
', array('%admin-access' => url('admin/access'), '%admin-settings-comment' => url('admin/settings/comment')));
81
      $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>';
82 83 84
      return $output;
    case 'admin/modules#description':
      return t('Allows users to comment on and discuss published content.');
Dries's avatar
 
Dries committed
85
    case 'admin/comment':
86
    case 'admin/comment/new':
87
      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>");
Dries's avatar
 
Dries committed
88
    case 'admin/comment/approval':
89
      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>");
Dries's avatar
Dries committed
90
    case 'admin/settings/comment':
91
      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>");
92
   }
Dries's avatar
 
Dries committed
93 94
}

Dries's avatar
 
Dries committed
95 96 97
/**
 * Implementation of hook_menu().
 */
Dries's avatar
 
Dries committed
98
function comment_menu($may_cache) {
Dries's avatar
 
Dries committed
99 100
  $items = array();

Dries's avatar
 
Dries committed
101 102 103 104 105 106 107 108 109 110
  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);

    // Subtabs:
111
    $items[] = array('path' => 'admin/comment/list/new', 'title' => t('published comments'),
Dries's avatar
 
Dries committed
112 113 114
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
    $items[] = array('path' => 'admin/comment/list/approval', 'title' => t('approval queue'),
      'callback' => 'comment_admin_overview', 'access' => $access,
115
      'callback arguments' => array('approval'),
Dries's avatar
 
Dries committed
116 117
      'type' => MENU_LOCAL_TASK);

Dries's avatar
Dries committed
118 119 120 121 122 123
    $items[] = array(
      'path' => 'admin/settings/comments',
      'title' => t('comments'),
      'callback' => 'comment_admin_settings',
      'access' => $access,
      'type' => MENU_NORMAL_ITEM);
124

125 126
    $items[] = array('path' => 'comment/delete', 'title' => t('delete comment'),
      'callback' => 'comment_delete', 'access' => $access, 'type' => MENU_CALLBACK);
Dries's avatar
 
Dries committed
127 128

    $access = user_access('post comments');
129
    $items[] = array('path' => 'comment/edit', 'title' => t('edit comment'),
Dries's avatar
 
Dries committed
130
      'callback' => 'comment_edit', 'access' => $access, 'type' => MENU_CALLBACK);
Dries's avatar
 
Dries committed
131
  }
Dries's avatar
 
Dries committed
132 133
  else {
    if (arg(0) == 'comment' && arg(1) == 'reply' && is_numeric(arg(2))) {
134
      $node = node_load(arg(2));
Dries's avatar
 
Dries committed
135 136 137 138 139 140 141 142 143 144
      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);
    }
Dries's avatar
 
Dries committed
145
  }
Dries's avatar
 
Dries committed
146 147 148 149 150 151 152 153

  return $items;
}

/**
 * Implementation of hook_perm().
 */
function comment_perm() {
154
  return array('access comments', 'post comments', 'administer comments', 'post comments without approval');
Dries's avatar
 
Dries committed
155 156 157 158 159 160 161 162 163 164 165 166
}

/**
 * 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;
  }
167
  else if ($op == 'view' && user_access('access comments')) {
Dries's avatar
 
Dries committed
168
    $block['subject'] = t('Recent comments');
169
    $block['content'] = theme('comment_block');
Dries's avatar
 
Dries committed
170 171 172 173
    return $block;
  }
}

174
function theme_comment_block() {
175
  $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);
176 177 178 179 180 181 182
  $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);
}

Dries's avatar
 
Dries committed
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
/**
 * 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);
        $new = comment_num_new($node->nid);

        if ($all) {
199
          $links['comment_comments'] = array(
200 201 202 203
            'title' => format_plural($all, '1 comment', '%count comments'),
            'href' => "node/$node->nid",
            'attributes' => array('title' => t('Jump to the first comment of this posting.')),
            'fragment' => 'comment'
204
          );
Dries's avatar
 
Dries committed
205 206

          if ($new) {
207
            $links['comment_new_comments'] = array(
208 209 210 211
              'title' => format_plural($new, '1 new comment', '%count new comments'),
              'href' => "node/$node->nid",
              'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
              'fragment' => 'new'
212
            );
Dries's avatar
 
Dries committed
213 214 215
          }
        }
        else {
Dries's avatar
Dries committed
216
          if ($node->comment == COMMENT_NODE_READ_WRITE) {
Dries's avatar
 
Dries committed
217
            if (user_access('post comments')) {
218
              $links['comment_add'] = array(
219 220 221 222
                'title' => t('add new comment'),
                'href' => "comment/reply/$node->nid",
                'attributes' => array('title' => t('Add a new comment to this page.')),
                'fragment' => 'comment_form'
223
              );
Dries's avatar
 
Dries committed
224 225
            }
            else {
226
              $links['comment_forbidden']['#title'] = theme('comment_post_forbidden', $node->nid);
Dries's avatar
 
Dries committed
227 228 229 230 231 232 233 234 235
            }
          }
        }
      }
    }
    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

236
      if ($node->comment == COMMENT_NODE_READ_WRITE) {
Dries's avatar
 
Dries committed
237
        if (user_access('post comments')) {
238
          if (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
239
            $links['comment_add'] = array(
240 241 242 243
              'title' => t('add new comment'),
              'href' => "comment/reply/$node->nid",
              'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
              'fragment' => 'comment_form'
244
            );
245
          }
Dries's avatar
 
Dries committed
246 247
        }
        else {
248
          $links['comment_forbidden']['#title'] = theme('comment_post_forbidden', $node->nid);
Dries's avatar
 
Dries committed
249 250 251 252 253 254 255 256 257 258 259 260
        }
      }
    }
  }

  if ($type == 'comment') {
    $links = comment_links($node, $main);
  }

  return $links;
}

261
function comment_form_alter($form_id, &$form) {
262 263 264 265
  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.'));
    }
266
    if ($form['type']['#value'] .'_node_form' == $form_id) {
267
      $node = $form['#node'];
268
      if (user_access('administer comments')) {
269
        $form['comment_settings'] = array(
270
          '#type' => 'fieldset',
271
          '#title' => t('Comment settings'),
272 273 274 275
          '#collapsible' => TRUE,
          '#collapsed' => TRUE,
          '#weight' => 30,
        );
276
        $form['comment_settings']['comment'] = array(
277 278 279 280 281 282 283
          '#type' => 'radios',
          '#parents' => array('comment'),
          '#default_value' => $node->comment,
          '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
        );
      }
      else {
284
        $form['comment_settings']['comment'] = array(
285 286 287 288
          '#type' => 'value',
          '#value' => $node->comment,
        );
      }
289
    }
290 291 292
  }
}

Dries's avatar
 
Dries committed
293 294
/**
 * Implementation of hook_nodeapi().
Dries's avatar
 
Dries committed
295
 *
Dries's avatar
 
Dries committed
296 297 298
 */
function comment_nodeapi(&$node, $op, $arg = 0) {
  switch ($op) {
Dries's avatar
 
Dries committed
299
    case 'load':
300
      return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid));
301 302 303 304
      break;

    case 'prepare':
      if (!isset($node->comment)) {
Dries's avatar
Dries committed
305
        $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
Dries's avatar
 
Dries committed
306 307
      }
      break;
308

Dries's avatar
 
Dries committed
309
    case 'insert':
310
      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);
Dries's avatar
 
Dries committed
311
      break;
312

Dries's avatar
 
Dries committed
313
    case 'delete':
Dries's avatar
 
Dries committed
314 315
      db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
      db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
Dries's avatar
 
Dries committed
316
      break;
317

Dries's avatar
Dries committed
318 319
    case 'update index':
      $text = '';
320
      $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED);
Dries's avatar
Dries committed
321
      while ($comment = db_fetch_object($comments)) {
322
        $text .= '<h2>'. check_plain($comment->subject) .'</h2>'. check_markup($comment->comment, $comment->format, FALSE);
Dries's avatar
Dries committed
323 324
      }
      return $text;
325

Dries's avatar
Dries committed
326 327 328
    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');
329

Steven Wittens's avatar
- Typo  
Steven Wittens committed
330
    case 'rss item':
331 332 333 334 335 336
      if ($node->comment != COMMENT_NODE_DISABLED) {
        return array(array('key' => 'comments', 'value' => url('node/'. $node->nid, NULL, 'comment', TRUE)));
      }
      else {
        return array();
      }
Dries's avatar
 
Dries committed
337 338 339 340 341 342 343 344 345 346 347
  }
}

/**
 * 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
348 349 350 351 352 353 354 355 356 357
    $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.'));
358 359

    return $form;
Dries's avatar
 
Dries committed
360
  }
361 362 363 364
  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);
  }
Dries's avatar
 
Dries committed
365 366
}

367
/**
Dries's avatar
 
Dries committed
368
 * Menu callback; presents the comment settings page.
369
 */
Dries's avatar
Dries committed
370
function comment_admin_settings() {
371 372
  $form['viewing_options'] = array(
    '#type' => 'fieldset',
373
    '#title' => t('Viewing options'),
374 375 376
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
Dries's avatar
 
Dries committed
377

Dries's avatar
Dries committed
378 379 380 381 382
  $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(),
383
    '#description' => t('The default view for comments. Expanded views display the body of the comment. Threaded views keep replies together.'),
Dries's avatar
Dries committed
384
  );
385

Dries's avatar
Dries committed
386 387 388
  $form['viewing_options']['comment_default_order'] = array(
    '#type' => 'radios',
    '#title' => t('Default display order'),
389
    '#default_value' => variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST),
Dries's avatar
Dries committed
390
    '#options' => _comment_get_orders(),
391
    '#description' => t('The default sorting for new users and anonymous users while viewing comments. These users may change their view using the comment control panel. For registered users, this change is remembered as a persistent user preference.'),
Dries's avatar
Dries committed
392
  );
Dries's avatar
 
Dries committed
393

394
  $form['viewing_options']['comment_default_per_page'] = array(
395 396 397 398
    '#type' => 'select',
    '#title' => t('Default comments per page'),
    '#default_value' => variable_get('comment_default_per_page', 50),
    '#options' => _comment_per_page(),
399
    '#description' => t('Default number of comments for each page: more comments are distributed in several pages.'),
400 401
  );

Dries's avatar
Dries committed
402 403 404 405 406 407 408 409 410
  $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')),
411
    '#description' => t('Position of the comment controls box. The comment controls let the user change the default display mode and display order of comments.'),
Dries's avatar
Dries committed
412
  );
413

414 415
  $form['posting_settings'] = array(
    '#type' => 'fieldset',
416
    '#title' => t('Posting settings'),
417 418 419
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
420

Dries's avatar
Dries committed
421 422
  $form['posting_settings']['comment_anonymous'] = array(
    '#type' => 'radios',
423
    '#title' => t('Anonymous commenting'),
Dries's avatar
Dries committed
424 425 426 427 428
    '#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')),
429
    '#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'))),
Dries's avatar
Dries committed
430
  );
431 432 433
  if (!user_access('post comments', user_load(array('uid' => 0)))) {
    $form['posting_settings']['comment_anonymous']['#attributes'] = array('disabled' => 'disabled');
  }
434 435

  $form['posting_settings']['comment_subject_field'] = array(
436 437 438 439
    '#type' => 'radios',
    '#title' => t('Comment subject field'),
    '#default_value' => variable_get('comment_subject_field', 1),
    '#options' => array(t('Disabled'), t('Enabled')),
440
    '#description' => t('Can users provide a unique subject for their comments?'),
441 442
  );

443 444 445 446 447 448
  $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')),
  );
449

Dries's avatar
Dries committed
450 451 452 453
  $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),
454
    '#options' => array(t('Display on separate page'), t('Display below post or comments')),
Dries's avatar
Dries committed
455
  );
456

Dries's avatar
Dries committed
457
  return system_settings_form('comment_admin_settings', $form);
Dries's avatar
 
Dries committed
458 459
}

460 461 462 463 464 465
/**
 * 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
466
 * statements based on the replies to their posts.
467
 */
Dries's avatar
 
Dries committed
468
function comment_access($op, $comment) {
Dries's avatar
 
Dries committed
469 470
  global $user;

471
  if ($op == 'edit') {
472
    return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments');
Dries's avatar
 
Dries committed
473 474
  }
}
475

Dries's avatar
 
Dries committed
476
function comment_node_url() {
Dries's avatar
Dries committed
477
  return arg(0) .'/'. arg(1);
Dries's avatar
 
Dries committed
478
}
Dries's avatar
 
Dries committed
479

Dries's avatar
 
Dries committed
480 481 482
function comment_edit($cid) {
  global $user;

483
  $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d', $cid));
Dries's avatar
 
Dries committed
484
  $comment = drupal_unpack($comment);
485
  $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
486
  if (comment_access('edit', $comment)) {
487
    return comment_form((array)$comment);
488 489 490
  }
  else {
    drupal_access_denied();
Dries's avatar
 
Dries committed
491 492 493
  }
}

Dries's avatar
Dries committed
494
function comment_reply($nid, $pid = NULL) {
495
  // set the breadcrumb trail
496
  $node = node_load($nid);
497
  menu_set_location(array(array('path' => "node/$nid", 'title' => $node->title), array('path' => "comment/reply/$nid")));
Dries's avatar
 
Dries committed
498

499
  $op = isset($_POST['op']) ? $_POST['op'] : '';
Dries's avatar
 
Dries committed
500

501
  $output = '';
Dries's avatar
 
Dries committed
502

Dries's avatar
Dries committed
503 504 505
  // or are we merely showing the form?
  if (user_access('access comments')) {

506 507 508 509 510 511
    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');
512
        drupal_goto("node/$nid");
513
      }
Dries's avatar
 
Dries committed
514 515
    }
    else {
516 517 518
      // 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) {
519
        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))) {
520 521 522 523 524
          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");
          }
525 526 527 528 529 530 531 532
          $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");
        }
533 534 535 536 537 538 539 540
      }
      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');
541
        drupal_goto("node/$nid");
542 543 544 545 546 547
      }
      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');
548
        drupal_goto("node/$nid");
549
      }
Dries's avatar
 
Dries committed
550
    }
Kjartan's avatar
Kjartan committed
551 552
  }
  else {
553
    drupal_set_message(t('You are not authorized to view comments.'), 'error');
554
    drupal_goto("node/$nid");
Dries's avatar
 
Dries committed
555
  }
Dries's avatar
 
Dries committed
556

Dries's avatar
 
Dries committed
557
  return $output;
Dries's avatar
 
Dries committed
558 559
}

560 561 562 563 564 565 566
/**
 * Accepts a submission of new or changed comment content.
 *
 * @param $edit
 *   A comment array.
 *
 * @return
567
 *   If the comment is successfully saved the comment ID is returned. If the comment
568 569 570
 *   is not saved, FALSE is returned.
 */
function comment_save($edit) {
Dries's avatar
 
Dries committed
571
  global $user;
572
  if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
Dries's avatar
 
Dries committed
573
    if (!form_get_errors()) {
574
      // Check for duplicate comments. Note that we have to use the
575
      // validated/filtered data to perform such check.
576
      $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);
Dries's avatar
 
Dries committed
577
      if ($duplicate != 0) {
578
        watchdog('content', t('Comment: duplicate %subject.', array('%subject' => theme('placeholder', $edit['subject']))), WATCHDOG_WARNING);
Dries's avatar
 
Dries committed
579
      }
Dries's avatar
 
Dries committed
580

581
      if ($edit['cid']) {
582
        // Update the comment in the database.
583
        db_query("UPDATE {comments} SET status = %d, timestamp = %d, subject = '%s', comment = '%s', format = %d, uid = %d, name = '%s', mail = '%s', homepage = '%s' WHERE cid = %d", $edit['status'], $edit['timestamp'], $edit['subject'], $edit['comment'], $edit['format'], $edit['uid'], $edit['name'], $edit['mail'], $edit['homepage'], $edit['cid']);
Dries's avatar
 
Dries committed
584

Dries's avatar
 
Dries committed
585 586
        _comment_update_node_statistics($edit['nid']);

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

Dries's avatar
 
Dries committed
590

Dries's avatar
Dries committed
591
        // Add an entry to the watchdog log.
592
        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']));
Dries's avatar
 
Dries committed
593 594
      }
      else {
595
        // Add the comment to database.
596
        $status = user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
597
        $roles = variable_get('comment_roles', array());
Dries's avatar
 
Dries committed
598 599 600 601 602 603
        $score = 0;

        foreach (array_intersect(array_keys($roles), array_keys($user->roles)) as $rid) {
          $score = max($roles[$rid], $score);
        }

Dries's avatar
 
Dries committed
604 605
        $users = serialize(array(0 => $score));

606
        // Here we are building the thread field. See the comment
607
        // in comment_render().
608
        if ($edit['pid'] == 0) {
609 610
          // This is a comment with no parent comment (depth 0): we start
          // by retrieving the maximum thread level.
611
          $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $edit['nid']));
Dries's avatar
 
Dries committed
612

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

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

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

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

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

632 633
          if ($max == '') {
            // First child of this parent.
634
            $thread = $parent->thread .'.'. int2vancode(0) .'/';
Dries's avatar
 
Dries committed
635 636
          }
          else {
637 638
            // Strip the "/" at the end of the thread.
            $max = rtrim($max, '/');
Dries's avatar
 
Dries committed
639

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

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

650
        $edit['cid'] = db_next_id('{comments}_cid');
Dries's avatar
 
Dries committed
651 652
        $edit['timestamp'] = time();

Dries's avatar
Dries committed
653
        if ($edit['uid'] == $user->uid) {
Dries's avatar
 
Dries committed
654 655 656
          $edit['name'] = $user->name;
        }

657
        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'], $status, $score, $users, $thread, $edit['name'], $edit['mail'], $edit['homepage']);
Dries's avatar
 
Dries committed
658 659

        _comment_update_node_statistics($edit['nid']);
Dries's avatar
 
Dries committed
660

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

664
        // Add an entry to the watchdog log.
665
        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']));
Dries's avatar
 
Dries committed
666
      }
Dries's avatar
 
Dries committed
667

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

Dries's avatar
 
Dries committed
671
      // Explain the approval queue if necessary, and then
Dries's avatar
 
Dries committed
672
      // redirect the user to the node he's commenting on.
673
      if ($status == COMMENT_NOT_PUBLISHED) {
Dries's avatar
 
Dries committed
674
        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
675
      }
676
      return $edit['cid'];
Dries's avatar
 
Dries committed
677 678
    }
    else {
679
      return FALSE;
Dries's avatar
 
Dries committed
680 681
    }
  }
Dries's avatar
 
Dries committed
682
  else {
683
    $txt = t('Comment: unauthorized comment submitted or comment submitted to a closed node %subject.', array('%subject' => theme('placeholder', $edit['subject'])));
684 685 686
    watchdog('content', $txt, WATCHDOG_WARNING);
    drupal_set_message($txt, 'error');
    return FALSE;
Dries's avatar
 
Dries committed
687 688 689 690
  }
}

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

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

695
  // If we are viewing just this comment, we link back to the node.
Dries's avatar
 
Dries committed
696
  if ($return) {
697
    $links['comment_parent'] = array(
698 699 700
      'title' => t('parent'),
      'href' => comment_node_url(),
      'fragment' => "comment-$comment->cid"
701
    );
Dries's avatar
 
Dries committed
702
  }
Dries's avatar
 
Dries committed
703

704
  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
705
    if (user_access('administer comments') && user_access('post comments')) {
706
      $links['comment_delete'] = array(
707 708
        'title' => t('delete'),
        'href' => "comment/delete/$comment->cid"
709 710
      );
      $links['comment_edit'] = array(
711 712
        'title' => t('edit'),
        'href' => "comment/edit/$comment->cid"
713 714
      );
      $links['comment_reply'] = array(
715 716
        'title' => t('reply'),
        'href' => "comment/reply/$comment->nid/$comment->cid"
717
      );
718
    }
719 720
    else if (user_access('post comments')) {
      if (comment_access('edit', $comment)) {
721
        $links['comment_edit'] = array(
722 723
          'title' => t('edit'),
          'href' => "comment/edit/$comment->cid"
724
        );
Dries's avatar
 
Dries committed
725
      }
726
      $links['comment_reply'] = array(
727 728
        'title' => t('reply'),
        'href' => "comment/reply/$comment->nid/$comment->cid"
729
      );
Dries's avatar
 
Dries committed
730 731
    }
    else {
732
      $links['comment_forbidden']['#title'] = theme('comment_post_forbidden', $comment->nid);
Dries's avatar
 
Dries committed
733
    }
Dries's avatar
 
Dries committed
734
  }
Dries's avatar
 
Dries committed
735

Dries's avatar
 
Dries committed
736
  return $links;
Dries's avatar
 
Dries committed
737 738
}

Dries's avatar
 
Dries committed
739
function comment_render($node, $cid = 0) {
Dries's avatar
 
Dries committed
740 741
  global $user;

742
  $output = '';
Dries's avatar
 
Dries committed
743

744 745
  if (user_access('access comments')) {
    // Pre-process variables.
Dries's avatar
 
Dries committed
746
    $nid = $node->nid;
Dries's avatar
 
Dries committed
747 748
    if (empty($nid)) {
      $nid = 0;
Dries's avatar
 
Dries committed
749 750
    }

751 752 753
    $mode = _comment_get_display_setting('mode');
    $order = _comment_get_display_setting('sort');
    $comments_per_page = _comment_get_display_setting('comments_per_page');
Dries's avatar
 
Dries committed
754

Dries's avatar
 
Dries committed
755
    $output .= "<a id=\"comment\"></a>\n";
Dries's avatar
 
Dries committed
756

Kjartan's avatar
Kjartan committed
757
    if ($cid) {
758
      // Single comment view.
759 760 761 762 763 764
      $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;
      }
765
      $query .= ' GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, u.picture, c.homepage, u.uid, u.name, u.picture, u.data, c.score, c.users, c.status';
766
      $result = db_query($query, $query_args);
Dries's avatar
 
Dries committed
767

Dries's avatar
 
Dries committed
768
      if ($comment = db_fetch_object($result)) {
769
        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
770 771 772 773 774 775 776 777
        $links = module_invoke_all('link', 'comment', $comment, 1);

        foreach (module_implements('link_alter') as $module) {
          $function = $module .'_link_alter';
          $function($node, $links);
        }

        $output .= theme('comment_view', $comment, $links);
Dries's avatar
 
Dries committed
778
      }
Dries's avatar
 
Dries committed
779
    }
Dries's avatar
 
Dries committed
780
    else {
781
      // Multiple comment view
782 783 784 785 786 787 788 789 790
      $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;
      }
Dries's avatar
 
Dries committed
791

792
      $query .= ' GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, u.picture, c.homepage, u.uid, u.name, u.picture, u.data, c.score, c.users, c.thread, c.status';
Dries's avatar
 
Dries committed
793

Dries's avatar
 
Dries committed
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
      /*
      ** 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
821
      ** the natural order sorted by time. However, if we "ORDER BY thread
Dries's avatar
 
Dries committed
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
      ** 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,
851 852
      ** 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"
Dries's avatar
 
Dries committed
853 854 855
      ** Here, we do not need to consider the trailing "/" so we use a
      ** substring only.
      */
Dries's avatar
 
Dries committed
856

Dries's avatar
Dries committed
857 858
      if ($order == COMMENT_ORDER_NEWEST_FIRST) {
        if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
859
          $query .= ' ORDER BY c.timestamp DESC';
Dries's avatar
 
Dries committed
860 861
        }
        else {
862
          $query .= ' ORDER BY c.thread DESC';
Dries's avatar
 
Dries committed
863
        }
Dries's avatar
 
Dries committed
864
      }
Dries's avatar
Dries committed
865 866
      else if ($order == COMMENT_ORDER_OLDEST_FIRST) {
        if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
867
          $query .= ' ORDER BY c.timestamp';
Dries's avatar
 
Dries committed
868 869 870 871
        }
        else {

          /*
872 873
          ** See comment above. Analysis learns that this doesn't cost
          ** too much. It scales much much better than having the whole
Dries's avatar
 
Dries committed
874 875 876
          ** comment structure.
          */

877
          $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))';
Dries's avatar
 
Dries committed
878
        }
Dries's avatar
 
Dries committed
879 880
      }

881
      // Start a form, for use with comment control.
882
      $result = pager_query($query, $comments_per_page, 0, $query_count, $query_args);
Dries's avatar
Dries committed
883
      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)) {
884
        $output .= comment_controls($mode, $order, $comments_per_page);
Dries's avatar
 
Dries committed
885
      }
Dries's avatar
 
Dries committed
886

Dries's avatar
 
Dries committed
887
      while ($comment = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
888
        $comment = drupal_unpack($comment);
889
        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
890
        $comment->depth = count(explode('.', $comment->thread)) - 1;
Dries's avatar
 
Dries committed
891

Dries's avatar
Dries committed
892
        if ($mode == COMMENT_MODE_FLAT_COLLAPSED) {
893
          $output .= theme('comment_flat_collapsed', $comment);
Dries's avatar
 
Dries committed
894
        }
Dries's avatar
Dries committed
895
        else if ($mode == COMMENT_MODE_FLAT_EXPANDED) {
896
          $output .= theme('comment_flat_expanded', $comment);
Dries's avatar