Code coverage for /20081101/modules/comment/comment.module

Line #Times calledCode
1
<?php
2
// $Id: comment.module,v 1.660 2008/11/01 19:51:06 dries Exp $
3
4
/**
5
 * @file
6
 * Enables users to comment on published content.
7
 *
8
 * When enabled, the Drupal comment module creates a discussion
9
 * board for each Drupal node. Users can post comments to discuss
10
 * a forum topic, weblog post, story, collaborative book page, etc.
11
 */
12
13
/**
14
 * Comment is awaiting approval.
15
 */
162366
define('COMMENT_NOT_PUBLISHED', 0);
17
18
/**
19
 * Comment is published.
20
 */
212366
define('COMMENT_PUBLISHED', 1);
22
23
/**
24
 * Comments are displayed in a flat list - collapsed.
25
 */
262366
define('COMMENT_MODE_FLAT_COLLAPSED', 1);
27
28
/**
29
 * Comments are displayed in a flat list - expanded.
30
 */
312366
define('COMMENT_MODE_FLAT_EXPANDED', 2);
32
33
/**
34
 * Comments are displayed as a threaded list - collapsed.
35
 */
362366
define('COMMENT_MODE_THREADED_COLLAPSED', 3);
37
38
/**
39
 * Comments are displayed as a threaded list - expanded.
40
 */
412366
define('COMMENT_MODE_THREADED_EXPANDED', 4);
42
43
/**
44
 * Anonymous posters cannot enter their contact information.
45
 */
462366
define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);
47
48
/**
49
 * Anonymous posters may leave their contact information.
50
 */
512366
define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);
52
53
/**
54
 * Anonymous posters are required to leave their contact information.
55
 */
562366
define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);
57
58
/**
59
 * Comment form should be displayed on a separate page.
60
 */
612366
define('COMMENT_FORM_SEPARATE_PAGE', 0);
62
63
/**
64
 * Comment form should be shown below post or list of comments.
65
 */
662366
define('COMMENT_FORM_BELOW', 1);
67
68
/**
69
 * Comments for this node are disabled.
70
 */
712366
define('COMMENT_NODE_DISABLED', 0);
72
73
/**
74
 * Comments for this node are locked.
75
 */
762366
define('COMMENT_NODE_READ_ONLY', 1);
77
78
/**
79
 * Comments are enabled on this node.
80
 */
812366
define('COMMENT_NODE_READ_WRITE', 2);
82
83
/**
84
 * Comment preview is optional.
85
 */
862366
define('COMMENT_PREVIEW_OPTIONAL', 0);
87
88
/**
89
 * Comment preview is required.
90
 */
912366
define('COMMENT_PREVIEW_REQUIRED', 1);
92
93
/**
94
 * Implementation of hook_help().
95
 */
962366
function comment_help($path, $arg) {
97
  switch ($path) {
981678
    case 'admin/help#comment':
9927
      $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>';
10027
      $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>';
10127
      $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
10327
      return $output;
104
1051676
    case 'admin/content/comment':
1069
      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
1081667
    case 'admin/content/comment/approval':
1095
      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>';
1100
  }
1111662
}
112
113
/**
114
 * Implementation of hook_theme().
115
 */
1162366
function comment_theme() {
117
  return array(
118
    'comment_block' => array(
119178
      'arguments' => array(),
120178
    ),
121
    'comment_admin_overview' => array(
122178
      'arguments' => array('form' => NULL),
123178
    ),
124
    'comment_preview' => array(
125178
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' =>
array(), 'visible' => 1),
126178
    ),
127
    'comment_view' => array(
128178
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' =>
array(), 'visible' => 1),
129178
    ),
130
    'comment' => array(
131178
      'template' => 'comment',
132178
      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' =>
array()),
133178
    ),
134
    'comment_folded' => array(
135178
      'template' => 'comment-folded',
136178
      'arguments' => array('comment' => NULL),
137178
    ),
138
    'comment_flat_collapsed' => array(
139178
      'arguments' => array('comment' => NULL, 'node' => NULL),
140178
    ),
141
    'comment_flat_expanded' => array(
142178
      'arguments' => array('comment' => NULL, 'node' => NULL),
143178
    ),
144
    'comment_thread_collapsed' => array(
145178
      'arguments' => array('comment' => NULL, 'node' => NULL),
146178
    ),
147
    'comment_thread_expanded' => array(
148178
      'arguments' => array('comment' => NULL, 'node' => NULL),
149178
    ),
150
    'comment_post_forbidden' => array(
151178
      'arguments' => array('nid' => NULL),
152178
    ),
153
    'comment_wrapper' => array(
154178
      'template' => 'comment-wrapper',
155178
      'arguments' => array('content' => NULL, 'node' => NULL),
156178
    ),
157
    'comment_submitted' => array(
158178
      'arguments' => array('comment' => NULL),
159178
    ),
160178
  );
1610
}
162
163
/**
164
 * Implementation of hook_menu().
165
 */
1662366
function comment_menu() {
167161
  $items['admin/content/comment'] = array(
168161
    'title' => 'Comments',
169161
    'description' => 'List and edit site comments and the comment
moderation queue.',
170161
    'page callback' => 'comment_admin',
171161
    'access arguments' => array('administer comments'),
172
  );
173
  // Tabs begin here.
174161
  $items['admin/content/comment/new'] = array(
175161
    'title' => 'Published comments',
176161
    'type' => MENU_DEFAULT_LOCAL_TASK,
177161
    'weight' => -10,
178
  );
179161
  $items['admin/content/comment/approval'] = array(
180161
    'title' => 'Approval queue',
181161
    'page arguments' => array('approval'),
182161
    'access arguments' => array('administer comments'),
183161
    'type' => MENU_LOCAL_TASK,
184
  );
185161
  $items['comment/delete'] = array(
186161
    'title' => 'Delete comment',
187161
    'page callback' => 'comment_delete',
188161
    'access arguments' => array('administer comments'),
189161
    'type' => MENU_CALLBACK,
190
  );
191161
  $items['comment/edit'] = array(
192161
    'title' => 'Edit comment',
193161
    'page callback' => 'comment_edit',
194161
    'access arguments' => array('post comments'),
195161
    'type' => MENU_CALLBACK,
196
  );
197161
  $items['comment/reply/%node'] = array(
198161
    'title' => 'Reply to comment',
199161
    'page callback' => 'comment_reply',
200161
    'page arguments' => array(2),
201161
    'access callback' => 'node_access',
202161
    'access arguments' => array('view', 2),
203161
    'type' => MENU_CALLBACK,
204
  );
205161
  $items['comment/approve'] = array(
206161
    'title' => 'Approve a comment',
207161
    'page callback' => 'comment_approve',
208161
    'page arguments' => array(2),
209161
    'access arguments' => array('administer comments'),
210161
    'type' => MENU_CALLBACK,
211
  );
212
213161
  return $items;
2140
}
215
216
/**
217
 * Implementation of hook_node_type().
218
 */
2192366
function comment_node_type($op, $info) {
220
  $settings = array(
221135
    'comment',
222135
    'comment_default_mode',
223135
    'comment_default_per_page',
224135
    'comment_anonymous',
225135
    'comment_subject_field',
226135
    'comment_preview',
227135
    'comment_form_location',
228135
  );
229
230
  switch ($op) {
231135
    case 'delete':
2320
      foreach ($settings as $setting) {
2330
        variable_del($setting . '_' . $info->type);
2340
      }
2350
      break;
2360
  }
237135
}
238
239
/**
240
 * Implementation of hook_perm().
241
 */
2422366
function comment_perm() {
243
  return array(
244
    'administer comments' => array(
245172
      'title' => t('Administer comments'),
246172
      'description' => t('Manage and approve comments, and configure
comment administration settings.'),
247172
    ),
248
    'access comments' => array(
249172
      'title' => t('Access comments'),
250172
      'description' => t('View comments attached to content.'),
251172
    ),
252
    'post comments' => array(
253172
      'title' => t('Post comments'),
254172
      'description' => t('Add comments to content (approval required).'),
255172
    ),
256
    'post comments without approval' => array(
257172
      'title' => t('Post comments without approval'),
258172
      'description' => t('Add comments to content (no approval
required).'),
259172
    ),
260172
  );
2610
}
262
263
/**
264
 * Implementation of hook_block().
265
 *
266
 * Generates a block with the most recent comments.
267
 */
2682366
function comment_block($op = 'list', $delta = '', $edit = array()) {
269
  switch ($op) {
27029
    case 'list':
27129
      $blocks['recent']['info'] = t('Recent comments');
272
27329
      return $blocks;
274
2750
    case 'configure':
2760
      $form['comment_block_count'] = array(
2770
        '#type' => 'select',
2780
        '#title' => t('Number of recent comments'),
2790
        '#default_value' => variable_get('comment_block_count', 10),
2800
        '#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)),
2810
        '#description' => t('Number of comments displayed in the <em>Recent
comments</em> block.'),
282
      );
283
2840
      return $form;
285
2860
    case 'save':
2870
      variable_set('comment_block_count',
(int)$edit['comment_block_count']);
2880
      break;
289
2900
    case 'view':
2910
      if (user_access('access comments')) {
2920
        $block['subject'] = t('Recent comments');
2930
        $block['content'] = theme('comment_block');
294
2950
        return $block;
2960
      }
2970
  }
2980
}
299
300
/**
301
 * Find the most recent comments that are available to the current user.
302
 *
303
 * This is done in two steps:
304
 *   1. Query the {node_comment_statistics} table to find n number of nodes
that
305
 *      have the most recent comments. This table is indexed on
306
 *      last_comment_timestamp, thus making it a fast query.
307
 *   2. Load the information from the comments table based on the nids
found
308
 *      in step 1.
309
 *
310
 * @param integer $number
311
 *   (optional) The maximum number of comments to find.
312
 * @return
313
 *   An array of comment objects each containing a nid,
314
 *   subject, cid, and timestamp, or an empty array if there are no recent
315
 *   comments visible to the current user.
316
 */
3172366
function comment_get_recent($number = 10) {
318
  // Step 1: Select a $number of nodes which have new comments,
319
  //         and are visible to the current user.
3200
  $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);
3210
  $nids = array();
3220
  while ($row = db_fetch_object($result)) {
3230
    $nids[] = $row->nid;
3240
  }
325
3260
  $comments = array();
3270
  if (!empty($nids)) {
328
    // Step 2: From among the comments on the nodes selected in the first
query,
329
    //         find the $number of most recent comments.
3300
    $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);
3310
    while ($comment = db_fetch_object($result)) {
3320
      $comments[] = $comment;
3330
    }
3340
  }
335
3360
  return $comments;
3370
}
338
339
/**
340
 * Calculate page number for first new comment.
341
 *
342
 * @param $num_comments
343
 *   Number of comments.
344
 * @param $new_replies
345
 *   Number of new replies.
346
 * @param $node
347
 *   The first new comment node.
348
 * @return
349
 *   "page=X" if the page number is greater than zero; empty string
otherwise.
350
 */
