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