comparison modules/comment/comment.module @ 1:c1f4ac30525a 6.0

Drupal 6.0
author Franck Deroche <webmaster@defr.org>
date Tue, 23 Dec 2008 14:28:28 +0100
parents
children 2427550111ae
comparison
equal deleted inserted replaced
0:5a113a1c4740 1:c1f4ac30525a
1 <?php
2 // $Id: comment.module,v 1.617 2008/01/25 16:19:12 goba 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 published.
15 */
16 define('COMMENT_PUBLISHED', 0);
17
18 /**
19 * Comment is awaiting approval.
20 */
21 define('COMMENT_NOT_PUBLISHED', 1);
22
23 /**
24 * Comments are displayed in a flat list - collapsed.
25 */
26 define('COMMENT_MODE_FLAT_COLLAPSED', 1);
27
28 /**
29 * Comments are displayed in a flat list - expanded.
30 */
31 define('COMMENT_MODE_FLAT_EXPANDED', 2);
32
33 /**
34 * Comments are displayed as a threaded list - collapsed.
35 */
36 define('COMMENT_MODE_THREADED_COLLAPSED', 3);
37
38 /**
39 * Comments are displayed as a threaded list - expanded.
40 */
41 define('COMMENT_MODE_THREADED_EXPANDED', 4);
42
43 /**
44 * Comments are ordered by date - newest first.
45 */
46 define('COMMENT_ORDER_NEWEST_FIRST', 1);
47
48 /**
49 * Comments are ordered by date - oldest first.
50 */
51 define('COMMENT_ORDER_OLDEST_FIRST', 2);
52
53 /**
54 * Comment controls should be shown above the comment list.
55 */
56 define('COMMENT_CONTROLS_ABOVE', 0);
57
58 /**
59 * Comment controls should be shown below the comment list.
60 */
61 define('COMMENT_CONTROLS_BELOW', 1);
62
63 /**
64 * Comment controls should be shown both above and below the comment list.
65 */
66 define('COMMENT_CONTROLS_ABOVE_BELOW', 2);
67
68 /**
69 * Comment controls are hidden.
70 */
71 define('COMMENT_CONTROLS_HIDDEN', 3);
72
73 /**
74 * Anonymous posters may not enter their contact information.
75 */
76 define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);
77
78 /**
79 * Anonymous posters may leave their contact information.
80 */
81 define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);
82
83 /**
84 * Anonymous posters must leave their contact information.
85 */
86 define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);
87
88 /**
89 * Comment form should be displayed on a separate page.
90 */
91 define('COMMENT_FORM_SEPARATE_PAGE', 0);
92
93 /**
94 * Comment form should be shown below post or list of comments.
95 */
96 define('COMMENT_FORM_BELOW', 1);
97
98 /**
99 * Comments for this node are disabled.
100 */
101 define('COMMENT_NODE_DISABLED', 0);
102
103 /**
104 * Comments for this node are locked.
105 */
106 define('COMMENT_NODE_READ_ONLY', 1);
107
108 /**
109 * Comments are enabled on this node.
110 */
111 define('COMMENT_NODE_READ_WRITE', 2);
112
113 /**
114 * Comment preview is optional.
115 */
116 define('COMMENT_PREVIEW_OPTIONAL', 0);
117
118 /**
119 * Comment preview is required.
120 */
121 define('COMMENT_PREVIEW_REQUIRED', 1);
122
123 /**
124 * Implementation of hook_help().
125 */
126 function comment_help($path, $arg) {
127 switch ($path) {
128 case 'admin/help#comment':
129 $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 (some display settings are customizable by individual users).', array('@content-type' => url('admin/content/types'))) .'</p>';
130 $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>';
131 $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>';
132 return $output;
133 case 'admin/content/comment':
134 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>';
135 case 'admin/content/comment/approval':
136 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>';
137 }
138 }
139
140 /**
141 * Implementation of hook_theme().
142 */
143 function comment_theme() {
144 return array(
145 'comment_block' => array(
146 'arguments' => array(),
147 ),
148 'comment_admin_overview' => array(
149 'arguments' => array('form' => NULL),
150 ),
151 'comment_preview' => array(
152 'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
153 ),
154 'comment_view' => array(
155 'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
156 ),
157 'comment_controls' => array(
158 'arguments' => array('form' => NULL),
159 ),
160 'comment' => array(
161 'template' => 'comment',
162 'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array()),
163 ),
164 'comment_folded' => array(
165 'template' => 'comment-folded',
166 'arguments' => array('comment' => NULL),
167 ),
168 'comment_flat_collapsed' => array(
169 'arguments' => array('comment' => NULL, 'node' => NULL),
170 ),
171 'comment_flat_expanded' => array(
172 'arguments' => array('comment' => NULL, 'node' => NULL),
173 ),
174 'comment_thread_collapsed' => array(
175 'arguments' => array('comment' => NULL, 'node' => NULL),
176 ),
177 'comment_thread_expanded' => array(
178 'arguments' => array('comment' => NULL, 'node' => NULL),
179 ),
180 'comment_post_forbidden' => array(
181 'arguments' => array('nid' => NULL),
182 ),
183 'comment_wrapper' => array(
184 'template' => 'comment-wrapper',
185 'arguments' => array('content' => NULL, 'node' => NULL),
186 ),
187 'comment_submitted' => array(
188 'arguments' => array('comment' => NULL),
189 ),
190 );
191 }
192
193 /**
194 * Implementation of hook_menu().
195 */
196 function comment_menu() {
197 $items['admin/content/comment'] = array(
198 'title' => 'Comments',
199 'description' => 'List and edit site comments and the comment moderation queue.',
200 'page callback' => 'comment_admin',
201 'access arguments' => array('administer comments'),
202 'file' => 'comment.admin.inc',
203 );
204
205 // Tabs:
206 $items['admin/content/comment/new'] = array(
207 'title' => 'Published comments',
208 'type' => MENU_DEFAULT_LOCAL_TASK,
209 'weight' => -10,
210 );
211 $items['admin/content/comment/approval'] = array(
212 'title' => 'Approval queue',
213 'page arguments' => array('approval'),
214 'type' => MENU_LOCAL_TASK,
215 'file' => 'comment.admin.inc',
216 );
217
218 $items['comment/delete'] = array(
219 'title' => 'Delete comment',
220 'page callback' => 'comment_delete',
221 'access arguments' => array('administer comments'),
222 'type' => MENU_CALLBACK,
223 'file' => 'comment.admin.inc',
224 );
225
226 $items['comment/edit'] = array(
227 'title' => 'Edit comment',
228 'page callback' => 'comment_edit',
229 'access arguments' => array('post comments'),
230 'type' => MENU_CALLBACK,
231 'file' => 'comment.pages.inc',
232 );
233 $items['comment/reply/%node'] = array(
234 'title' => 'Reply to comment',
235 'page callback' => 'comment_reply',
236 'page arguments' => array(2),
237 'access callback' => 'node_access',
238 'access arguments' => array('view', 2),
239 'type' => MENU_CALLBACK,
240 'file' => 'comment.pages.inc',
241 );
242
243 return $items;
244 }
245
246 /**
247 * Implementation of hook_node_type().
248 */
249 function comment_node_type($op, $info) {
250 $settings = array(
251 'comment',
252 'comment_default_mode',
253 'comment_default_order',
254 'comment_default_per_page',
255 'comment_controls',
256 'comment_anonymous',
257 'comment_subject_field',
258 'comment_preview',
259 'comment_form_location',
260 );
261 switch ($op) {
262 case 'delete':
263 foreach ($settings as $setting) {
264 variable_del($setting .'_'. $info->type);
265 }
266 break;
267 }
268 }
269
270 /**
271 * Implementation of hook_perm().
272 */
273 function comment_perm() {
274 return array('access comments', 'post comments', 'administer comments', 'post comments without approval');
275 }
276
277 /**
278 * Implementation of hook_block().
279 *
280 * Generates a block with the most recent comments.
281 */
282 function comment_block($op = 'list', $delta = 0) {
283 if ($op == 'list') {
284 $blocks[0]['info'] = t('Recent comments');
285 return $blocks;
286 }
287 else if ($op == 'view' && user_access('access comments')) {
288 $block['subject'] = t('Recent comments');
289 $block['content'] = theme('comment_block');
290 return $block;
291 }
292 }
293
294 /**
295 * Find a number of recent comments. This is done in two steps.
296 * 1. Find the n (specified by $number) nodes that have the most recent
297 * comments. This is done by querying node_comment_statistics which has
298 * an index on last_comment_timestamp, and is thus a fast query.
299 * 2. Loading the information from the comments table based on the nids found
300 * in step 1.
301 *
302 * @param $number
303 * (optional) The maximum number of comments to find.
304 * @return
305 * An array of comment objects each containing a nid,
306 * subject, cid, and timestamp, or an empty array if there are no recent
307 * comments visible to the current user.
308 */
309 function comment_get_recent($number = 10) {
310 // Select the $number nodes (visible to the current user) with the most
311 // recent comments. This is efficient due to the index on
312 // last_comment_timestamp.
313 $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);
314
315 $nids = array();
316 while ($row = db_fetch_object($result)) {
317 $nids[] = $row->nid;
318 }
319
320 $comments = array();
321 if (!empty($nids)) {
322 // From among the comments on the nodes selected in the first query,
323 // find the $number most recent comments.
324 $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);
325 while ($comment = db_fetch_object($result)) {
326 $comments[] = $comment;
327 }
328 }
329
330 return $comments;
331 }
332
333 /**
334 * Calculate page number for first new comment.
335 *
336 * @param $num_comments
337 * Number of comments.
338 * @param $new_replies
339 * Number of new replies.
340 * @param $node
341 * The first new comment node.
342 * @return
343 * "page=X" if the page number is greater than zero; empty string otherwise.
344 */
345 function comment_new_page_count($num_comments, $new_replies, $node) {
346 $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
347 $mode = _comment_get_display_setting('mode', $node);
348 $order = _comment_get_display_setting('sort', $node);
349 $pagenum = NULL;
350 $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
351 if ($num_comments <= $comments_per_page || ($flat && $order == COMMENT_ORDER_NEWEST_FIRST)) {
352 // Only one page of comments or flat forum and newest first.
353 // First new comment will always be on first page.
354 $pageno = 0;
355 }
356 else {
357 if ($flat) {
358 // Flat comments and oldest first.
359 $count = $num_comments - $new_replies;
360 }
361 else {
362 // Threaded comments. See the documentation for comment_render().
363 if ($order == COMMENT_ORDER_NEWEST_FIRST) {
364 // Newest first: find the last thread with new comment
365 $result = db_query('(SELECT thread FROM {comments} WHERE nid = %d AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1', $node->nid, $new_replies);
366 $thread = db_result($result);
367 $result_count = db_query("SELECT COUNT(*) FROM {comments} WHERE nid = %d AND status = 0 AND thread > '". $thread ."'", $node->nid);
368 }
369 else {
370 // Oldest first: find the first thread with new comment
371 $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);
372 $thread = substr(db_result($result), 0, -1);
373 $result_count = db_query("SELECT COUNT(*) FROM {comments} WHERE nid = %d AND status = 0 AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < '". $thread ."'", $node->nid);
374 }
375 $count = db_result($result_count);
376 }
377 $pageno = $count / $comments_per_page;
378 }
379 if ($pageno >= 1) {
380 $pagenum = "page=". intval($pageno);
381 }
382 return $pagenum;
383 }
384
385 /**
386 * Returns a formatted list of recent comments to be displayed in the comment block.
387 *
388 * @return
389 * The comment list HTML.
390 * @ingroup themeable
391 */
392 function theme_comment_block() {
393 $items = array();
394 foreach (comment_get_recent() as $comment) {
395 $items[] = l($comment->subject, 'node/'. $comment->nid, array('fragment' => 'comment-'. $comment->cid)) .'<br />'. t('@time ago', array('@time' => format_interval(time() - $comment->timestamp)));
396 }
397 if ($items) {
398 return theme('item_list', $items);
399 }
400 }
401
402 /**
403 * Implementation of hook_link().
404 */
405 function comment_link($type, $node = NULL, $teaser = FALSE) {
406 $links = array();
407
408 if ($type == 'node' && $node->comment) {
409
410 if ($teaser) {
411 // Main page: display the number of comments that have been posted.
412
413 if (user_access('access comments')) {
414 $all = comment_num_all($node->nid);
415
416 if ($all) {
417 $links['comment_comments'] = array(
418 'title' => format_plural($all, '1 comment', '@count comments'),
419 'href' => "node/$node->nid",
420 'attributes' => array('title' => t('Jump to the first comment of this posting.')),
421 'fragment' => 'comments'
422 );
423
424 $new = comment_num_new($node->nid);
425
426 if ($new) {
427 $links['comment_new_comments'] = array(
428 'title' => format_plural($new, '1 new comment', '@count new comments'),
429 'href' => "node/$node->nid",
430 'query' => comment_new_page_count($all, $new, $node),
431 'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
432 'fragment' => 'new'
433 );
434 }
435 }
436 else {
437 if ($node->comment == COMMENT_NODE_READ_WRITE) {
438 if (user_access('post comments')) {
439 $links['comment_add'] = array(
440 'title' => t('Add new comment'),
441 'href' => "comment/reply/$node->nid",
442 'attributes' => array('title' => t('Add a new comment to this page.')),
443 'fragment' => 'comment-form'
444 );
445 }
446 else {
447 $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
448 }
449 }
450 }
451 }
452 }
453 else {
454 // Node page: add a "post comment" link if the user is allowed to
455 // post comments, if this node is not read-only, and if the comment form isn't already shown
456
457 if ($node->comment == COMMENT_NODE_READ_WRITE) {
458 if (user_access('post comments')) {
459 if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
460 $links['comment_add'] = array(
461 'title' => t('Add new comment'),
462 'href' => "comment/reply/$node->nid",
463 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
464 'fragment' => 'comment-form'
465 );
466 }
467 }
468 else {
469 $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
470 }
471 }
472 }
473 }
474
475 if ($type == 'comment') {
476 $links = comment_links($node, $teaser);
477 }
478 if (isset($links['comment_forbidden'])) {
479 $links['comment_forbidden']['html'] = TRUE;
480 }
481
482 return $links;
483 }
484
485 /**
486 * Implementation of hook_form_alter().
487 */
488 function comment_form_alter(&$form, $form_state, $form_id) {
489 if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
490 $form['comment'] = array(
491 '#type' => 'fieldset',
492 '#title' => t('Comment settings'),
493 '#collapsible' => TRUE,
494 '#collapsed' => TRUE,
495 );
496 $form['comment']['comment'] = array(
497 '#type' => 'radios',
498 '#title' => t('Default comment setting'),
499 '#default_value' => variable_get('comment_'. $form['#node_type']->type, COMMENT_NODE_READ_WRITE),
500 '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
501 '#description' => t('Users with the <em>administer comments</em> permission will be able to override this setting.'),
502 );
503 $form['comment']['comment_default_mode'] = array(
504 '#type' => 'radios',
505 '#title' => t('Default display mode'),
506 '#default_value' => variable_get('comment_default_mode_'. $form['#node_type']->type, COMMENT_MODE_THREADED_EXPANDED),
507 '#options' => _comment_get_modes(),
508 '#description' => t('The default view for comments. Expanded views display the body of the comment. Threaded views keep replies together.'),
509 );
510 $form['comment']['comment_default_order'] = array(
511 '#type' => 'radios',
512 '#title' => t('Default display order'),
513 '#default_value' => variable_get('comment_default_order_'. $form['#node_type']->type, COMMENT_ORDER_NEWEST_FIRST),
514 '#options' => _comment_get_orders(),
515 '#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.'),
516 );
517 $form['comment']['comment_default_per_page'] = array(
518 '#type' => 'select',
519 '#title' => t('Default comments per page'),
520 '#default_value' => variable_get('comment_default_per_page_'. $form['#node_type']->type, 50),
521 '#options' => _comment_per_page(),
522 '#description' => t('Default number of comments for each page: more comments are distributed in several pages.'),
523 );
524 $form['comment']['comment_controls'] = array(
525 '#type' => 'radios',
526 '#title' => t('Comment controls'),
527 '#default_value' => variable_get('comment_controls_'. $form['#node_type']->type, COMMENT_CONTROLS_HIDDEN),
528 '#options' => array(
529 t('Display above the comments'),
530 t('Display below the comments'),
531 t('Display above and below the comments'),
532 t('Do not display')),
533 '#description' => t('Position of the comment controls box. The comment controls let the user change the default display mode and display order of comments.'),
534 );
535 $form['comment']['comment_anonymous'] = array(
536 '#type' => 'radios',
537 '#title' => t('Anonymous commenting'),
538 '#default_value' => variable_get('comment_anonymous_'. $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT),
539 '#options' => array(
540 COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'),
541 COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'),
542 COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information')),
543 '#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')))),
544 );
545 if (!user_access('post comments', drupal_anonymous_user())) {
546 $form['comment']['comment_anonymous']['#disabled'] = TRUE;
547 }
548 $form['comment']['comment_subject_field'] = array(
549 '#type' => 'radios',
550 '#title' => t('Comment subject field'),
551 '#default_value' => variable_get('comment_subject_field_'. $form['#node_type']->type, 1),
552 '#options' => array(t('Disabled'), t('Enabled')),
553 '#description' => t('Can users provide a unique subject for their comments?'),
554 );
555 $form['comment']['comment_preview'] = array(
556 '#type' => 'radios',
557 '#title' => t('Preview comment'),
558 '#default_value' => variable_get('comment_preview_'. $form['#node_type']->type, COMMENT_PREVIEW_REQUIRED),
559 '#options' => array(t('Optional'), t('Required')),
560 '#description' => t("Forces a user to look at their comment by clicking on a 'Preview' button before they can actually add the comment"),
561 );
562 $form['comment']['comment_form_location'] = array(
563 '#type' => 'radios',
564 '#title' => t('Location of comment submission form'),
565 '#default_value' => variable_get('comment_form_location_'. $form['#node_type']->type, COMMENT_FORM_SEPARATE_PAGE),
566 '#options' => array(t('Display on separate page'), t('Display below post or comments')),
567 );
568 }
569 elseif (isset($form['type']) && isset($form['#node'])) {
570 if ($form['type']['#value'] .'_node_form' == $form_id) {
571 $node = $form['#node'];
572 $form['comment_settings'] = array(
573 '#type' => 'fieldset',
574 '#access' => user_access('administer comments'),
575 '#title' => t('Comment settings'),
576 '#collapsible' => TRUE,
577 '#collapsed' => TRUE,
578 '#weight' => 30,
579 );
580 $form['comment_settings']['comment'] = array(
581 '#type' => 'radios',
582 '#parents' => array('comment'),
583 '#default_value' => $node->comment,
584 '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
585 );
586 }
587 }
588 }
589
590 /**
591 * Implementation of hook_nodeapi().
592 */
593 function comment_nodeapi(&$node, $op, $arg = 0) {
594 switch ($op) {
595 case 'load':
596 return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid));
597 break;
598
599 case 'prepare':
600 if (!isset($node->comment)) {
601 $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
602 }
603 break;
604
605 case 'insert':
606 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);
607 break;
608
609 case 'delete':
610 db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
611 db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
612 break;
613
614 case 'update index':
615 $text = '';
616 $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED);
617 while ($comment = db_fetch_object($comments)) {
618 $text .= '<h2>'. check_plain($comment->subject) .'</h2>'. check_markup($comment->comment, $comment->format, FALSE);
619 }
620 return $text;
621
622 case 'search result':
623 $comments = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $node->nid));
624 return format_plural($comments, '1 comment', '@count comments');
625
626 case 'rss item':
627 if ($node->comment != COMMENT_NODE_DISABLED) {
628 return array(array('key' => 'comments', 'value' => url('node/'. $node->nid, array('fragment' => 'comments', 'absolute' => TRUE))));
629 }
630 else {
631 return array();
632 }
633 }
634 }
635
636 /**
637 * Implementation of hook_user().
638 */
639 function comment_user($type, $edit, &$user, $category = NULL) {
640 if ($type == 'delete') {
641 db_query('UPDATE {comments} SET uid = 0 WHERE uid = %d', $user->uid);
642 db_query('UPDATE {node_comment_statistics} SET last_comment_uid = 0 WHERE last_comment_uid = %d', $user->uid);
643 }
644 }
645
646 /**
647 * This is *not* a hook_access() implementation. This function is called
648 * to determine whether the current user has access to a particular comment.
649 *
650 * Authenticated users can edit their comments as long they have not been
651 * replied to. This prevents people from changing or revising their
652 * statements based on the replies to their posts.
653 *
654 * @param $op
655 * The operation that is to be performed on the comment. Only 'edit' is recognized now.
656 * @param $comment
657 * The comment object.
658 * @return
659 * TRUE if the current user has acces to the comment, FALSE otherwise.
660 */
661 function comment_access($op, $comment) {
662 global $user;
663
664 if ($op == 'edit') {
665 return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments');
666 }
667 }
668
669 /**
670 * A simple helper function.
671 *
672 * @return
673 * The 0th and the 1st path components joined by a slash.
674 */
675 function comment_node_url() {
676 return arg(0) .'/'. arg(1);
677 }
678
679 /**
680 * Accepts a submission of new or changed comment content.
681 *
682 * @param $edit
683 * A comment array.
684 *
685 * @return
686 * If the comment is successfully saved the comment ID is returned. If the comment
687 * is not saved, FALSE is returned.
688 */
689 function comment_save($edit) {
690 global $user;
691 if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
692 if (!form_get_errors()) {
693 $edit += array(
694 'mail' => '',
695 'homepage' => '',
696 'name' => '',
697 'status' => user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED,
698 );
699 if ($edit['cid']) {
700 // Update the comment in the database.
701 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']);
702
703 // Allow modules to respond to the updating of a comment.
704 comment_invoke_comment($edit, 'update');
705
706 // Add an entry to the watchdog log.
707 watchdog('content', 'Comment: updated %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], array('fragment' => 'comment-'. $edit['cid'])));
708 }
709 else {
710 // Add the comment to database.
711 // Here we are building the thread field. See the documentation for
712 // comment_render().
713 if ($edit['pid'] == 0) {
714 // This is a comment with no parent comment (depth 0): we start
715 // by retrieving the maximum thread level.
716 $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $edit['nid']));
717
718 // Strip the "/" from the end of the thread.
719 $max = rtrim($max, '/');
720
721 // Finally, build the thread field for this new comment.
722 $thread = int2vancode(vancode2int($max) + 1) .'/';
723 }
724 else {
725 // This is comment with a parent comment: we increase
726 // the part of the thread value at the proper depth.
727
728 // Get the parent comment:
729 $parent = _comment_load($edit['pid']);
730
731 // Strip the "/" from the end of the parent thread.
732 $parent->thread = (string) rtrim((string) $parent->thread, '/');
733
734 // Get the max value in _this_ thread.
735 $max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $edit['nid']));
736
737 if ($max == '') {
738 // First child of this parent.
739 $thread = $parent->thread .'.'. int2vancode(0) .'/';
740 }
741 else {
742 // Strip the "/" at the end of the thread.
743 $max = rtrim($max, '/');
744
745 // We need to get the value at the correct depth.
746 $parts = explode('.', $max);
747 $parent_depth = count(explode('.', $parent->thread));
748 $last = $parts[$parent_depth];
749
750 // Finally, build the thread field for this new comment.
751 $thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/';
752 }
753 }
754
755 $edit['timestamp'] = time();
756
757 if ($edit['uid'] === $user->uid) { // '===' because we want to modify anonymous users too
758 $edit['name'] = $user->name;
759 }
760
761 db_query("INSERT INTO {comments} (nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $edit['nid'], $edit['pid'], $edit['uid'], $edit['subject'], $edit['comment'], $edit['format'], ip_address(), $edit['timestamp'], $edit['status'], $thread, $edit['name'], $edit['mail'], $edit['homepage']);
762 $edit['cid'] = db_last_insert_id('comments', 'cid');
763
764 // Tell the other modules a new comment has been submitted.
765 comment_invoke_comment($edit, 'insert');
766
767 // Add an entry to the watchdog log.
768 watchdog('content', 'Comment: added %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], array('fragment' => 'comment-'. $edit['cid'])));
769 }
770 _comment_update_node_statistics($edit['nid']);
771
772 // Clear the cache so an anonymous user can see his comment being added.
773 cache_clear_all();
774
775 // Explain the approval queue if necessary, and then
776 // redirect the user to the node he's commenting on.
777 if ($edit['status'] == COMMENT_NOT_PUBLISHED) {
778 drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
779 }
780 else {
781 comment_invoke_comment($edit, 'publish');
782 }
783 return $edit['cid'];
784 }
785 else {
786 return FALSE;
787 }
788 }
789 else {
790 watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject']), WATCHDOG_WARNING);
791 drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject'])), 'error');
792 return FALSE;
793 }
794 }
795
796 /**
797 * Build command links for a comment (e.g.\ edit, reply, delete) with respect to the current user's access permissions.
798 *
799 * @param $comment
800 * The comment to which the links will be related.
801 * @param $return
802 * Not used.
803 * @return
804 * An associative array containing the links.
805 */
806 function comment_links($comment, $return = 1) {
807 global $user;
808
809 $links = array();
810
811 // If we are viewing just this comment, we link back to the node.
812 if ($return) {
813 $links['comment_parent'] = array(
814 'title' => t('parent'),
815 'href' => comment_node_url(),
816 'fragment' => "comment-$comment->cid"
817 );
818 }
819
820 if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
821 if (user_access('administer comments') && user_access('post comments')) {
822 $links['comment_delete'] = array(
823 'title' => t('delete'),
824 'href' => "comment/delete/$comment->cid"
825 );
826 $links['comment_edit'] = array(
827 'title' => t('edit'),
828 'href' => "comment/edit/$comment->cid"
829 );
830 $links['comment_reply'] = array(
831 'title' => t('reply'),
832 'href' => "comment/reply/$comment->nid/$comment->cid"
833 );
834 }
835 else if (user_access('post comments')) {
836 if (comment_access('edit', $comment)) {
837 $links['comment_edit'] = array(
838 'title' => t('edit'),
839 'href' => "comment/edit/$comment->cid"
840 );
841 }
842 $links['comment_reply'] = array(
843 'title' => t('reply'),
844 'href' => "comment/reply/$comment->nid/$comment->cid"
845 );
846 }
847 else {
848 $node = node_load($comment->nid);
849 $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
850 }
851 }
852
853 return $links;
854 }
855
856 /**
857 * Renders comment(s).
858 *
859 * @param $node
860 * The node which comment(s) needs rendering.
861 * @param $cid
862 * Optional, if given, only one comment is rendered.
863 *
864 * To display threaded comments in the correct order we keep a 'thread' field
865 * and order by that value. This field keeps this data in
866 * a way which is easy to update and convenient to use.
867 *
868 * A "thread" value starts at "1". If we add a child (A) to this comment,
869 * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
870 * brother of (A) will get "1.2". Next brother of the parent of (A) will get
871 * "2" and so on.
872 *
873 * First of all note that the thread field stores the depth of the comment:
874 * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
875 *
876 * Now to get the ordering right, consider this example:
877 *
878 * 1
879 * 1.1
880 * 1.1.1
881 * 1.2
882 * 2
883 *
884 * If we "ORDER BY thread ASC" we get the above result, and this is the
885 * natural order sorted by time. However, if we "ORDER BY thread DESC"
886 * we get:
887 *
888 * 2
889 * 1.2
890 * 1.1.1
891 * 1.1
892 * 1
893 *
894 * Clearly, this is not a natural way to see a thread, and users will get
895 * confused. The natural order to show a thread by time desc would be:
896 *
897 * 2
898 * 1
899 * 1.2
900 * 1.1
901 * 1.1.1
902 *
903 * which is what we already did before the standard pager patch. To achieve
904 * this we simply add a "/" at the end of each "thread" value. This way out
905 * thread fields will look like depicted below:
906 *
907 * 1/
908 * 1.1/
909 * 1.1.1/
910 * 1.2/
911 * 2/
912 *
913 * we add "/" since this char is, in ASCII, higher than every number, so if
914 * now we "ORDER BY thread DESC" we get the correct order. However this would
915 * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
916 * to consider the trailing "/" so we use a substring only.
917 */
918 function comment_render($node, $cid = 0) {
919 global $user;
920
921 $output = '';
922
923 if (user_access('access comments')) {
924 // Pre-process variables.
925 $nid = $node->nid;
926 if (empty($nid)) {
927 $nid = 0;
928 }
929
930 $mode = _comment_get_display_setting('mode', $node);
931 $order = _comment_get_display_setting('sort', $node);
932 $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
933
934 if ($cid && is_numeric($cid)) {
935 // Single comment view.
936 $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';
937 $query_args = array($cid);
938 if (!user_access('administer comments')) {
939 $query .= ' AND c.status = %d';
940 $query_args[] = COMMENT_PUBLISHED;
941 }
942
943 $query = db_rewrite_sql($query, 'c', 'cid');
944 $result = db_query($query, $query_args);
945
946 if ($comment = db_fetch_object($result)) {
947 $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
948 $links = module_invoke_all('link', 'comment', $comment, 1);
949 drupal_alter('link', $links, $node);
950
951 $output .= theme('comment_view', $comment, $node, $links);
952 }
953 }
954 else {
955 // Multiple comment view
956 $query_count = 'SELECT COUNT(*) FROM {comments} WHERE nid = %d';
957 $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';
958
959 $query_args = array($nid);
960 if (!user_access('administer comments')) {
961 $query .= ' AND c.status = %d';
962 $query_count .= ' AND status = %d';
963 $query_args[] = COMMENT_PUBLISHED;
964 }
965
966 if ($order == COMMENT_ORDER_NEWEST_FIRST) {
967 if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
968 $query .= ' ORDER BY c.cid DESC';
969 }
970 else {
971 $query .= ' ORDER BY c.thread DESC';
972 }
973 }
974 else if ($order == COMMENT_ORDER_OLDEST_FIRST) {
975 if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
976 $query .= ' ORDER BY c.cid';
977 }
978 else {
979 // See comment above. Analysis reveals that this doesn't cost too
980 // much. It scales much much better than having the whole comment
981 // structure.
982 $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))';
983 }
984 }
985 $query = db_rewrite_sql($query, 'c', 'cid');
986 $query_count = db_rewrite_sql($query_count, 'c', 'cid');
987
988 // Start a form, for use with comment control.
989 $result = pager_query($query, $comments_per_page, 0, $query_count, $query_args);
990
991 $divs = 0;
992 $num_rows = FALSE;
993 $comments = '';
994 drupal_add_css(drupal_get_path('module', 'comment') .'/comment.css');
995 while ($comment = db_fetch_object($result)) {
996 $comment = drupal_unpack($comment);
997 $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
998 $comment->depth = count(explode('.', $comment->thread)) - 1;
999
1000 if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) {
1001 if ($comment->depth > $divs) {
1002 $divs++;
1003 $comments .= '<div class="indented">';
1004 }
1005 else {
1006 while ($comment->depth < $divs) {
1007 $divs--;
1008 $comments .= '</div>';
1009 }
1010 }
1011 }
1012
1013 if ($mode == COMMENT_MODE_FLAT_COLLAPSED) {
1014 $comments .= theme('comment_flat_collapsed', $comment, $node);
1015 }
1016 else if ($mode == COMMENT_MODE_FLAT_EXPANDED) {
1017 $comments .= theme('comment_flat_expanded', $comment, $node);
1018 }
1019 else if ($mode == COMMENT_MODE_THREADED_COLLAPSED) {
1020 $comments .= theme('comment_thread_collapsed', $comment, $node);
1021 }
1022 else if ($mode == COMMENT_MODE_THREADED_EXPANDED) {
1023 $comments .= theme('comment_thread_expanded', $comment, $node);
1024 }
1025
1026 $num_rows = TRUE;
1027 }
1028 while ($divs-- > 0) {
1029 $comments .= '</div>';
1030 }
1031
1032 $comment_controls = variable_get('comment_controls_'. $node->type, COMMENT_CONTROLS_HIDDEN);
1033 if ($num_rows && ($comment_controls == COMMENT_CONTROLS_ABOVE || $comment_controls == COMMENT_CONTROLS_ABOVE_BELOW)) {
1034 $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page);
1035 }
1036
1037 $output .= $comments;
1038 $output .= theme('pager', NULL, $comments_per_page, 0);
1039
1040 if ($num_rows && ($comment_controls == COMMENT_CONTROLS_BELOW || $comment_controls == COMMENT_CONTROLS_ABOVE_BELOW)) {
1041 $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page);
1042 }
1043 }
1044
1045 // If enabled, show new comment form if it's not already being displayed.
1046 $reply = arg(0) == 'comment' && arg(1) == 'reply';
1047 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) {
1048 $output .= comment_form_box(array('nid' => $nid), t('Post new comment'));
1049 }
1050
1051 $output = theme('comment_wrapper', $output, $node);
1052 }
1053
1054 return $output;
1055 }
1056
1057 /**
1058 * Comment operations. We offer different update operations depending on
1059 * which comment administration page we're on.
1060 *
1061 * @param $action
1062 * The comment administration page.
1063 * @return
1064 * An associative array containing the offered operations.
1065 */
1066 function comment_operations($action = NULL) {
1067 if ($action == 'publish') {
1068 $operations = array(
1069 'publish' => array(t('Publish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_PUBLISHED .' WHERE cid = %d'),
1070 'delete' => array(t('Delete the selected comments'), '')
1071 );
1072 }
1073 else if ($action == 'unpublish') {
1074 $operations = array(
1075 'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d'),
1076 'delete' => array(t('Delete the selected comments'), '')
1077 );
1078 }
1079 else {
1080 $operations = array(
1081 'publish' => array(t('Publish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_PUBLISHED .' WHERE cid = %d'),
1082 'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d'),
1083 'delete' => array(t('Delete the selected comments'), '')
1084 );
1085 }
1086 return $operations;
1087 }
1088
1089 /**
1090 * Misc functions: helpers, privates, history
1091 */
1092
1093 /**
1094 * Load the entire comment by cid.
1095 *
1096 * @param $cid
1097 * The identifying comment id.
1098 * @return
1099 * The comment object.
1100 */
1101 function _comment_load($cid) {
1102 return db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $cid));
1103 }
1104
1105 /**
1106 * Get comment count for a node.
1107 *
1108 * @param $nid
1109 * The node id.
1110 * @return
1111 * The comment count.
1112 */
1113 function comment_num_all($nid) {
1114 static $cache;
1115
1116 if (!isset($cache[$nid])) {
1117 $cache[$nid] = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $nid));
1118 }
1119 return $cache[$nid];
1120 }
1121
1122 /**
1123 * Get replies count for a comment.
1124 *
1125 * @param $pid
1126 * The comment id.
1127 * @return
1128 * The replies count.
1129 */
1130 function comment_num_replies($pid) {
1131 static $cache;
1132
1133 if (!isset($cache[$pid])) {
1134 $cache[$pid] = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE pid = %d AND status = %d', $pid, COMMENT_PUBLISHED));
1135 }
1136
1137 return $cache[$pid];
1138 }
1139
1140 /**
1141 * Get number of new comments for current user and specified node.
1142 *
1143 * @param $nid
1144 * node-id to count comments for
1145 * @param $timestamp
1146 * time to count from (defaults to time of last user access
1147 * to node)
1148 */
1149 function comment_num_new($nid, $timestamp = 0) {
1150 global $user;
1151
1152 if ($user->uid) {
1153 // Retrieve the timestamp at which the current user last viewed the
1154 // specified node.
1155 if (!$timestamp) {
1156 $timestamp = node_last_viewed($nid);
1157 }
1158 $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT);
1159
1160 // Use the timestamp to retrieve the number of new comments.
1161 $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));
1162
1163 return $result;
1164 }
1165 else {
1166 return 0;
1167 }
1168
1169 }
1170
1171 /**
1172 * Validate comment data.
1173 *
1174 * @param $edit
1175 * An associative array containig the comment data.
1176 * @return
1177 * The original $edit.
1178 */
1179 function comment_validate($edit) {
1180 global $user;
1181
1182 // Invoke other validation handlers
1183 comment_invoke_comment($edit, 'validate');
1184
1185 if (isset($edit['date'])) {
1186 // As of PHP 5.1.0, strtotime returns FALSE upon failure instead of -1.
1187 if (strtotime($edit['date']) <= 0) {
1188 form_set_error('date', t('You have to specify a valid date.'));
1189 }
1190 }
1191 if (isset($edit['author']) && !$account = user_load(array('name' => $edit['author']))) {
1192 form_set_error('author', t('You have to specify a valid author.'));
1193 }
1194
1195 // Check validity of name, mail and homepage (if given)
1196 if (!$user->uid || isset($edit['is_anonymous'])) {
1197 $node = node_load($edit['nid']);
1198 if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) > COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
1199 if ($edit['name']) {
1200 $taken = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE LOWER(name) = '%s'", $edit['name']));
1201
1202 if ($taken != 0) {
1203 form_set_error('name', t('The name you used belongs to a registered user.'));
1204 }
1205
1206 }
1207 else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
1208 form_set_error('name', t('You have to leave your name.'));
1209 }
1210
1211 if ($edit['mail']) {
1212 if (!valid_email_address($edit['mail'])) {
1213 form_set_error('mail', t('The e-mail address you specified is not valid.'));
1214 }
1215 }
1216 else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
1217 form_set_error('mail', t('You have to leave an e-mail address.'));
1218 }
1219
1220 if ($edit['homepage']) {
1221 if (!valid_url($edit['homepage'], TRUE)) {
1222 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>.'));
1223 }
1224 }
1225 }
1226 }
1227
1228 return $edit;
1229 }
1230
1231 /**
1232 * Generate the basic commenting form, for appending to a node or display on a separate page.
1233 *
1234 * @param $title
1235 * Not used.
1236 * @ingroup forms
1237 * @see comment_form_validate()
1238 * @see comment_form_submit()
1239 */
1240 function comment_form(&$form_state, $edit, $title = NULL) {
1241 global $user;
1242
1243 $op = isset($_POST['op']) ? $_POST['op'] : '';
1244 $node = node_load($edit['nid']);
1245
1246 if (!$user->uid && variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
1247 drupal_add_js(drupal_get_path('module', 'comment') .'/comment.js');
1248 }
1249 $edit += array('name' => '', 'mail' => '', 'homepage' => '');
1250 if ($user->uid) {
1251 if (!empty($edit['cid']) && user_access('administer comments')) {
1252 if (!empty($edit['author'])) {
1253 $author = $edit['author'];
1254 }
1255 elseif (!empty($edit['name'])) {
1256 $author = $edit['name'];
1257 }
1258 else {
1259 $author = $edit['registered_name'];
1260 }
1261
1262 if (!empty($edit['status'])) {
1263 $status = $edit['status'];
1264 }
1265 else {
1266 $status = 0;
1267 }
1268
1269 if (!empty($edit['date'])) {
1270 $date = $edit['date'];
1271 }
1272 else {
1273 $date = format_date($edit['timestamp'], 'custom', 'Y-m-d H:i O');
1274 }
1275
1276 $form['admin'] = array(
1277 '#type' => 'fieldset',
1278 '#title' => t('Administration'),
1279 '#collapsible' => TRUE,
1280 '#collapsed' => TRUE,
1281 '#weight' => -2,
1282 );
1283
1284 if ($edit['registered_name'] != '') {
1285 // The comment is by a registered user
1286 $form['admin']['author'] = array(
1287 '#type' => 'textfield',
1288 '#title' => t('Authored by'),
1289 '#size' => 30,
1290 '#maxlength' => 60,
1291 '#autocomplete_path' => 'user/autocomplete',
1292 '#default_value' => $author,
1293 '#weight' => -1,
1294 );
1295 }
1296 else {
1297 // The comment is by an anonymous user
1298 $form['is_anonymous'] = array(
1299 '#type' => 'value',
1300 '#value' => TRUE,
1301 );
1302 $form['admin']['name'] = array(
1303 '#type' => 'textfield',
1304 '#title' => t('Authored by'),
1305 '#size' => 30,
1306 '#maxlength' => 60,
1307 '#default_value' => $author,
1308 '#weight' => -1,
1309 );
1310 $form['admin']['mail'] = array(
1311 '#type' => 'textfield',
1312 '#title' => t('E-mail'),
1313 '#maxlength' => 64,
1314 '#size' => 30,
1315 '#default_value' => $edit['mail'],
1316 '#description' => t('The content of this field is kept private and will not be shown publicly.'),
1317 );
1318
1319 $form['admin']['homepage'] = array(
1320 '#type' => 'textfield',
1321 '#title' => t('Homepage'),
1322 '#maxlength' => 255,
1323 '#size' => 30,
1324 '#default_value' => $edit['homepage'],
1325 );
1326 }
1327
1328 $form['admin']['date'] = array('#type' => 'textfield', '#parents' => array('date'), '#title' => t('Authored on'), '#size' => 20, '#maxlength' => 25, '#default_value' => $date, '#weight' => -1);
1329
1330 $form['admin']['status'] = array('#type' => 'radios', '#parents' => array('status'), '#title' => t('Status'), '#default_value' => $status, '#options' => array(t('Published'), t('Not published')), '#weight' => -1);
1331
1332 }
1333 else {
1334 $form['_author'] = array('#type' => 'item', '#title' => t('Your name'), '#value' => theme('username', $user)
1335 );
1336 $form['author'] = array('#type' => 'value', '#value' => $user->name);
1337 }
1338 }
1339 else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MAY_CONTACT) {
1340 $form['name'] = array('#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 60, '#size' => 30, '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', t('Anonymous'))
1341 );
1342
1343 $form['mail'] = array('#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.')
1344 );
1345
1346 $form['homepage'] = array('#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage']);
1347 }
1348 else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
1349 $form['name'] = array('#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 60, '#size' => 30, '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', t('Anonymous')), '#required' => TRUE);
1350
1351 $form['mail'] = array('#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.'), '#required' => TRUE);
1352
1353 $form['homepage'] = array('#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage']);
1354 }
1355
1356 if (variable_get('comment_subject_field_'. $node->type, 1) == 1) {
1357 $form['subject'] = array('#type' => 'textfield', '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => !empty($edit['subject']) ? $edit['subject'] : '');
1358 }
1359
1360 if (!empty($edit['comment'])) {
1361 $default = $edit['comment'];
1362 }
1363 else {
1364 $default = '';
1365 }
1366
1367 $form['comment_filter']['comment'] = array(
1368 '#type' => 'textarea',
1369 '#title' => t('Comment'),
1370 '#rows' => 15,
1371 '#default_value' => $default,
1372 '#required' => TRUE,
1373 );
1374 if (!isset($edit['format'])) {
1375 $edit['format'] = FILTER_FORMAT_DEFAULT;
1376 }
1377 $form['comment_filter']['format'] = filter_form($edit['format']);
1378
1379 $form['cid'] = array('#type' => 'value', '#value' => !empty($edit['cid']) ? $edit['cid'] : NULL);
1380 $form['pid'] = array('#type' => 'value', '#value' => !empty($edit['pid']) ? $edit['pid'] : NULL);
1381 $form['nid'] = array('#type' => 'value', '#value' => $edit['nid']);
1382 $form['uid'] = array('#type' => 'value', '#value' => !empty($edit['uid']) ? $edit['uid'] : NULL);
1383
1384 // Only show save button if preview is optional or if we are in preview mode.
1385 // We show the save button in preview mode even if there are form errors so that
1386 // optional form elements (e.g., captcha) can be updated in preview mode.
1387 if (!form_get_errors() && ((variable_get('comment_preview_'. $node->type, COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL) || ($op == t('Preview')) || ($op == t('Save')))) {
1388 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 19);
1389 }
1390
1391 $form['preview'] = array('#type' => 'button', '#value' => t('Preview'), '#weight' => 20);
1392 $form['#token'] = 'comment'. $edit['nid'] . (isset($edit['pid']) ? $edit['pid'] : '');
1393
1394 if ($op == t('Preview')) {
1395 $form['#after_build'] = array('comment_form_add_preview');
1396 }
1397
1398 if (empty($edit['cid']) && empty($edit['pid'])) {
1399 $form['#action'] = url('comment/reply/'. $edit['nid']);
1400 }
1401
1402 return $form;
1403 }
1404
1405 /**
1406 * Theme the comment form box.
1407 *
1408 * @param $edit
1409 * The form structure.
1410 * @param $title
1411 * The form title.
1412 */
1413 function comment_form_box($edit, $title = NULL) {
1414 return theme('box', $title, drupal_get_form('comment_form', $edit, $title));
1415 }
1416
1417 /**
1418 * Form builder; Generate and validate a comment preview form.
1419 *
1420 * @ingroup forms
1421 */
1422 function comment_form_add_preview($form, &$form_state) {
1423 global $user;
1424 $edit = $form_state['values'];
1425 drupal_set_title(t('Preview comment'));
1426
1427 $output = '';
1428 $node = node_load($edit['nid']);
1429
1430 // Invoke full validation for the form, to protect against cross site
1431 // request forgeries (CSRF) and setting arbitrary values for fields such as
1432 // the input format. Preview the comment only when form validation does not
1433 // set any errors.
1434 drupal_validate_form($form['form_id']['#value'], $form, $form_state);
1435 if (!form_get_errors()) {
1436 _comment_form_submit($edit);
1437 $comment = (object)$edit;
1438
1439 // Attach the user and time information.
1440 if (!empty($edit['author'])) {
1441 $account = user_load(array('name' => $edit['author']));
1442 }
1443 elseif ($user->uid && !isset($edit['is_anonymous'])) {
1444 $account = $user;
1445 }
1446 if (!empty($account)) {
1447 $comment->uid = $account->uid;
1448 $comment->name = check_plain($account->name);
1449 }
1450 elseif (empty($comment->name)) {
1451 $comment->name = variable_get('anonymous', t('Anonymous'));
1452 }
1453 $comment->timestamp = !empty($edit['timestamp']) ? $edit['timestamp'] : time();
1454 $output .= theme('comment_view', $comment, $node);
1455 }
1456 $form['comment_preview'] = array(
1457 '#value' => $output,
1458 '#weight' => -100,
1459 '#prefix' => '<div class="preview">',
1460 '#suffix' => '</div>',
1461 );
1462
1463 $output = '';
1464
1465 if ($edit['pid']) {
1466 $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));
1467 $comment = drupal_unpack($comment);
1468 $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
1469 $output .= theme('comment_view', $comment, $node);
1470 }
1471 else {
1472 $suffix = empty($form['#suffix']) ? '' : $form['#suffix'];
1473 $form['#suffix'] = $suffix . node_view($node);
1474 $edit['pid'] = 0;
1475 }
1476
1477 $form['comment_preview_below'] = array('#value' => $output, '#weight' => 100);
1478
1479 return $form;
1480 }
1481
1482 /**
1483 * Validate comment form submissions.
1484 */
1485 function comment_form_validate($form, &$form_state) {
1486 global $user;
1487 if ($user->uid === 0) {
1488 foreach (array('name', 'homepage', 'mail') as $field) {
1489 // Set cookie for 365 days.
1490 if (isset($form_state['values'][$field])) {
1491 setcookie('comment_info_'. $field, $form_state['values'][$field], time() + 31536000, '/');
1492 }
1493 }
1494 }
1495 comment_validate($form_state['values']);
1496 }
1497
1498 /**
1499 * Prepare a comment for submission.
1500 *
1501 * @param $comment_values
1502 * An associative array containing the comment data.
1503 */
1504 function _comment_form_submit(&$comment_values) {
1505 $comment_values += array('subject' => '');
1506 if (!isset($comment_values['date'])) {
1507 $comment_values['date'] = 'now';
1508 }
1509 $comment_values['timestamp'] = strtotime($comment_values['date']);
1510 if (isset($comment_values['author'])) {
1511 $account = user_load(array('name' => $comment_values['author']));
1512 $comment_values['uid'] = $account->uid;
1513 $comment_values['name'] = $comment_values['author'];
1514 }
1515 // Validate the comment's subject. If not specified, extract
1516 // one from the comment's body.
1517 if (trim($comment_values['subject']) == '') {
1518 // The body may be in any format, so we:
1519 // 1) Filter it into HTML
1520 // 2) Strip out all HTML tags
1521 // 3) Convert entities back to plain-text.
1522 // Note: format is checked by check_markup().
1523 $comment_values['subject'] = trim(truncate_utf8(decode_entities(strip_tags(check_markup($comment_values['comment'], $comment_values['format']))), 29, TRUE));
1524 // Edge cases where the comment body is populated only by HTML tags will
1525 // require a default subject.
1526 if ($comment_values['subject'] == '') {
1527 $comment_values['subject'] = t('(No subject)');
1528 }
1529 }
1530 }
1531
1532 /**
1533 * Process comment form submissions; prepare the comment, store it, and set a redirection target.
1534 */
1535 function comment_form_submit($form, &$form_state) {
1536 _comment_form_submit($form_state['values']);
1537 if ($cid = comment_save($form_state['values'])) {
1538 $node = node_load($form_state['values']['nid']);
1539 $page = comment_new_page_count($node->comment_count, 1, $node);
1540 $form_state['redirect'] = array('node/'. $node->nid, $page, "comment-$cid");
1541 return;
1542 }
1543 }
1544
1545 /**
1546 * Theme a single comment block.
1547 *
1548 * @param $comment
1549 * The comment object.
1550 * @param $node
1551 * The comment node.
1552 * @param $links
1553 * An associative array containing control links.
1554 * @param $visible
1555 * Switches between folded/unfolded view.
1556 * @ingroup themeable
1557 */
1558 function theme_comment_view($comment, $node, $links = array(), $visible = TRUE) {
1559 static $first_new = TRUE;
1560
1561 $output = '';
1562 $comment->new = node_mark($comment->nid, $comment->timestamp);
1563 if ($first_new && $comment->new != MARK_READ) {
1564 // Assign the anchor only for the first new comment. This avoids duplicate
1565 // id attributes on a page.
1566 $first_new = FALSE;
1567 $output .= "<a id=\"new\"></a>\n";
1568 }
1569
1570 $output .= "<a id=\"comment-$comment->cid\"></a>\n";
1571
1572 // Switch to folded/unfolded view of the comment
1573 if ($visible) {
1574 $comment->comment = check_markup($comment->comment, $comment->format, FALSE);
1575
1576 // Comment API hook
1577 comment_invoke_comment($comment, 'view');
1578
1579 $output .= theme('comment', $comment, $node, $links);
1580 }
1581 else {
1582 $output .= theme('comment_folded', $comment);
1583 }
1584
1585 return $output;
1586 }
1587
1588
1589 /**
1590 * Build a comment control form.
1591 *
1592 * @param $mode
1593 * Comment display mode.
1594 * @param $order
1595 * Comment order mode.
1596 * @param $comments_per_page
1597 * Comments per page.
1598 * @ingroup forms
1599 */
1600 function comment_controls($mode = COMMENT_MODE_THREADED_EXPANDED, $order = COMMENT_ORDER_NEWEST_FIRST, $comments_per_page = 50) {
1601 $form['mode'] = array('#type' => 'select',
1602 '#default_value' => $mode,
1603 '#options' => _comment_get_modes(),
1604 '#weight' => 1,
1605 );
1606 $form['order'] = array(
1607 '#type' => 'select',
1608 '#default_value' => $order,
1609 '#options' => _comment_get_orders(),
1610 '#weight' => 2,
1611 );
1612 foreach (_comment_per_page() as $i) {
1613 $options[$i] = t('!a comments per page', array('!a' => $i));
1614 }
1615 $form['comments_per_page'] = array('#type' => 'select',
1616 '#default_value' => $comments_per_page,
1617 '#options' => $options,
1618 '#weight' => 3,
1619 );
1620
1621 $form['submit'] = array('#type' => 'submit',
1622 '#value' => t('Save settings'),
1623 '#weight' => 20,
1624 );
1625
1626 return $form;
1627 }
1628
1629 /**
1630 * Theme comment controls box where the user can change the default display mode and display order of comments.
1631 *
1632 * @param $form
1633 * The form structure.
1634 * @ingroup themeable
1635 */
1636 function theme_comment_controls($form) {
1637 $output = '<div class="container-inline">';
1638 $output .= drupal_render($form);
1639 $output .= '</div>';
1640 $output .= '<div class="description">'. t('Select your preferred way to display the comments and click "Save settings" to activate your changes.') .'</div>';
1641 return theme('box', t('Comment viewing options'), $output);
1642 }
1643
1644 /**
1645 * Process comment_controls form submissions.
1646 */
1647 function comment_controls_submit($form, &$form_state) {
1648 global $user;
1649
1650 $mode = $form_state['values']['mode'];
1651 $order = $form_state['values']['order'];
1652 $comments_per_page = $form_state['values']['comments_per_page'];
1653
1654 if ($user->uid) {
1655 $account = user_save($user, array('mode' => $mode, 'sort' => $order, 'comments_per_page' => $comments_per_page));
1656 // Terminate if an error occured during user_save().
1657 if (!$account) {
1658 drupal_set_message(t("Error saving user account."), 'error');
1659 return;
1660 }
1661 $user = $account;
1662 }
1663 else {
1664 $_SESSION['comment_mode'] = $mode;
1665 $_SESSION['comment_sort'] = $order;
1666 $_SESSION['comment_comments_per_page'] = $comments_per_page;
1667 }
1668 }
1669
1670 /**
1671 * Process variables for comment.tpl.php.
1672 *
1673 * @see comment.tpl.php
1674 * @see theme_comment()
1675 */
1676 function template_preprocess_comment(&$variables) {
1677 $comment = $variables['comment'];
1678 $node = $variables['node'];
1679 $variables['author'] = theme('username', $comment);
1680 $variables['content'] = $comment->comment;
1681 $variables['date'] = format_date($comment->timestamp);
1682 $variables['links'] = isset($variables['links']) ? theme('links', $variables['links']) : '';
1683 $variables['new'] = $comment->new ? t('new') : '';
1684 $variables['picture'] = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', $comment) : '';
1685 $variables['signature'] = $comment->signature;
1686 $variables['submitted'] = theme('comment_submitted', $comment);
1687 $variables['title'] = l($comment->subject, $_GET['q'], array('fragment' => "comment-$comment->cid"));
1688 $variables['template_files'][] = 'comment-'. $node->type;
1689 // set status to a string representation of comment->status.
1690 if (isset($comment->preview)) {
1691 $variables['status'] = 'comment-preview';
1692 }
1693 else {
1694 $variables['status'] = ($comment->status == COMMENT_NOT_PUBLISHED) ? 'comment-unpublished' : 'comment-published';
1695 }
1696 }
1697
1698 /**
1699 * Process variables for comment-folded.tpl.php.
1700 *
1701 * @see comment-folded.tpl.php
1702 * @see theme_comment_folded()
1703 */
1704 function template_preprocess_comment_folded(&$variables) {
1705 $comment = $variables['comment'];
1706 $variables['author'] = theme('username', $comment);
1707 $variables['date'] = format_date($comment->timestamp);
1708 $variables['new'] = $comment->new ? t('new') : '';
1709 $variables['title'] = l($comment->subject, comment_node_url() .'/'. $comment->cid, array('fragment' => "comment-$comment->cid"));
1710 }
1711
1712 /**
1713 * Theme comment flat collapsed view.
1714 *
1715 * @param $comment
1716 * The comment to be themed.
1717 * @param $node
1718 * The comment node.
1719 * @ingroup themeable
1720 */
1721 function theme_comment_flat_collapsed($comment, $node) {
1722 return theme('comment_view', $comment, $node, '', 0);
1723 }
1724
1725 /**
1726 * Theme comment flat expanded view.
1727 *
1728 * @param $comment
1729 * The comment to be themed.
1730 * @param $node
1731 * The comment node.
1732 * @ingroup themeable
1733 */
1734 function theme_comment_flat_expanded($comment, $node) {
1735 return theme('comment_view', $comment, $node, module_invoke_all('link', 'comment', $comment, 0));
1736 }
1737
1738 /**
1739 * Theme comment thread collapsed view.
1740 *
1741 * @param $comment
1742 * The comment to be themed.
1743 * @param $node
1744 * The comment node.
1745 * @ingroup themeable
1746 */
1747 function theme_comment_thread_collapsed($comment, $node) {
1748 return theme('comment_view', $comment, $node, '', 0);
1749 }
1750
1751 /**
1752 * Theme comment thread expanded view.
1753 *
1754 * @param $comment
1755 * The comment to be themed.
1756 * @param $node
1757 * The comment node.
1758 * @ingroup themeable
1759 */
1760 function theme_comment_thread_expanded($comment, $node) {
1761 return theme('comment_view', $comment, $node, module_invoke_all('link', 'comment', $comment, 0));
1762 }
1763
1764 /**
1765 * Theme a "you can't post comments" notice.
1766 *
1767 * @param $node
1768 * The comment node.
1769 * @ingroup themeable
1770 */
1771 function theme_comment_post_forbidden($node) {
1772 global $user;
1773 static $authenticated_post_comments;
1774
1775 if (!$user->uid) {
1776 if (!isset($authenticated_post_comments)) {
1777 // We only output any link if we are certain, that users get permission
1778 // to post comments by logging in. We also locally cache this information.
1779 $authenticated_post_comments = array_key_exists(DRUPAL_AUTHENTICATED_RID, user_roles(TRUE, 'post comments') + user_roles(TRUE, 'post comments without approval'));
1780 }
1781
1782 if ($authenticated_post_comments) {
1783 // We cannot use drupal_get_destination() because these links
1784 // sometimes appear on /node and taxonomy listing pages.
1785 if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
1786 $destination = 'destination='. drupal_urlencode("comment/reply/$node->nid#comment-form");
1787 }
1788 else {
1789 $destination = 'destination='. drupal_urlencode("node/$node->nid#comment-form");
1790 }
1791
1792 if (variable_get('user_register', 1)) {
1793 // Users can register themselves.
1794 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))));
1795 }
1796 else {
1797 // Only admins can add new users, no public registration.
1798 return t('<a href="@login">Login</a> to post comments', array('@login' => url('user/login', array('query' => $destination))));
1799 }
1800 }
1801 }
1802 }
1803
1804 /**
1805 * Process variables for comment-wrapper.tpl.php.
1806 *
1807 * @see comment-wrapper.tpl.php
1808 * @see theme_comment_wrapper()
1809 */
1810 function template_preprocess_comment_wrapper(&$variables) {
1811 // Provide contextual information.
1812 $variables['display_mode'] = _comment_get_display_setting('mode', $variables['node']);
1813 $variables['display_order'] = _comment_get_display_setting('sort', $variables['node']);
1814 $variables['comment_controls_state'] = variable_get('comment_controls_'. $variables['node']->type, COMMENT_CONTROLS_HIDDEN);
1815 $variables['template_files'][] = 'comment-wrapper-'. $variables['node']->type;
1816 }
1817
1818 /**
1819 * Theme a "Submitted by ..." notice.
1820 *
1821 * @param $comment
1822 * The comment.
1823 * @ingroup themeable
1824 */
1825 function theme_comment_submitted($comment) {
1826 return t('Submitted by !username on @datetime.',
1827 array(
1828 '!username' => theme('username', $comment),
1829 '@datetime' => format_date($comment->timestamp)
1830 ));
1831 }
1832
1833 /**
1834 * Return an array of viewing modes for comment listings.
1835 *
1836 * We can't use a global variable array because the locale system
1837 * is not initialized yet when the comment module is loaded.
1838 */
1839 function _comment_get_modes() {
1840 return array(
1841 COMMENT_MODE_FLAT_COLLAPSED => t('Flat list - collapsed'),
1842 COMMENT_MODE_FLAT_EXPANDED => t('Flat list - expanded'),
1843 COMMENT_MODE_THREADED_COLLAPSED => t('Threaded list - collapsed'),
1844 COMMENT_MODE_THREADED_EXPANDED => t('Threaded list - expanded')
1845 );
1846 }
1847
1848 /**
1849 * Return an array of viewing orders for comment listings.
1850 *
1851 * We can't use a global variable array because the locale system
1852 * is not initialized yet when the comment module is loaded.
1853 */
1854 function _comment_get_orders() {
1855 return array(
1856 COMMENT_ORDER_NEWEST_FIRST => t('Date - newest first'),
1857 COMMENT_ORDER_OLDEST_FIRST => t('Date - oldest first')
1858 );
1859 }
1860
1861 /**
1862 * Return an array of "comments per page" settings from which the user
1863 * can choose.
1864 */
1865 function _comment_per_page() {
1866 return drupal_map_assoc(array(10, 30, 50, 70, 90, 150, 200, 250, 300));
1867 }
1868
1869 /**
1870 * Return a current comment display setting
1871 *
1872 * @param $setting
1873 * can be one of these: 'mode', 'sort', 'comments_per_page'
1874 * @param $node
1875 * The comment node in question.
1876 */
1877 function _comment_get_display_setting($setting, $node) {
1878 global $user;
1879
1880 if (isset($_GET[$setting])) {
1881 $value = $_GET[$setting];
1882 }
1883 else {
1884 // get the setting's site default
1885 switch ($setting) {
1886 case 'mode':
1887 $default = variable_get('comment_default_mode_'. $node->type, COMMENT_MODE_THREADED_EXPANDED);
1888 break;
1889 case 'sort':
1890 $default = variable_get('comment_default_order_'. $node->type, COMMENT_ORDER_NEWEST_FIRST);
1891 break;
1892 case 'comments_per_page':
1893 $default = variable_get('comment_default_per_page_'. $node->type, 50);
1894 }
1895 if (variable_get('comment_controls_'. $node->type, COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_HIDDEN) {
1896 // if comment controls are disabled use site default
1897 $value = $default;
1898 }
1899 else {
1900 // otherwise use the user's setting if set
1901 if (isset($user->$setting) && $user->$setting) {
1902 $value = $user->$setting;
1903 }
1904 else if (isset($_SESSION['comment_'. $setting]) && $_SESSION['comment_'. $setting]) {
1905 $value = $_SESSION['comment_'. $setting];
1906 }
1907 else {
1908 $value = $default;
1909 }
1910 }
1911 }
1912 return $value;
1913 }
1914
1915 /**
1916 * Updates the comment statistics for a given node. This should be called any
1917 * time a comment is added, deleted, or updated.
1918 *
1919 * The following fields are contained in the node_comment_statistics table.
1920 * - last_comment_timestamp: the timestamp of the last comment for this node or the node create stamp if no comments exist for the node.
1921 * - last_comment_name: the name of the anonymous poster for the last comment
1922 * - 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.
1923 * - comment_count: the total number of approved/published comments on this node.
1924 */
1925 function _comment_update_node_statistics($nid) {
1926 $count = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE nid = %d AND status = %d', $nid, COMMENT_PUBLISHED));
1927
1928 // comments exist
1929 if ($count > 0) {
1930 $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));
1931 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);
1932 }
1933
1934 // no comments
1935 else {
1936 $node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE nid = %d", $nid));
1937 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);
1938 }
1939 }
1940
1941 /**
1942 * Invoke a hook_comment() operation in all modules.
1943 *
1944 * @param &$comment
1945 * A comment object.
1946 * @param $op
1947 * A string containing the name of the comment operation.
1948 * @return
1949 * The returned value of the invoked hooks.
1950 */
1951 function comment_invoke_comment(&$comment, $op) {
1952 $return = array();
1953 foreach (module_implements('comment') as $name) {
1954 $function = $name .'_comment';
1955 $result = $function($comment, $op);
1956 if (isset($result) && is_array($result)) {
1957 $return = array_merge($return, $result);
1958 }
1959 else if (isset($result)) {
1960 $return[] = $result;
1961 }
1962 }
1963 return $return;
1964 }
1965
1966 /**
1967 * Generate vancode.
1968 *
1969 * Consists of a leading character indicating length, followed by N digits
1970 * with a numerical value in base 36. Vancodes can be sorted as strings
1971 * without messing up numerical order.
1972 *
1973 * It goes:
1974 * 00, 01, 02, ..., 0y, 0z,
1975 * 110, 111, ... , 1zy, 1zz,
1976 * 2100, 2101, ..., 2zzy, 2zzz,
1977 * 31000, 31001, ...
1978 */
1979 function int2vancode($i = 0) {
1980 $num = base_convert((int)$i, 10, 36);
1981 $length = strlen($num);
1982 return chr($length + ord('0') - 1) . $num;
1983 }
1984
1985 /**
1986 * Decode vancode back to an integer.
1987 */
1988 function vancode2int($c = '00') {
1989 return base_convert(substr($c, 1), 36, 10);
1990 }
1991
1992 /**
1993 * Implementation of hook_hook_info().
1994 */
1995 function comment_hook_info() {
1996 return array(
1997 'comment' => array(
1998 'comment' => array(
1999 'insert' => array(
2000 'runs when' => t('After saving a new comment'),
2001 ),
2002 'update' => array(
2003 'runs when' => t('After saving an updated comment'),
2004 ),
2005 'delete' => array(
2006 'runs when' => t('After deleting a comment')
2007 ),
2008 'view' => array(
2009 'runs when' => t('When a comment is being viewed by an authenticated user')
2010 ),
2011 ),
2012 ),
2013 );
2014 }
2015
2016 /**
2017 * Implementation of hook_action_info().
2018 */
2019 function comment_action_info() {
2020 return array(
2021 'comment_unpublish_action' => array(
2022 'description' => t('Unpublish comment'),
2023 'type' => 'comment',
2024 'configurable' => FALSE,
2025 'hooks' => array(
2026 'comment' => array('insert', 'update'),
2027 )
2028 ),
2029 'comment_unpublish_by_keyword_action' => array(
2030 'description' => t('Unpublish comment containing keyword(s)'),
2031 'type' => 'comment',
2032 'configurable' => TRUE,
2033 'hooks' => array(
2034 'comment' => array('insert', 'update'),
2035 )
2036 )
2037 );
2038 }
2039
2040 /**
2041 * Drupal action to unpublish a comment.
2042 *
2043 * @param $context
2044 * Keyed array. Must contain the id of the comment if $comment is not passed.
2045 * @param $comment
2046 * An optional comment object.
2047 */
2048 function comment_unpublish_action($comment, $context = array()) {
2049 if (isset($comment->cid)) {
2050 $cid = $comment->cid;
2051 $subject = $comment->subject;
2052 }
2053 else {
2054 $cid = $context['cid'];
2055 $subject = db_result(db_query("SELECT subject FROM {comments} WHERE cid = %d", $cid));
2056 }
2057 db_query('UPDATE {comments} SET status = %d WHERE cid = %d', COMMENT_NOT_PUBLISHED, $cid);
2058 watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject));
2059 }
2060
2061 /**
2062 * Form builder; Prepare a form for blacklisted keywords.
2063 *
2064 * @ingroup forms
2065 */
2066 function comment_unpublish_by_keyword_action_form($context) {
2067 $form['keywords'] = array(
2068 '#title' => t('Keywords'),
2069 '#type' => 'textarea',
2070 '#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.'),
2071 '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
2072 );
2073 return $form;
2074 }
2075
2076 /**
2077 * Process comment_unpublish_by_keyword_action_form form submissions.
2078 */
2079 function comment_unpublish_by_keyword_action_submit($form, $form_state) {
2080 return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
2081 }
2082
2083 /**
2084 * Implementation of a configurable Drupal action.
2085 * Unpublish a comment if it contains a certain string.
2086 *
2087 * @param $context
2088 * An array providing more information about the context of the call to this action.
2089 * Unused here since this action currently only supports the insert and update ops of
2090 * the comment hook, both of which provide a complete $comment object.
2091 * @param $comment
2092 * A comment object.
2093 */
2094 function comment_unpublish_by_keyword_action($comment, $context) {
2095 foreach ($context['keywords'] as $keyword) {
2096 if (strstr($comment->comment, $keyword) || strstr($comment->subject, $keyword)) {
2097 db_query('UPDATE {comments} SET status = %d WHERE cid = %d', COMMENT_NOT_PUBLISHED, $comment->cid);
2098 watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject));
2099 break;
2100 }
2101 }
2102 }