3512366
function comment_new_page_count($num_comments, $new_replies, $node) {
35216
  $comments_per_page = _comment_get_display_setting('comments_per_page',
$node);
35316
  $mode = _comment_get_display_setting('mode', $node);
35416
  $pagenum = NULL;
35516
  $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED,
COMMENT_MODE_FLAT_EXPANDED));
35616
  if ($num_comments <= $comments_per_page) {
357
    // Only one page of comments.
35816
    $pageno = 0;
35916
  }
3600
  elseif ($flat) {
361
    // Flat comments.
3620
    $count = $num_comments - $new_replies;
3630
    $pageno =  $count / $comments_per_page;
3640
  }
365
  else {
366
    // Threaded comments.
367
    // Find the first thread with a new comment.
3680
    $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);
3690
    $thread = substr(db_result($result), 0, -1);
3700
    $result_count = db_query("SELECT COUNT(*) FROM {comments} WHERE nid =
%d AND status = 0 AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < '" .
$thread . "'", $node->nid);
3710
    $count = db_result($result_count);
3720
    $pageno =  $count / $comments_per_page;
373
  }
374
37516
  if ($pageno >= 1) {
3760
    $pagenum = "page=" . intval($pageno);
3770
  }
378
37916
  return $pagenum;
3800
}
381
382
/**
383
 * Returns a formatted list of recent comments to be displayed in the
comment block.
384
 *
385
 * @return
386
 *   The comment list HTML.
387
 * @ingroup themeable
388
 */
3892366
function theme_comment_block() {
3900
  $items = array();
3910
  $number = variable_get('comment_block_count', 10);
3920
  foreach (comment_get_recent($number) as $comment) {
3930
    $items[] = l($comment->subject, 'node/' . $comment->nid,
array('fragment' => 'comment-' . $comment->cid)) . '<br />' . t('@time
ago', array('@time' => format_interval(REQUEST_TIME -
$comment->timestamp)));
3940
  }
395
3960
  if ($items) {
3970
    return theme('item_list', $items);
3980
  }
3990
}
400
401
/**
402
 * Implementation of hook_link().
403
 */
