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