4042366
function comment_link($type, $node = NULL, $teaser = FALSE) {
405251
  $links = array();
406
407251
  if ($type == 'node' && $node->comment) {
408211
    if ($teaser) {
409
      // Main page: display the number of comments that have been posted.
41024
      if (user_access('access comments')) {
41111
        if ($node->comment_count) {
4125
          $links['comment_comments'] = array(
4135
            'title' => format_plural($node->comment_count, '1 comment',
'@count comments'),
4145
            'href' => "node/$node->nid",
4155
            'attributes' => array('title' => t('Jump to the first comment
of this posting.')),
416
            'fragment' => 'comments'
4175
          );
418
4195
          $new = comment_num_new($node->nid);
4205
          if ($new) {
4210
            $links['comment_new_comments'] = array(
4220
              'title' => format_plural($new, '1 new comment', '@count new
comments'),
4230
              'href' => "node/$node->nid",
4240
              'query' => comment_new_page_count($node->comment_count, $new,
$node),
4250
              'attributes' => array('title' => t('Jump to the first new
comment of this posting.')),
426
              'fragment' => 'new'
4270
            );
4280
          }
4295
        }
430
        else {
4316
          if ($node->comment == COMMENT_NODE_READ_WRITE) {
4326
            if (user_access('post comments')) {
4336
              $links['comment_add'] = array(
4346
                'title' => t('Add new comment'),
4356
                'href' => "comment/reply/$node->nid",
4366
                'attributes' => array('title' => t('Add a new comment to
this page.')),
437
                'fragment' => 'comment-form'
4386
              );
4396
            }
440
            else {
4410
              $links['comment_forbidden']['title'] =
theme('comment_post_forbidden', $node);
442
            }
4436
          }
444
        }
44511
      }
44624
    }
447
    else {
448
      // Node page: add a "post comment" link if the user is allowed to
post comments,
449
      // if this node is not read-only, and if the comment form isn't
already shown.
450187
      if ($node->comment == COMMENT_NODE_READ_WRITE) {
451185
        if (user_access('post comments')) {
452181
          if (variable_get('comment_form_location_' . $node->type,
COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
453177
            $links['comment_add'] = array(
454177
              'title' => t('Add new comment'),
455177
              'href' => "comment/reply/$node->nid",
456177
              'attributes' => array('title' => t('Share your thoughts and
opinions related to this posting.')),
457
              'fragment' => 'comment-form'
458177
            );
459177
          }
460181
        }
461
        else {
4624
          $links['comment_forbidden']['title'] =
theme('comment_post_forbidden', $node);
463
        }
464185
      }
465
    }
466211
  }
467
468251
  if ($type == 'comment') {
46922
    $links = comment_links($node, $teaser);
47022
  }
471
472251
  if (isset($links['comment_forbidden'])) {
4734
    $links['comment_forbidden']['html'] = TRUE;
4744
  }
475
476251
  return $links;
4770
}
478
479
/**
480
 * Implementation of hook_form_alter().
481
 */
4822366
function comment_form_alter(&$form, $form_state, $form_id) {
4831575
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
4843
    $form['comment'] = array(
4853
      '#type' => 'fieldset',
4863
      '#title' => t('Comment settings'),
4873
      '#collapsible' => TRUE,
4883
      '#collapsed' => TRUE,
489
    );
4903
    $form['comment']['comment'] = array(
4913
      '#type' => 'radios',
4923
      '#title' => t('Default comment setting'),
4933
      '#default_value' => variable_get('comment_' .
$form['#node_type']->type, COMMENT_NODE_READ_WRITE),
4943
      '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
4953
      '#description' => t('Users with the <em>administer comments</em>
permission will be able to override this setting.'),
496
    );
4973
    $form['comment']['comment_default_mode'] = array(
4983
      '#type' => 'radios',
4993
      '#title' => t('Default display mode'),
5003
      '#default_value' => variable_get('comment_default_mode_' .
$form['#node_type']->type, COMMENT_MODE_THREADED_EXPANDED),
5013
      '#options' => _comment_get_modes(),
5023
      '#description' => t('Expanded views display the body of the comment.
Threaded views keep replies together.'),
503
    );
5043
    $form['comment']['comment_default_per_page'] = array(
5053
      '#type' => 'select',
5063
      '#title' => t('Comments per page'),
5073
      '#default_value' => variable_get('comment_default_per_page_' .
$form['#node_type']->type, 50),
5083
      '#options' => _comment_per_page(),
5093
      '#description' => t('Additional comments will be displayed on
separate pages.'),
510
    );
5113
    $form['comment']['comment_anonymous'] = array(
5123
      '#type' => 'radios',
5133
      '#title' => t('Anonymous commenting'),
5143
      '#default_value' => variable_get('comment_anonymous_' .
$form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT),
515
      '#options' => array(
5163
        COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not
enter their contact information'),
5173
        COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave
their contact information'),
5183
        COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave
their contact information')),
5193
      '#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')))),
520
    );
521
5223
    if (!user_access('post comments', drupal_anonymous_user())) {
5233
      $form['comment']['comment_anonymous']['#disabled'] = TRUE;
5243
    }
525
5263
    $form['comment']['comment_subject_field'] = array(
5273
      '#type' => 'radios',
5283
      '#title' => t('Comment subject field'),
5293
      '#default_value' => variable_get('comment_subject_field_' .
$form['#node_type']->type, 1),
5303
      '#options' => array(t('Disabled'), t('Enabled')),
5313
      '#description' => t('Can users provide a unique subject for their
comments?'),
532
    );
5333
    $form['comment']['comment_preview'] = array(
5343
      '#type' => 'radios',
5353
      '#title' => t('Preview comment'),
5363
      '#default_value' => variable_get('comment_preview_' .
$form['#node_type']->type, COMMENT_PREVIEW_REQUIRED),
5373
      '#options' => array(t('Optional'), t('Required')),
5383
      '#description' => t("Forces a user to look at their comment by
clicking on a 'Preview' button before they can actually add the comment"),
539
    );
5403
    $form['comment']['comment_form_location'] = array(
5413
      '#type' => 'radios',
5423
      '#title' => t('Location of comment submission form'),
5433
      '#default_value' => variable_get('comment_form_location_' .
$form['#node_type']->type, COMMENT_FORM_SEPARATE_PAGE),
5443
      '#options' => array(t('Display on separate page'), t('Display below
post or comments')),
545
    );
5463
  }
5471572
  elseif (!empty($form['#node_edit_form'])) {
54889
    $node = $form['#node'];
54989
    $form['comment_settings'] = array(
55089
      '#type' => 'fieldset',
55189
      '#access' => user_access('administer comments'),
55289
      '#title' => t('Comment settings'),
55389
      '#collapsible' => TRUE,
55489
      '#collapsed' => TRUE,
55589
      '#weight' => 30,
556
    );
55789
    $form['comment_settings']['comment'] = array(
55889
      '#type' => 'radios',
55989
      '#parents' => array('comment'),
56089
      '#default_value' => $node->comment,
56189
      '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
562
    );
56389
  }
5641575
}
565
566
/**
567
 * Implementation of hook_nodeapi_load().
568
 */
5692366
function comment_nodeapi_load(&$node, $arg = 0) {
570436
  if ($node->comment != COMMENT_NODE_DISABLED) {
571363
    return db_fetch_array(db_query("SELECT last_comment_timestamp,
last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid =
%d", $node->nid));
5720
  }
57375
  return array('last_comment_timestamp' => $node->created,
'last_comment_name' => '', 'comment_count' => 0);
5740
}
575
576
/**
577
 * Implementation of hook_nodeapi_prepare().
578
 */
5792366
function comment_nodeapi_prepare(&$node, $arg = 0) {
58086
  if (!isset($node->comment)) {
58145
    $node->comment = variable_get("comment_$node->type",
COMMENT_NODE_READ_WRITE);
58245
  }
58386
}
584
585
/**
586
 * Implementation of hook_nodeapi_insert().
587
 */
5882366
function comment_nodeapi_insert(&$node, $arg = 0) {
58958
  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);
59058
}
591
592
/**
593
 * Implementation of hook_nodeapi_delete().
594
 */
5952366
function comment_nodeapi_delete(&$node, $arg = 0) {
59612
  db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
59712
  db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d',
$node->nid);
59812
}
599
600
/**
601
 * Implementation of hook_nodeapi_update_index().
602
 */
6032366
function comment_nodeapi_update_index(&$node, $arg = 0) {
6042
  $text = '';
6052
  $comments = db_query('SELECT subject, comment, format FROM {comments}
WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED);
6062
  while ($comment = db_fetch_object($comments)) {
6070
    $text .= '<h2>' . check_plain($comment->subject) . '</h2>' .
check_markup($comment->comment, $comment->format, FALSE);
6080
  }
6092
  return $text;
6100
}
611
612
/**
613
 * Implementation of hook_nodeapi_search_result().
614
 */
6152366
function comment_nodeapi_search_result(&$node, $arg = 0) {
6164
  $comments = db_result(db_query('SELECT comment_count FROM
{node_comment_statistics} WHERE nid = %d', $node->nid));
6174
  return format_plural($comments, '1 comment', '@count comments');
6180
}
619
620
/**
621
 * Implementation of hook_nodeapi_rss_item().
622
 */
6232366
function comment_nodeapi_rss_item(&$node, $arg = 0) {
6240
  if ($node->comment != COMMENT_NODE_DISABLED) {
6250
    return array(array('key' => 'comments', 'value' => url('node/' .
$node->nid, array('fragment' => 'comments', 'absolute' => TRUE))));
6260
  }
627
  else {
6280
    return array();
629
  }
6300
}
631
632
/**
633
 * Implementation of hook_user_delete().
634
 */
6352366
function comment_user_delete(&$edit, &$user, $category = NULL) {
6362
  db_query('UPDATE {comments} SET uid = 0 WHERE uid = %d', $user->uid);
6372
  db_query('UPDATE {node_comment_statistics} SET last_comment_uid = 0 WHERE
last_comment_uid = %d', $user->uid);
6382
}
639
640
/**
641
 * This is *not* a hook_access() implementation. This function is called
642
 * to determine whether the current user has access to a particular
comment.
643
 *
644
 * Authenticated users can edit their comments as long they have not been
645
 * replied to. This prevents people from changing or revising their
646
 * statements based on the replies to their posts.
647
 *
648
 * @param $op
649
 *   The operation that is to be performed on the comment. Only 'edit' is
recognized now.
650
 * @param $comment
651
 *   The comment object.
652
 * @return
653
 *   TRUE if the current user has acces to the comment, FALSE otherwise.
654
 */
6552366
function comment_access($op, $comment) {
65621
  global $user;
657
65821
  if ($op == 'edit') {
65921
    return ($user->uid && $user->uid == $comment->uid &&
comment_num_replies($comment->cid) == 0) || user_access('administer
comments');
6600
  }
6610
}
662
663
/**
664
 * A simple helper function.
665
 *
666
 * @return
667
 *   The 0th and the 1st path components joined by a slash.
668
 */
6692366
function comment_node_url() {
6701
  return arg(0) . '/' . arg(1);
6710
}
672
673
/**
674
 * Accepts a submission of new or changed comment content.
675
 *
676
 * @param $edit
677
 *   A comment array.
678
 *
679
 * @return
680
 *   If the comment is successfully saved the comment ID is returned. If
the comment
681
 *   is not saved, FALSE is returned.
682
 */
6832366
function comment_save($edit) {
68414
  global $user;
68514
  if (user_access('post comments') && (user_access('administer comments')
|| node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
68614
    if (!form_get_errors()) {
687
      $edit += array(
68814
        'mail' => '',
68914
        'homepage' => '',
69014
        'name' => '',
69114
        'status' => user_access('post comments without approval') ?
COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED,
6920
      );
69314
      if ($edit['cid']) {
694
        // Update the comment in the database.
6951
        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['comment_format'], $edit['uid'],
$edit['name'], $edit['mail'], $edit['homepage'], $edit['cid']);
696
        // Allow modules to respond to the updating of a comment.
6971
        comment_invoke_comment($edit, 'update');
698
        // Add an entry to the watchdog log.
6991
        watchdog('content', 'Comment: updated %subject.', array('%subject'
=> $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'],
array('fragment' => 'comment-' . $edit['cid'])));
7001
      }
701
      else {
702
        // Add the comment to database. This next section builds the thread
field.
703
        // Also see the documentation for comment_render().
70413
        if ($edit['pid'] == 0) {
705
          // This is a comment with no parent comment (depth 0): we start
706
          // by retrieving the maximum thread level.
70711
          $max = db_result(db_query('SELECT MAX(thread) FROM {comments}
WHERE nid = %d', $edit['nid']));
708
          // Strip the "/" from the end of the thread.
70911
          $max = rtrim($max, '/');
710
          // Finally, build the thread field for this new comment.
71111
          $thread = int2vancode(vancode2int($max) + 1) . '/';
71211
        }
713
        else {
714
          // This is a comment with a parent comment, so increase
715
          // the part of the thread value at the proper depth.
716
717
          // Get the parent comment:
7182
          $parent = comment_load($edit['pid']);
719
          // Strip the "/" from the end of the parent thread.
7202
          $parent->thread = (string) rtrim((string) $parent->thread, '/');
721
          // Get the max value in *this* thread.
7222
          $max = db_query("SELECT MAX(thread) FROM {comments} WHERE thread
LIKE :thread AND nid = :nid", array(':thread' => $parent->thread .'.%',
':nid' => $edit['nid']))->fetchField();
723
7242
          if ($max == '') {
725
            // First child of this parent.
7261
            $thread = $parent->thread . '.' . int2vancode(0) . '/';
7271
          }
728
          else {
729
            // Strip the "/" at the end of the thread.
7301
            $max = rtrim($max, '/');
731
            // Get the value at the correct depth.
7321
            $parts = explode('.', $max);
7331
            $parent_depth = count(explode('.', $parent->thread));
7341
            $last = $parts[$parent_depth];
735
            // Finally, build the thread field for this new comment.
7361
            $thread = $parent->thread . '.' .
int2vancode(vancode2int($last) + 1) . '/';
737
          }
738
        }
739
74013
        if (empty($edit['timestamp'])) {
7410
          $edit['timestamp'] = REQUEST_TIME;
7420
        }
743
74413
        if ($edit['uid'] === $user->uid) { // '===' Need to modify
anonymous users as well.
7458
          $edit['name'] = $user->name;
7468
        }
747
74813
        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['comment_format'], ip_address(),
$edit['timestamp'], $edit['status'], $thread, $edit['name'], $edit['mail'],
$edit['homepage']);
74913
        $edit['cid'] = db_last_insert_id('comments', 'cid');
750
        // Tell the other modules a new comment has been submitted.
75113
        comment_invoke_comment($edit, 'insert');
752
        // Add an entry to the watchdog log.
75313
        watchdog('content', 'Comment: added %subject.', array('%subject' =>
$edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/' . $edit['nid'],
array('fragment' => 'comment-' . $edit['cid'])));
754
      }
75514
      _comment_update_node_statistics($edit['nid']);
756
      // Clear the cache so an anonymous user can see his comment being
added.
75714
      cache_clear_all();
758
759
      // Explain the approval queue if necessary, and then
760
      // redirect the user to the node he's commenting on.
76114
      if ($edit['status'] == COMMENT_NOT_PUBLISHED) {
7622
        drupal_set_message(t('Your comment has been queued for moderation
by site administrators and will be published after approval.'));
7632
      }
764
      else {
76512
        drupal_set_message(t('Your comment has been posted.'));
76612
        comment_invoke_comment($edit, 'publish');
767
      }
768
76914
      return $edit['cid'];
7700
    }
771
    else {
7720
      return FALSE;
773
    }
7740
  }
775
  else {
7760
    watchdog('content', 'Comment: unauthorized comment submitted or comment
submitted to a closed post %subject.', array('%subject' =>
$edit['subject']), WATCHDOG_WARNING);
7770
    drupal_set_message(t('Comment: unauthorized comment submitted or
comment submitted to a closed post %subject.', array('%subject' =>
$edit['subject'])), 'error');
778
7790
    return FALSE;
780
  }
7810
}
782
783
/**
784
 * Build command links for a comment (e.g.\ edit, reply, delete) with
respect to the current user's access permissions.
785
 *
786
 * @param $comment
787
 *   The comment to which the links will be related.
788
 * @param $return
789
 *   Not used.
790
 * @return
791
 *   An associative array containing the links.
792
 */
7932366
function comment_links($comment, $return = 1) {
79422
  global $user;
79522
  $links = array();
796
797
  // If viewing just this comment, link back to the node.
79822
  if ($return) {
7991
    $links['comment_parent'] = array(
8001
      'title' => t('parent'),
8011
      'href' => comment_node_url(),
8021
      'fragment' => "comment-$comment->cid"
8031
    );
8041
  }
805
80622
  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
80722
    if (user_access('administer comments') && user_access('post comments'))
{
8084
      $links['comment_delete'] = array(
8094
        'title' => t('delete'),
8104
        'href' => "comment/delete/$comment->cid"
8114
      );
8124
      $links['comment_edit'] = array(
8134
        'title' => t('edit'),
8144
        'href' => "comment/edit/$comment->cid"
8154
      );
8164
      $links['comment_reply'] = array(
8174
        'title' => t('reply'),
8184
        'href' => "comment/reply/$comment->nid/$comment->cid"
8194
      );
8204
      if ($comment->status == COMMENT_NOT_PUBLISHED) {
8211
        $links['comment_approve'] = array(
8221
          'title' => t('approve'),
8231
          'href' => "comment/approve/$comment->cid"
8241
        );
8251
      }
8264
    }
82718
    elseif (user_access('post comments')) {
82818
      if (comment_access('edit', $comment)) {
82912
        $links['comment_edit'] = array(
83012
          'title' => t('edit'),
83112
          'href' => "comment/edit/$comment->cid"
83212
        );
83312
      }
83418
      $links['comment_reply'] = array(
83518
        'title' => t('reply'),
83618
        'href' => "comment/reply/$comment->nid/$comment->cid"
83718
      );
83818
    }
839
    else {
8400
      $node = node_load($comment->nid);
8410
      $links['comment_forbidden']['title'] =
theme('comment_post_forbidden', $node);
842
    }
84322
  }
844
84522
  return $links;
8460
}
847
848
/**
849
 * Renders comment(s).
850
 *
851
 * @param $node
852
 *   The node which comment(s) needs rendering.
853
 * @param $cid
854
 *   Optional, if given, only one comment is rendered.
855
 *
856
 * To display threaded comments in the correct order we keep a 'thread'
field
857
 * and order by that value. This field keeps this data in
858
 * a way which is easy to update and convenient to use.
859
 *
860
 * A "thread" value starts at "1". If we add a child (A) to this comment,
861
 * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
862
 * brother of (A) will get "1.2". Next brother of the parent of (A) will
get
863
 * "2" and so on.
864
 *
865
 * First of all note that the thread field stores the depth of the comment:
866
 * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
867
 *
868
 * Now to get the ordering right, consider this example:
869
 *
870
 * 1
871
 * 1.1
872
 * 1.1.1
873
 * 1.2
874
 * 2
875
 *
876
 * If we "ORDER BY thread ASC" we get the above result, and this is the
877
 * natural order sorted by time. However, if we "ORDER BY thread DESC"
878
 * we get:
879
 *
880
 * 2
881
 * 1.2
882
 * 1.1.1
883
 * 1.1
884
 * 1
885
 *
886
 * Clearly, this is not a natural way to see a thread, and users will get
887
 * confused. The natural order to show a thread by time desc would be:
888
 *
889
 * 2
890
 * 1
891
 * 1.2
892
 * 1.1
893
 * 1.1.1
894
 *
895
 * which is what we already did before the standard pager patch. To achieve
896
 * this we simply add a "/" at the end of each "thread" value. This way,
the
897
 * thread fields will look like this:
898
 *
899
 * 1/
900
 * 1.1/
901
 * 1.1.1/
902
 * 1.2/
903
 * 2/
904
 *
905
 * we add "/" since this char is, in ASCII, higher than every number, so if
906
 * now we "ORDER BY thread DESC" we get the correct order. However this
would
907
 * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not
need
908
 * to consider the trailing "/" so we use a substring only.
909
 */
9102366
function comment_render($node, $cid = 0) {
911149
  global $user;
912149
  $output = '';
913
914149
  if (user_access('access comments')) {
915
    // Pre-process variables.
916145
    $nid = $node->nid;
917145
    if (empty($nid)) {
9180
      $nid = 0;
9190
    }
920
921145
    $mode = _comment_get_display_setting('mode', $node);
922145
    $comments_per_page = _comment_get_display_setting('comments_per_page',
$node);
923
924145
    if ($cid && is_numeric($cid)) {
925
      // Single comment view.
9261
      $query = 'SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.format,
c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name,
u.signature, u.picture, u.data, c.status FROM {comments} c INNER JOIN
{users} u ON c.uid = u.uid WHERE c.cid = %d';
9271
      $query_args = array($cid);
9281
      if (!user_access('administer comments')) {
9291
        $query .= ' AND c.status = %d';
9301
        $query_args[] = COMMENT_PUBLISHED;
9311
      }
932
9331
      $query = db_rewrite_sql($query, 'c', 'cid');
9341
      $result = db_query($query, $query_args);
935
9361
      if ($comment = db_fetch_object($result)) {
9371
        $comment->name = $comment->uid ? $comment->registered_name :
$comment->name;
9381
        $links = module_invoke_all('link', 'comment', $comment, 1);
9391
        drupal_alter('link', $links, $node);
940
9411
        $output .= theme('comment_view', $comment, $node, $links);
9421
      }
9431
    }
944
    else {
945
      // Multiple comment view.
946144
      $query_count = 'SELECT COUNT(*) FROM {comments} c WHERE c.nid = %d';
947144
      $query = 'SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment,
c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS
registered_name, u.signature, u.picture, u.data, c.thread, c.status FROM
{comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d';
948
949144
      $query_args = array($nid);
950144
      if (!user_access('administer comments')) {
951139
        $query .= ' AND c.status = %d';
952139
        $query_count .= ' AND c.status = %d';
953139
        $query_args[] = COMMENT_PUBLISHED;
954139
      }
955144
      if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode ==
COMMENT_MODE_FLAT_EXPANDED) {
9560
        $query .= ' ORDER BY c.cid';
9570
      }
958
      else {
959
        // See comment above. Analysis reveals that this doesn't cost too
960
        // much. It scales much much better than having the whole comment
961
        // structure.
962144
        $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) -
1))';
963
      }
964
965144
      $query = db_rewrite_sql($query, 'c', 'cid');
966144
      $query_count = db_rewrite_sql($query_count, 'c', 'cid');
967
968144
      $result = pager_query($query, $comments_per_page, 0, $query_count,
$query_args);
969
970144
      $divs = 0;
971144
      $num_rows = FALSE;
972144
      $comments = '';
973144
      drupal_add_css(drupal_get_path('module', 'comment') .
'/comment.css');
974144
      while ($comment = db_fetch_object($result)) {
97521
        $comment = drupal_unpack($comment);
97621
        $comment->name = $comment->uid ? $comment->registered_name :
$comment->name;
97721
        $comment->depth = count(explode('.', $comment->thread)) - 1;
978
97921
        if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode ==
COMMENT_MODE_THREADED_EXPANDED) {
98021
          if ($comment->depth > $divs) {
9818
            $divs++;
9828
            $comments .= '<div class="indented">';
9838
          }
984
          else {
98520
            while ($comment->depth < $divs) {
9864
              $divs--;
9874
              $comments .= '</div>';
9884
            }
989
          }
99021
        }
991
99221
        if ($mode == COMMENT_MODE_FLAT_COLLAPSED) {
9930
          $comments .= theme('comment_flat_collapsed', $comment, $node);
9940
        }
99521
        elseif ($mode == COMMENT_MODE_FLAT_EXPANDED) {
9960
          $comments .= theme('comment_flat_expanded', $comment, $node);
9970
        }
99821
        elseif ($mode == COMMENT_MODE_THREADED_COLLAPSED) {
9990
          $comments .= theme('comment_thread_collapsed', $comment, $node);
10000
        }
100121
        elseif ($mode == COMMENT_MODE_THREADED_EXPANDED) {
100221
          $comments .= theme('comment_thread_expanded', $comment, $node);
100321
        }
100421
        $num_rows = TRUE;
100521
      }
1006144
      while ($divs-- > 0) {
10074
        $comments .= '</div>';
10084
      }
1009144
      $output .= $comments;
1010144
      $output .= theme('pager', NULL, $comments_per_page, 0);
1011
    }
1012
1013
    // If enabled, show new comment form if it's not already being
displayed.
1014145
    $reply = arg(0) == 'comment' && arg(1) == 'reply';
1015145
    if (user_access('post comments') && node_comment_mode($nid) ==
COMMENT_NODE_READ_WRITE && (variable_get('comment_form_location_' .
$node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_BELOW) && !$reply)
{
10162
      $output .= comment_form_box(array('nid' => $nid), t('Post new
comment'));
10172
    }
1018145
    $output = theme('comment_wrapper', $output, $node);
1019145
  }
1020
1021149
  return $output;
10220
}
1023
1024
/**
1025
 * Comment operations. Offer different update operations depending on
1026
 * which comment administration page is being viewed.
1027
 *
1028
 * @param $action
1029
 *   The comment administration page.
1030
 * @return
1031
 *   An associative array containing the offered operations.
1032
 */
10332366
function comment_operations($action = NULL) {
103417
  if ($action == 'publish') {
1035
    $operations = array(
10368
      'publish' => array(t('Publish the selected comments'), 'UPDATE
{comments} SET status = ' . COMMENT_PUBLISHED . ' WHERE cid = %d'),
10378
      'delete' => array(t('Delete the selected comments'), '')
10388
    );
10398
  }
104011
  elseif ($action == 'unpublish') {
1041
    $operations = array(
10429
      'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE
{comments} SET status = ' . COMMENT_NOT_PUBLISHED . ' WHERE cid = %d'),
10439
      'delete' => array(t('Delete the selected comments'), '')
10449
    );
10459
  }
1046
  else {
1047
    $operations = array(
10483
      'publish' => array(t('Publish the selected comments'), 'UPDATE
{comments} SET status = ' . COMMENT_PUBLISHED . ' WHERE cid = %d'),
10493
      'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE
{comments} SET status = ' . COMMENT_NOT_PUBLISHED . ' WHERE cid = %d'),
10503
      'delete' => array(t('Delete the selected comments'), '')
10513
    );
1052
  }
1053
105417
  return $operations;
10550
}
1056
1057
/**
1058
 * Begin the misc functions: helpers, privates, history.
1059
 */
1060
1061
/**
1062
 * Load the entire comment by cid.
1063
 *
1064
 * @param $cid
1065
 *   The identifying comment id.
1066
 * @return
1067
 *   The comment object.
1068
 */
10692366
function comment_load($cid) {
10709
  return db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid =
%d', $cid));
10710
}
1072
1073
/**
1074
 * Get replies count for a comment.
1075
 *
1076
 * @param $pid
1077
 *   The comment id.
1078
 * @return
1079
 *   The replies count.
1080
 */
10812366
function comment_num_replies($pid) {
108215
  static $cache;
1083
108415
  if (!isset($cache[$pid])) {
108515
    $cache[$pid] = db_result(db_query('SELECT COUNT(cid) FROM {comments}
WHERE pid = %d AND status = %d', $pid, COMMENT_PUBLISHED));
108615
  }
1087
108815
  return $cache[$pid];
10890
}
1090
1091
/**
1092
 * Get number of new comments for current user and specified node.
1093
 *
1094
 * @param $nid
1095
 *   Node-id to count comments for.
1096
 * @param $timestamp
1097
 *   Time to count from (defaults to time of last user access
1098
 *   to node).
1099
 * @return The result or FALSE on error.
1100
 */
11012366
function comment_num_new($nid, $timestamp = 0) {
110211
  global $user;
1103
110411
  if ($user->uid) {
1105
    // Retrieve the timestamp at which the current user last viewed this
node.
11067
    if (!$timestamp) {
11073
      $timestamp = node_last_viewed($nid);
11083
    }
11097
    $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp :
NODE_NEW_LIMIT);
1110
1111
    // Use the timestamp to retrieve the number of new comments.
11127
    $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));
1113
11147
    return $result;
11150
  }
1116
  else {
11174
    return FALSE;
1118
  }
1119
11200
}
1121
1122
/**
1123
 * Validate comment data.
1124
 *
1125
 * @param $edit
1126
 *   An associative array containing the comment data.
1127
 * @return
1128
 *   The original $edit.
1129
 */
11302366
function comment_validate($edit) {
113128
  global $user;
1132
1133
  // Invoke other validation handlers.
113428
  comment_invoke_comment($edit, 'validate');
1135
113628
  if (isset($edit['date'])) {
1137
    if (strtotime($edit['date']) === FALSE) {
11380
      form_set_error('date', t('You have to specify a valid date.'));
11390
    }
11400
  }
11410
  if (isset($edit['author']) && !$account = user_load(array('name' =>
$edit['author']))) {
114228
    form_set_error('author', t('You have to specify a valid author.'));
11430
  }
11440
1145
  // Check validity of name, mail and homepage (if given).
1146
  if (!$user->uid || isset($edit['is_anonymous'])) {
114728
    $node = node_load($edit['nid']);
114812
    if (variable_get('comment_anonymous_' . $node->type,
COMMENT_ANONYMOUS_MAYNOT_CONTACT) > COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
114912
      if ($edit['name']) {
11506
        $taken = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE
LOWER(name) = '%s'", $edit['name']));
11516
        if ($taken != 0) {
11526
          form_set_error('name', t('The name you used belongs to a
registered user.'));
11530
        }
11540
      }
11556
      elseif (variable_get('comment_anonymous_' . $node->type,
COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
11560
        form_set_error('name', t('You have to leave your name.'));
11570
      }
11580
1159
      if ($edit['mail']) {
11606
        if (!valid_email_address($edit['mail'])) {
11612
          form_set_error('mail', t('The e-mail address you specified is not
valid.'));
11620
        }
11630
      }
11642
      elseif (variable_get('comment_anonymous_' . $node->type,
COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
11654
        form_set_error('mail', t('You have to leave an e-mail address.'));
11662
      }
11672
1168
      if ($edit['homepage']) {
11696
        if (!valid_url($edit['homepage'], TRUE)) {
11700
          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>.'));
11710
        }
11720
      }
11730
    }
11746
  }
117512
1176
  return $edit;
117728
}
11780
1179
/**
1180
 * Generate the basic commenting form, for appending to a node or display
on a separate page.
1181
 *
1182
 * @param $title
1183
 *   Not used.
1184
 * @ingroup forms
1185
 * @see comment_form_validate()
1186
 * @see comment_form_submit()
1187
 */
1188
function comment_form(&$form_state, $edit, $title = NULL) {
11892366
  global $user;
119048
  $op = isset($_POST['op']) ? $_POST['op'] : '';
119148
  $node = node_load($edit['nid']);
119248
1193
  if (!$user->uid && variable_get('comment_anonymous_' . $node->type,
COMMENT_ANONYMOUS_MAYNOT_CONTACT) != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
119448
    drupal_add_js(drupal_get_path('module', 'comment') . '/comment.js');
119511
  }
119611
  $edit += array('name' => '', 'mail' => '', 'homepage' => '');
119748
1198
  if ($user->uid) {
119948
    if (!empty($edit['cid']) && user_access('administer comments')) {
120028
      if (!empty($edit['author'])) {
12010
        $author = $edit['author'];
12020
      }
12030
      elseif (!empty($edit['name'])) {
12040
        $author = $edit['name'];
12050
      }
12060
      else {
1207
        $author = $edit['registered_name'];
12080
      }
1209
1210
      if (!empty($edit['status'])) {
12110
        $status = $edit['status'];
12120
      }
12130
      else {
1214
        $status = 0;
12150
      }
1216
1217
      if (!empty($edit['date'])) {
12180
        $date = $edit['date'];
12190
      }
12200
      else {
1221
        $date = format_date($edit['timestamp'], 'custom', 'Y-m-d H:i O');
12220
      }
1223
1224
      $form['admin'] = array(
12250
        '#type' => 'fieldset',
12260
        '#title' => t('Administration'),
12270
        '#collapsible' => TRUE,
12280
        '#collapsed' => TRUE,
12290
        '#weight' => -2,
12300
      );
1231
1232
      if ($edit['registered_name'] != '') {
12330
        // The comment is by a registered user.
1234
        $form['admin']['author'] = array(
12350
          '#type' => 'textfield',
12360
          '#title' => t('Authored by'),
12370
          '#size' => 30,
12380
          '#maxlength' => 60,
12390
          '#autocomplete_path' => 'user/autocomplete',
12400
          '#default_value' => $author,
12410
          '#weight' => -1,
12420
        );
1243
      }
12440
      else {
1245
        // The comment is by an anonymous user.
1246
        $form['is_anonymous'] = array(
12470
          '#type' => 'value',
12480
          '#value' => TRUE,
12490
        );
1250
        $form['admin']['name'] = array(
12510
          '#type' => 'textfield',
12520
          '#title' => t('Authored by'),
12530
          '#size' => 30,
12540
          '#maxlength' => 60,
12550
          '#default_value' => $author,
12560
          '#weight' => -1,
12570
        );
1258
        $form['admin']['mail'] = array(
12590
          '#type' => 'textfield',
12600
          '#title' => t('E-mail'),
12610
          '#maxlength' => 64,
12620
          '#size' => 30,
12630
          '#default_value' => $edit['mail'],
12640
          '#description' => t('The content of this field is kept private
and will not be shown publicly.'),
12650
        );
1266
        $form['admin']['homepage'] = array(
12670
          '#type' => 'textfield',
12680
          '#title' => t('Homepage'),
12690
          '#maxlength' => 255,
12700
          '#size' => 30,
12710
          '#default_value' => $edit['homepage'],
12720
        );
1273
      }
1274
      $form['admin']['date'] = array(
12750
        '#type' => 'textfield',
12760
        '#parents' => array('date'),
12770
        '#title' => t('Authored on'),
12780
        '#size' => 20,
12790
        '#maxlength' => 25,
12800
        '#default_value' => $date,
12810
        '#weight' => -1,
12820
      );
1283
      $form['admin']['status'] = array(
12840
        '#type' => 'radios',
12850
        '#parents' => array('status'),
12860
        '#title' => t('Status'),
12870
        '#default_value' =>  $status,
12880
        '#options' => array(t('Published'), t('Not published')),
12890
        '#weight' => -1,
12900
      );
1291
    }
12920
    else {
1293
      $form['_author'] = array(
129428
        '#type' => 'item',
129528
        '#title' => t('Your name'),
129628
        '#markup' => theme('username', $user),
129728
      );
1298
      $form['author'] = array(
129928
        '#type' => 'value',
130028
        '#value' => $user->name,
130128
      );
1302
    }
1303
  }
130428
  elseif (variable_get('comment_anonymous_' . $node->type,
COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MAY_CONTACT) {
130520
    $form['name'] = array(
13064
      '#type' => 'textfield',
13074
      '#title' => t('Your name'),
13084
      '#maxlength' => 60,
13094
      '#size' => 30,
13104
      '#default_value' => $edit['name'] ? $edit['name'] :
variable_get('anonymous', t('Anonymous')),
13114
    );
1312
    $form['mail'] = array(
13134
      '#type' => 'textfield',
13144
      '#title' => t('E-mail'),
13154
      '#maxlength' => 64,
13164
      '#size' => 30,
13174
      '#default_value' => $edit['mail'], '#description' => t('The content
of this field is kept private and will not be shown publicly.'),
13184
    );
1319
    $form['homepage'] = array(
13204
      '#type' => 'textfield',
13214
      '#title' => t('Homepage'),
13224
      '#maxlength' => 255,
13234
      '#size' => 30,
13244
      '#default_value' => $edit['homepage'],
13254
    );
1326
  }
13274
  elseif (variable_get('comment_anonymous_' . $node->type,
COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
132816
    $form['name'] = array(
13297
      '#type' => 'textfield',
13307
      '#title' => t('Your name'),
13317
      '#maxlength' => 60,
13327
      '#size' => 30,
13337
      '#default_value' => $edit['name'] ? $edit['name'] :
variable_get('anonymous', t('Anonymous')),
13347
      '#required' => TRUE,
13357
    );
1336
    $form['mail'] = array(
13377
      '#type' => 'textfield',
13387
      '#title' => t('E-mail'),
13397
      '#maxlength' => 64,
13407
      '#size' => 30,
13417
      '#default_value' => $edit['mail'], '#description' => t('The content
of this field is kept private and will not be shown publicly.'),
13427
      '#required' => TRUE,
13437
    );
1344
    $form['homepage'] = array(
13457
      '#type' => 'textfield',
13467
      '#title' => t('Homepage'),
13477
      '#maxlength' => 255,
13487
      '#size' => 30,
13497
      '#default_value' => $edit['homepage'],
13507
    );
1351
  }
13527
1353
  if (variable_get('comment_subject_field_' . $node->type, 1) == 1) {
135448
    $form['subject'] = array(
135547
      '#type' => 'textfield',
135647
      '#title' => t('Subject'),
135747
      '#maxlength' => 64,
135847
      '#default_value' => !empty($edit['subject']) ? $edit['subject'] : '',
135947
    );
1360
  }
136147
1362
  if (!empty($edit['comment'])) {
136348
    $default = $edit['comment'];
13643
  }
13653
  else {
1366
    $default = '';
136745
  }
1368
1369
  $form['comment'] = array(
137048
    '#type' => 'textarea',
137148
    '#title' => t('Comment'),
137248
    '#rows' => 15,
137348
    '#default_value' => $default,
137448
    '#input_format' => isset($edit['format']) ? $edit['format'] :
FILTER_FORMAT_DEFAULT,
137548
    '#required' => TRUE,
137648
  );
1377
1378
  $form['cid'] = array(
137948
    '#type' => 'value',
138048
    '#value' => !empty($edit['cid']) ? $edit['cid'] : NULL,
138148
  );
1382
  $form['pid'] = array(
138348
    '#type' => 'value',
138448
    '#value' => !empty($edit['pid']) ? $edit['pid'] : NULL,
138548
  );
1386
  $form['nid'] = array(
138748
    '#type' => 'value',
138848
    '#value' => $edit['nid'],
138948
  );
1390
  $form['uid'] = array(
139148
    '#type' => 'value',
139248
    '#value' => !empty($edit['uid']) ? $edit['uid'] : NULL,
139348
  );
1394
1395
  // Only show the save button if comment previews are optional or if we
are
1396
  // already previewing the submission.  However, if there are form errors,
1397
  // we hide the save button no matter what, so that optional form elements
1398
  // (e.g., captchas) can be updated.
1399
  if (!form_get_errors() && ((variable_get('comment_preview_' .
$node->type, COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL) || ($op
== t('Preview')) || ($op == t('Save')))) {
140048
    $form['submit'] = array(
140130
      '#type' => 'submit',
140230
      '#value' => t('Save'),
140330
      '#weight' => 19,
140430
    );
1405
  }
140630
  $form['preview'] = array(
140748
    '#type' => 'button',
140848
    '#value' => t('Preview'),
140948
    '#weight' => 20,
141048
  );
1411
  $form['#token'] = 'comment' . $edit['nid'] . (isset($edit['pid']) ?
$edit['pid'] : '');
141248
1413
  if ($op == t('Preview')) {
141448
    $form['#after_build'] = array('comment_form_add_preview');
141513
  }
141613
1417
  if (empty($edit['cid']) && empty($edit['pid'])) {
141848
    $form['#action'] = url('comment/reply/' . $edit['nid']);
141939
  }
142039
1421
  return $form;
142248
}
14230
1424
/**
1425
 * Theme the comment form box.
1426
 *
1427
 * @param $edit
1428
 *   The form structure.
1429
 * @param $title
1430
 *   The form title.
1431
 */
1432
function comment_form_box($edit, $title = NULL) {
14332366
  return theme('box', $title, drupal_get_form('comment_form', $edit,
$title));
143448
}
14350
1436
/**
1437
 * Form builder; Generate and validate a comment preview form.
1438
 *
1439
 * @ingroup forms
1440
 */
1441
function comment_form_add_preview($form, &$form_state) {
14422366
  global $user;
144313
  $edit = $form_state['values'];
144413
  drupal_set_title(t('Preview comment'), PASS_THROUGH);
144513
  $output = '';
144613
  $node = node_load($edit['nid']);
144713
1448
  // Invoke full validation for the form, to protect against cross site
1449
  // request forgeries (CSRF) and setting arbitrary values for fields such
as
1450
  // the input format. Preview the comment only when form validation does
not
1451
  // set any errors.
1452
  drupal_validate_form($form['form_id']['#value'], $form, $form_state);
145313
  if (!form_get_errors()) {
145413
    _comment_form_submit($edit);
145512
    $comment = (object)$edit;
145612
    $comment->format = $comment->comment_format;
145712
1458
    // Attach the user and time information.
1459
    if (!empty($edit['author'])) {
146012
      $account = user_load(array('name' => $edit['author']));
14617
    }
14627
    elseif ($user->uid && !isset($edit['is_anonymous'])) {
14635
      $account = $user;
14640
    }
14650
1466
    if (!empty($account)) {
146712
      $comment->uid = $account->uid;
14687
      $comment->name = check_plain($account->name);
14697
    }
14707
    elseif (empty($comment->name)) {
14715
      $comment->name = variable_get('anonymous', t('Anonymous'));
14723
    }
14733
1474
    $comment->timestamp = !empty($edit['timestamp']) ? $edit['timestamp'] :
REQUEST_TIME;
147512
    $output .= theme('comment_view', $comment, $node);
147612
  }
147712
1478
  $form['comment_preview'] = array(
147913
    '#markup' => $output,
148013
    '#weight' => -100,
148113
    '#prefix' => '<div class="preview">',
148213
    '#suffix' => '</div>',
148313
  );
1484
1485
  $output = ''; // Isn't this line a duplication of the first $output
above?
148613
1487
  if ($edit['pid']) {
148813
    $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS
registered_name, u.signature, u.picture, u.data FROM {comments} c INNER
JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d',
$edit['pid'], COMMENT_PUBLISHED));
14893
    $comment = drupal_unpack($comment);
14903
    $comment->name = $comment->uid ? $comment->registered_name :
$comment->name;
14913
    $output .= theme('comment_view', $comment, $node);
14923
  }
14933
  else {
1494
    $suffix = empty($form['#suffix']) ? '' : $form['#suffix'];
149510
    $form['#suffix'] = $suffix . node_view($node);
149610
    $edit['pid'] = 0;
149710
  }
1498
1499
  $form['comment_preview_below'] = array(
150013
    '#markup' => $output,
150113
    '#weight' => 100,
150213
  );
1503
1504
  return $form;
150513
}
15060
1507
/**
1508
 * Validate comment form submissions.
1509
 */
1510
function comment_form_validate($form, &$form_state) {
15112366
  global $user;
151228
  if ($user->uid === 0) {
151328
    foreach (array('name', 'homepage', 'mail') as $field) {
151412
      // Set cookie for 365 days.
1515
      if (isset($form_state['values'][$field])) {
151612
        setcookie('comment_info_' . $field, $form_state['values'][$field],
REQUEST_TIME + 31536000, '/');
15176
      }
15186
    }
151912
  }
152012
  comment_validate($form_state['values']);
152128
}
152228
1523
/**
1524
 * Prepare a comment for submission.
1525
 *
1526
 * @param $comment_values
1527
 *   An associative array containing the comment data.
1528
 */
1529
function _comment_form_submit(&$comment_values) {
15302366
  $comment_values += array('subject' => '');
153126
  if (!isset($comment_values['date'])) {
153226
    $comment_values['date'] = 'now';
153326
  }
153426
1535
  $comment_values['timestamp'] = strtotime($comment_values['date']);
153626
  if (isset($comment_values['author'])) {
153726
    $account = user_load(array('name' => $comment_values['author']));
153816
    $comment_values['uid'] = $account->uid;
153916
    $comment_values['name'] = $comment_values['author'];
154016
  }
154116
1542
  // Validate the comment's subject. If not specified, extract from comment
body.
1543
  if (trim($comment_values['subject']) == '') {
154426
    // The body may be in any format, so:
1545
    // 1) Filter it into HTML
1546
    // 2) Strip out all HTML tags
1547
    // 3) Convert entities back to plain-text.
1548
    // Note: format is checked by check_markup().
1549
    $comment_values['subject'] =
trim(truncate_utf8(decode_entities(strip_tags(check_markup($comment_values['comment'],
$comment_values['comment_format']))), 29, TRUE));
15500
    // Edge cases where the comment body is populated only by HTML tags
will
1551
    // require a default subject.
1552
    if ($comment_values['subject'] == '') {
15530
      $comment_values['subject'] = t('(No subject)');
15540
    }
15550
  }
15560
}
155726
1558
/**
1559
 * Process comment form submissions; prepare the comment, store it, and set
a redirection target.
1560
 */
1561
function comment_form_submit($form, &$form_state) {
15622366
  _comment_form_submit($form_state['values']);
156314
  if ($cid = comment_save($form_state['values'])) {
156414
    $node = node_load($form_state['values']['nid']);
156514
    $page = comment_new_page_count($node->comment_count, 1, $node);
156614
    $form_state['redirect'] = array('node/' . $node->nid, $page,
"comment-$cid");
156714
    return;
156814
  }
15690
}
15700
1571
/**
1572
 * Theme a single comment block.
1573
 *
1574
 * @param $comment
1575
 *   The comment object.
1576
 * @param $node
1577
 *   The comment node.
1578
 * @param $links
1579
 *   An associative array containing control links.
1580
 * @param $visible
1581
 *   Switches between folded/unfolded view.
1582
 * @ingroup themeable
1583
 */
1584
function theme_comment_view($comment, $node, $links = array(), $visible =
TRUE) {
15852366
  static $first_new = TRUE;
158638
  $comment->new = node_mark($comment->nid, $comment->timestamp);
158738
  $output = '';
158838
1589
  if ($first_new && $comment->new != MARK_READ) {
159038
    // Assign the anchor only for the first new comment. This avoids
duplicate
1591
    // id attributes on a page.
1592
    $first_new = FALSE;
159319
    $output .= "<a id=\"new\"></a>\n";
159419
  }
159519
1596
  $output .= "<a id=\"comment-$comment->cid\"></a>\n";
159738
1598
  // Switch to folded/unfolded view of the comment.
1599
  if ($visible) {
160038
    $comment->comment = check_markup($comment->comment, $comment->format,
FALSE);
160138
    // Comment API hook.
1602
    comment_invoke_comment($comment, 'view');
160338
    $output .= theme('comment', $comment, $node, $links);
160438
  }
160538
  else {
1606
    $output .= theme('comment_folded', $comment);
16070
  }
1608
1609
  return $output;
161038
}
16110
1612
/**
1613
 * Process variables for comment.tpl.php.
1614
 *
1615
 * @see comment.tpl.php
1616
 * @see theme_comment()
1617
 */
1618
function template_preprocess_comment(&$variables) {
16192366
  $comment = $variables['comment'];
162038
  $node = $variables['node'];
162138
  $variables['author']    = theme('username', $comment);
162238
  $variables['content']   = $comment->comment;
162338
  $variables['date']      = format_date($comment->timestamp);
162438
  $variables['links']     = isset($variables['links']) ? theme('links',
$variables['links']) : '';
162538
  $variables['new']       = $comment->new ? t('new') : '';
162638
  $variables['picture']   =
theme_get_setting('toggle_comment_user_picture') ? theme('user_picture',
$comment) : '';
162738
  $variables['signature'] = $comment->signature;
162838
  $variables['submitted'] = theme('comment_submitted', $comment);
162938
  $variables['title']     = l($comment->subject, $_GET['q'],
array('fragment' => "comment-$comment->cid"));
163038
  $variables['template_files'][] = 'comment-' . $node->type;
163138
  // Set status to a string representation of comment->status.
1632
  if (isset($comment->preview)) {
163338
    $variables['status']  = 'comment-preview';
163412
  }
163512
  else {
1636
    $variables['status']  = ($comment->status == COMMENT_NOT_PUBLISHED) ?
'comment-unpublished' : 'comment-published';
163729
  }
1638
}
163938
1640
/**
1641
 * Process variables for comment-folded.tpl.php.
1642
 *
1643
 * @see comment-folded.tpl.php
1644
 * @see theme_comment_folded()
1645
 */
1646
function template_preprocess_comment_folded(&$variables) {
16472366
  $comment = $variables['comment'];
16480
  $variables['author'] = theme('username', $comment);
16490
  $variables['date']   = format_date($comment->timestamp);
16500
  $variables['new']    = $comment->new ? t('new') : '';
16510
  $variables['title']  = l($comment->subject, comment_node_url() . '/' .
$comment->cid, array('fragment' => "comment-$comment->cid"));
16520
}
16530
1654
/**
1655
 * Theme comment flat collapsed view.
1656
 *
1657
 * @param $comment
1658
 *   The comment to be themed.
1659
 * @param $node
1660
 *   The comment node.
1661
 * @ingroup themeable
1662
 */
1663
function theme_comment_flat_collapsed($comment, $node) {
16642366
  return theme('comment_view', $comment, $node, '', 0);
16650
}
16660
1667
/**
1668
 * Theme comment flat expanded view.
1669
 *
1670
 * @param $comment
1671
 *   The comment to be themed.
1672
 * @param $node
1673
 *   The comment node.
1674
 * @ingroup themeable
1675
 */
1676
function theme_comment_flat_expanded($comment, $node) {
16772366
  return theme('comment_view', $comment, $node, module_invoke_all('link',
'comment', $comment, 0));
16780
}
16790
1680
/**
1681
 * Theme comment thread collapsed view.
1682
 *
1683
 * @param $comment
1684
 *   The comment to be themed.
1685
 * @param $node
1686
 *   The comment node.
1687
 * @ingroup themeable
1688
 */
1689
function theme_comment_thread_collapsed($comment, $node) {
16902366
  return theme('comment_view', $comment, $node, '', 0);
16910
}
16920
1693
/**
1694
 * Theme comment thread expanded view.
1695
 *
1696
 * @param $comment
1697
 *   The comment to be themed.
1698
 * @param $node
1699
 *   The comment node.
1700
 * @ingroup themeable
1701
 */
1702
function theme_comment_thread_expanded($comment, $node) {
17032366
  return theme('comment_view', $comment, $node, module_invoke_all('link',
'comment', $comment, 0));
170421
}
17050
1706
/**
1707
 * Theme a "you can't post comments" notice.
1708
 *
1709
 * @param $node
1710
 *   The comment node.
1711
 * @ingroup themeable
1712
 */
1713
function theme_comment_post_forbidden($node) {
17142366
  global $user;
17154
  static $authenticated_post_comments;
17164
1717
  if (!$user->uid) {
17184
    if (!isset($authenticated_post_comments)) {
17194
      // We only output any link if we are certain, that users get
permission
1720
      // to post comments by logging in. We also locally cache this
information.
1721
      $authenticated_post_comments =
array_key_exists(DRUPAL_AUTHENTICATED_RID, user_roles(TRUE, 'post
comments') + user_roles(TRUE, 'post comments without approval'));
17224
    }
17234
1724
    if ($authenticated_post_comments) {
17254
      // We cannot use drupal_get_destination() because these links
1726
      // sometimes appear on /node and taxonomy listing pages.
1727
      if (variable_get('comment_form_location_' . $node->type,
COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
17284
        $destination = 'destination=' .
drupal_urlencode("comment/reply/$node->nid#comment-form");
17294
      }
17304
      else {
1731
        $destination = 'destination=' .
drupal_urlencode("node/$node->nid#comment-form");
17320
      }
1733
1734
      if (variable_get('user_register', 1)) {
17354
        // Users can register themselves.
1736
        return t('<a href="@login">Login</a> or <a
href="@register">register</a> to post comments', array('@login' =>
url('user/login', array('query' => $destination)), '@register' =>
url('user/register', array('query' => $destination))));
17374
      }
17380
      else {
1739
        // Only admins can add new users, no public registration.
1740
        return t('<a href="@login">Login</a> to post comments',
array('@login' => url('user/login', array('query' => $destination))));
17410
      }
1742
    }
17430
  }
17440
}
17450
1746
/**
1747
 * Process variables for comment-wrapper.tpl.php.
1748
 *
1749
 * @see comment-wrapper.tpl.php
1750
 * @see theme_comment_wrapper()
1751
 */
1752
function template_preprocess_comment_wrapper(&$variables) {
17532366
  // Provide contextual information.
1754
  $variables['display_mode']  = _comment_get_display_setting('mode',
$variables['node']);
17550
  $variables['template_files'][] = 'comment-wrapper-' .
$variables['node']->type;
17560
}
17570
1758
/**
1759
 * Theme a "Submitted by ..." notice.
1760
 *
1761
 * @param $comment
1762
 *   The comment.
1763
 * @ingroup themeable
1764
 */
1765
function theme_comment_submitted($comment) {
17662366
  return t('Submitted by !username on @datetime.',
17670
    array(
1768
      '!username' => theme('username', $comment),
17690
      '@datetime' => format_date($comment->timestamp)
17700
    ));
17710
}
17720
1773
/**
1774
 * Return an array of viewing modes for comment listings.
1775
 *
1776
 * We can't use a global variable array because the locale system
1777
 * is not initialized yet when the comment module is loaded.
1778
 */
1779
function _comment_get_modes() {
17802366
  return array(
1781
    COMMENT_MODE_FLAT_COLLAPSED => t('Flat list - collapsed'),
17823
    COMMENT_MODE_FLAT_EXPANDED => t('Flat list - expanded'),
17833
    COMMENT_MODE_THREADED_COLLAPSED => t('Threaded list - collapsed'),
17843
    COMMENT_MODE_THREADED_EXPANDED => t('Threaded list - expanded')
17853
  );
17863
}
17870
1788
/**
1789
 * Return an array of "comments per page" settings from which the user
1790
 * can choose.
1791
 */
1792
function _comment_per_page() {
17932366
  return drupal_map_assoc(array(10, 30, 50, 70, 90, 150, 200, 250, 300));
17943
}
17950
1796
/**
1797
 * Return a current comment display setting
1798
 *
1799
 * @param $setting
1800
 *   can be one of these: 'mode', 'sort', 'comments_per_page'
1801
 * @param $node
1802
 *   The comment node in question.
1803
 */
1804
function _comment_get_display_setting($setting, $node) {
18052366
  switch ($setting) {
1806
    case 'mode':
1807161
      $value = variable_get('comment_default_mode_' . $node->type,
COMMENT_MODE_THREADED_EXPANDED);
1808161
      break;
1809161
1810
    case 'comments_per_page':
1811161
      $value = variable_get('comment_default_per_page_' . $node->type, 50);
1812161
  }
1813161
1814
  return $value;
1815161
}
18160
1817
/**
1818
 * Updates the comment statistics for a given node. This should be called
any
1819
 * time a comment is added, deleted, or updated.
1820
 *
1821
 * The following fields are contained in the node_comment_statistics table.
1822
 * - last_comment_timestamp: the timestamp of the last comment for this
node or the node create stamp if no comments exist for the node.
1823
 * - last_comment_name: the name of the anonymous poster for the last
comment
1824
 * - 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.
1825
 * - comment_count: the total number of approved/published comments on this
node.
1826
 */
1827
function _comment_update_node_statistics($nid) {
18282366
  $count = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE nid
= %d AND status = %d', $nid, COMMENT_PUBLISHED));
182920
1830
  if ($count > 0) {
183120
    // Comments exist.
1832
    $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));
183318
    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);
183418
  }
183518
  else {
1836
    // Comments do not exist.
1837
    $node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE
nid = %d", $nid));
18382
    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);
18392
  }
1840
}
184120
1842
/**
1843
 * Invoke a hook_comment() operation in all modules.
1844
 *
1845
 * @param &$comment
1846
 *   A comment object.
1847
 * @param $op
1848
 *   A string containing the name of the comment operation.
1849
 * @return
1850
 *   The returned value of the invoked hooks.
1851
 */
1852
function comment_invoke_comment(&$comment, $op) {
18532366
  $return = array();
185458
  foreach (module_implements('comment') as $name) {
185558
    $function = $name . '_comment';
185658
    $result = $function($comment, $op);
185758
    if (isset($result) && is_array($result)) {
185858
      $return = array_merge($return, $result);
18590
    }
18600
    elseif (isset($result)) {
186158
      $return[] = $result;
18620
    }
18630
  }
186458
1865
  return $return;
186658
}
18670
1868
/**
1869
 * Generate vancode.
1870
 *
1871
 * Consists of a leading character indicating length, followed by N digits
1872
 * with a numerical value in base 36. Vancodes can be sorted as strings
1873
 * without messing up numerical order.
1874
 *
1875
 * It goes:
1876
 * 00, 01, 02, ..., 0y, 0z,
1877
 * 110, 111, ... , 1zy, 1zz,
1878
 * 2100, 2101, ..., 2zzy, 2zzz,
1879
 * 31000, 31001, ...
1880
 */
1881
function int2vancode($i = 0) {
18822366
  $num = base_convert((int)$i, 10, 36);
188313
  $length = strlen($num);
188413
1885
  return chr($length + ord('0') - 1) . $num;
188613
}
18870
1888
/**
1889
 * Decode vancode back to an integer.
1890
 */
1891
function vancode2int($c = '00') {
18922366
  return base_convert(substr($c, 1), 36, 10);
189312
}
18940
1895
/**
1896
 * Implementation of hook_hook_info().
1897
 */
1898
function comment_hook_info() {
18992366
  return array(
1900
    'comment' => array(
1901
      'comment' => array(
1902
        'insert' => array(
1903
          'runs when' => t('After saving a new comment'),
190455
        ),
190555
        'update' => array(
1906
          'runs when' => t('After saving an updated comment'),
190755
        ),
190855
        'delete' => array(
1909
          'runs when' => t('After deleting a comment')
191055
        ),
191155
        'view' => array(
1912
          'runs when' => t('When a comment is being viewed by an
authenticated user')
191355
        ),
191455
      ),
191555
    ),
191655
  );
191755
}
19180
1919
/**
1920
 * Implementation of hook_action_info().
1921
 */
1922
function comment_action_info() {
19232366
  return array(
1924
    'comment_unpublish_action' => array(
1925
      'description' => t('Unpublish comment'),
1926215
      'type' => 'comment',
1927215
      'configurable' => FALSE,
1928215
      'hooks' => array(
1929
        'comment' => array('insert', 'update'),
1930215
      )
1931
    ),
1932215
    'comment_unpublish_by_keyword_action' => array(
1933
      'description' => t('Unpublish comment containing keyword(s)'),
1934215
      'type' => 'comment',
1935215
      'configurable' => TRUE,
1936215
      'hooks' => array(
1937
        'comment' => array('insert', 'update'),
1938215
      )
1939
    )
1940215
  );
1941215
}
19420
1943
/**
1944
 * Drupal action to unpublish a comment.
1945
 *
1946
 * @param $context
1947
 *   Keyed array. Must contain the id of the comment if $comment is not
passed.
1948
 * @param $comment
1949
 *   An optional comment object.
1950
 */
1951
function comment_unpublish_action($comment, $context = array()) {
19522366
  if (isset($comment->cid)) {
19530
    $cid = $comment->cid;
19540
    $subject = $comment->subject;
19550
  }
19560
  else {
1957
    $cid = $context['cid'];
19580
    $subject = db_result(db_query("SELECT subject FROM {comments} WHERE cid
= %d", $cid));
19590
  }
1960
  db_query('UPDATE {comments} SET status = %d WHERE cid = %d',
COMMENT_NOT_PUBLISHED, $cid);
19610
  watchdog('action', 'Unpublished comment %subject.', array('%subject' =>
$subject));
19620
}
19630
1964
/**
1965
 * Form builder; Prepare a form for blacklisted keywords.
1966
 *
1967
 * @ingroup forms
1968
 */
1969
function comment_unpublish_by_keyword_action_form($context) {
19702366
  $form['keywords'] = array(
19710
    '#title' => t('Keywords'),
19720
    '#type' => 'textarea',
19730
    '#description' => t('The comment will be unpublished if it contains any
of the character sequences above. Use a comma-separated list of character
sequences. Example: funny, bungee jumping, "Company, Inc." . Character
sequences are case-sensitive.'),
19740
    '#default_value' => isset($context['keywords']) ?
drupal_implode_tags($context['keywords']) : '',
19750
  );
1976
1977
  return $form;
19780
}
19790
1980
/**
1981
 * Process comment_unpublish_by_keyword_action_form form submissions.
1982
 */
1983
function comment_unpublish_by_keyword_action_submit($form, $form_state) {
19842366
  return array('keywords' =>
drupal_explode_tags($form_state['values']['keywords']));
19850
}
19860
1987
/**
1988
 * Implementation of a configurable Drupal action.
1989
 *
1990
 * Unpublish a comment if it contains a certain string.
1991
 *
1992
 * @param $context
1993
 *   An array providing more information about the context of the call to
this action.
1994
 *   Unused here, since this action currently only supports the insert and
update ops of
1995
 *   the comment hook, both of which provide a complete $comment object.
1996
 * @param $comment
1997
 *   A comment object.
1998
 */
1999
function comment_unpublish_by_keyword_action($comment, $context) {
20002366
  foreach ($context['keywords'] as $keyword) {
20010
    if (strstr($comment->comment, $keyword) || strstr($comment->subject,
$keyword)) {
20020
      db_query('UPDATE {comments} SET status = %d WHERE cid = %d',
COMMENT_NOT_PUBLISHED, $comment->cid);
20030
      watchdog('action', 'Unpublished comment %subject.', array('%subject'
=> $comment->subject));
20040
      break;
20050
    }
20060
  }
20070
}
20080
2009
/**
2010
 * Implementation of hook_ranking().
2011
 */
2012
function comment_ranking() {
20132366
  return array(
2014
    'comments' => array(
2015
      'title' => t('Number of comments'),
20167
      'join' => 'LEFT JOIN {node_comment_statistics}
node_comment_statistics ON node_comment_statistics.nid = i.sid',
20177
      // Inverse law that maps the highest reply count on the site to 1 and
0 to 0.
2018
      'score' => '2.0 - 2.0 / (1.0 + node_comment_statistics.comment_count
* %f)',
20197
      'arguments' => array(variable_get('node_cron_comments_scale', 0)),
20207
    ),
20217
  );
20227
}
20230