Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Functions and classes for commenting 19 * 20 * @package core 21 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 defined('MOODLE_INTERNAL') || die(); 25 26 /** 27 * Comment is helper class to add/delete comments anywhere in moodle 28 * 29 * @package core 30 * @category comment 31 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class comment { 35 /** @var int there may be several comment box in one page so we need a client_id to recognize them */ 36 private $cid; 37 /** @var string commentarea is used to specify different parts shared the same itemid */ 38 private $commentarea; 39 /** @var int itemid is used to associate with commenting content */ 40 private $itemid; 41 /** @var string this html snippet will be used as a template to build comment content */ 42 private $template; 43 /** @var int The context id for comments */ 44 private $contextid; 45 /** @var stdClass The context itself */ 46 private $context; 47 /** @var int The course id for comments */ 48 private $courseid; 49 /** @var stdClass course module object, only be used to help find pluginname automatically */ 50 private $cm; 51 /** 52 * The component that this comment is for. 53 * 54 * It is STRONGLY recommended to set this. 55 * Added as a database field in 2.9, old comments will have a null component. 56 * 57 * @var string 58 */ 59 private $component; 60 /** @var string This is calculated by normalising the component */ 61 private $pluginname; 62 /** @var string This is calculated by normalising the component */ 63 private $plugintype; 64 /** @var bool Whether the user has the required capabilities/permissions to view comments. */ 65 private $viewcap = false; 66 /** @var bool Whether the user has the required capabilities/permissions to post comments. */ 67 private $postcap = false; 68 /** @var string to customize link text */ 69 private $linktext; 70 /** @var bool If set to true then comment sections won't be able to be opened and closed instead they will always be visible. */ 71 protected $notoggle = false; 72 /** @var bool If set to true comments are automatically loaded as soon as the page loads. */ 73 protected $autostart = false; 74 /** @var bool If set to true the total count of comments is displayed when displaying comments. */ 75 protected $displaytotalcount = false; 76 /** @var bool If set to true a cancel button will be shown on the form used to submit comments. */ 77 protected $displaycancel = false; 78 /** @var int The number of comments associated with this comments params */ 79 protected $totalcommentcount = null; 80 81 /** 82 * Set to true to remove the col attribute from the textarea making it full width. 83 * @var bool 84 */ 85 protected $fullwidth = false; 86 87 /** @var bool Use non-javascript UI */ 88 private static $nonjs = false; 89 /** @var int comment itemid used in non-javascript UI */ 90 private static $comment_itemid = null; 91 /** @var int comment context used in non-javascript UI */ 92 private static $comment_context = null; 93 /** @var string comment area used in non-javascript UI */ 94 private static $comment_area = null; 95 /** @var string comment page used in non-javascript UI */ 96 private static $comment_page = null; 97 /** @var string comment itemid component in non-javascript UI */ 98 private static $comment_component = null; 99 100 /** 101 * Construct function of comment class, initialise 102 * class members 103 * 104 * @param stdClass $options { 105 * context => context context to use for the comment [required] 106 * component => string which plugin will comment being added to [required] 107 * itemid => int the id of the associated item (forum post, glossary item etc) [required] 108 * area => string comment area 109 * cm => stdClass course module 110 * course => course course object 111 * client_id => string an unique id to identify comment area 112 * autostart => boolean automatically expend comments 113 * showcount => boolean display the number of comments 114 * displaycancel => boolean display cancel button 115 * notoggle => boolean don't show/hide button 116 * linktext => string title of show/hide button 117 * } 118 */ 119 public function __construct(stdClass $options) { 120 $this->viewcap = false; 121 $this->postcap = false; 122 123 // setup client_id 124 if (!empty($options->client_id)) { 125 $this->cid = $options->client_id; 126 } else { 127 $this->cid = uniqid(); 128 } 129 130 // setup context 131 if (!empty($options->context)) { 132 $this->context = $options->context; 133 $this->contextid = $this->context->id; 134 } else if(!empty($options->contextid)) { 135 $this->contextid = $options->contextid; 136 $this->context = context::instance_by_id($this->contextid); 137 } else { 138 print_error('invalidcontext'); 139 } 140 141 if (!empty($options->component)) { 142 // set and validate component 143 $this->set_component($options->component); 144 } else { 145 // component cannot be empty 146 throw new comment_exception('invalidcomponent'); 147 } 148 149 // setup course 150 // course will be used to generate user profile link 151 if (!empty($options->course)) { 152 $this->courseid = $options->course->id; 153 } else if (!empty($options->courseid)) { 154 $this->courseid = $options->courseid; 155 } else { 156 if ($coursecontext = $this->context->get_course_context(false)) { 157 $this->courseid = $coursecontext->instanceid; 158 } else { 159 $this->courseid = SITEID; 160 } 161 } 162 163 // setup coursemodule 164 if (!empty($options->cm)) { 165 $this->cm = $options->cm; 166 } else { 167 $this->cm = null; 168 } 169 170 // setup commentarea 171 if (!empty($options->area)) { 172 $this->commentarea = $options->area; 173 } 174 175 // setup itemid 176 if (!empty($options->itemid)) { 177 $this->itemid = $options->itemid; 178 } else { 179 $this->itemid = 0; 180 } 181 182 // setup customized linktext 183 if (!empty($options->linktext)) { 184 $this->linktext = $options->linktext; 185 } else { 186 $this->linktext = get_string('comments'); 187 } 188 189 // setup options for callback functions 190 $this->comment_param = new stdClass(); 191 $this->comment_param->context = $this->context; 192 $this->comment_param->courseid = $this->courseid; 193 $this->comment_param->cm = $this->cm; 194 $this->comment_param->commentarea = $this->commentarea; 195 $this->comment_param->itemid = $this->itemid; 196 197 // setup notoggle 198 if (!empty($options->notoggle)) { 199 $this->set_notoggle($options->notoggle); 200 } 201 202 // setup notoggle 203 if (!empty($options->autostart)) { 204 $this->set_autostart($options->autostart); 205 } 206 207 // setup displaycancel 208 if (!empty($options->displaycancel)) { 209 $this->set_displaycancel($options->displaycancel); 210 } 211 212 // setup displaytotalcount 213 if (!empty($options->showcount)) { 214 $this->set_displaytotalcount($options->showcount); 215 } 216 217 // setting post and view permissions 218 $this->check_permissions(); 219 220 // load template 221 $this->template = html_writer::start_tag('div', array('class' => 'comment-message')); 222 223 $this->template .= html_writer::start_tag('div', array('class' => 'comment-message-meta mr-3')); 224 225 $this->template .= html_writer::tag('span', '___picture___', array('class' => 'picture')); 226 $this->template .= html_writer::tag('span', '___name___', array('class' => 'user')) . ' - '; 227 $this->template .= html_writer::tag('span', '___time___', array('class' => 'time')); 228 229 $this->template .= html_writer::end_tag('div'); // .comment-message-meta 230 $this->template .= html_writer::tag('div', '___content___', array('class' => 'text')); 231 232 $this->template .= html_writer::end_tag('div'); // .comment-message 233 234 if (!empty($this->plugintype)) { 235 $this->template = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'template', array($this->comment_param), $this->template); 236 } 237 238 unset($options); 239 } 240 241 /** 242 * Receive nonjs comment parameters 243 * 244 * @param moodle_page $page The page object to initialise comments within 245 * If not provided the global $PAGE is used 246 */ 247 public static function init(moodle_page $page = null) { 248 global $PAGE; 249 250 if (empty($page)) { 251 $page = $PAGE; 252 } 253 // setup variables for non-js interface 254 self::$nonjs = optional_param('nonjscomment', '', PARAM_ALPHANUM); 255 self::$comment_itemid = optional_param('comment_itemid', '', PARAM_INT); 256 self::$comment_component = optional_param('comment_component', '', PARAM_COMPONENT); 257 self::$comment_context = optional_param('comment_context', '', PARAM_INT); 258 self::$comment_page = optional_param('comment_page', '', PARAM_INT); 259 self::$comment_area = optional_param('comment_area', '', PARAM_AREA); 260 261 $page->requires->strings_for_js(array( 262 'addcomment', 263 'comments', 264 'commentscount', 265 'commentsrequirelogin', 266 'deletecommentbyon' 267 ), 268 'moodle' 269 ); 270 } 271 272 /** 273 * Sets the component. 274 * 275 * This method shouldn't be public, changing the component once it has been set potentially 276 * invalidates permission checks. 277 * A coding_error is now thrown if code attempts to change the component. 278 * 279 * @throws coding_exception if you try to change the component after it has been set. 280 * @param string $component 281 */ 282 public function set_component($component) { 283 if (!empty($this->component) && $this->component !== $component) { 284 throw new coding_exception('You cannot change the component of a comment once it has been set'); 285 } 286 $this->component = $component; 287 list($this->plugintype, $this->pluginname) = core_component::normalize_component($component); 288 } 289 290 /** 291 * Determines if the user can view the comment. 292 * 293 * @param bool $value 294 */ 295 public function set_view_permission($value) { 296 $this->viewcap = (bool)$value; 297 } 298 299 /** 300 * Determines if the user can post a comment 301 * 302 * @param bool $value 303 */ 304 public function set_post_permission($value) { 305 $this->postcap = (bool)$value; 306 } 307 308 /** 309 * check posting comments permission 310 * It will check based on user roles and ask modules 311 * If you need to check permission by modules, a 312 * function named $pluginname_check_comment_post must be implemented 313 */ 314 private function check_permissions() { 315 $this->postcap = has_capability('moodle/comment:post', $this->context); 316 $this->viewcap = has_capability('moodle/comment:view', $this->context); 317 if (!empty($this->plugintype)) { 318 $permissions = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'permissions', array($this->comment_param), array('post'=>false, 'view'=>false)); 319 $this->postcap = $this->postcap && $permissions['post']; 320 $this->viewcap = $this->viewcap && $permissions['view']; 321 } 322 } 323 324 /** 325 * Gets a link for this page that will work with JS disabled. 326 * 327 * @global moodle_page $PAGE 328 * @param moodle_page $page 329 * @return moodle_url 330 */ 331 public function get_nojslink(moodle_page $page = null) { 332 if ($page === null) { 333 global $PAGE; 334 $page = $PAGE; 335 } 336 337 $link = new moodle_url($page->url, array( 338 'nonjscomment' => true, 339 'comment_itemid' => $this->itemid, 340 'comment_context' => $this->context->id, 341 'comment_component' => $this->get_component(), 342 'comment_area' => $this->commentarea, 343 )); 344 $link->remove_params(array('comment_page')); 345 return $link; 346 } 347 348 /** 349 * Sets the value of the notoggle option. 350 * 351 * If set to true then the user will not be able to expand and collase 352 * the comment section. 353 * 354 * @param bool $newvalue 355 */ 356 public function set_notoggle($newvalue = true) { 357 $this->notoggle = (bool)$newvalue; 358 } 359 360 /** 361 * Sets the value of the autostart option. 362 * 363 * If set to true then the comments will be loaded during page load. 364 * Normally this happens only once the user expands the comment section. 365 * 366 * @param bool $newvalue 367 */ 368 public function set_autostart($newvalue = true) { 369 $this->autostart = (bool)$newvalue; 370 } 371 372 /** 373 * Sets the displaycancel option 374 * 375 * If set to true then a cancel button will be shown when using the form 376 * to post comments. 377 * 378 * @param bool $newvalue 379 */ 380 public function set_displaycancel($newvalue = true) { 381 $this->displaycancel = (bool)$newvalue; 382 } 383 384 /** 385 * Sets the displaytotalcount option 386 * 387 * If set to true then the total number of comments will be displayed 388 * when printing comments. 389 * 390 * @param bool $newvalue 391 */ 392 public function set_displaytotalcount($newvalue = true) { 393 $this->displaytotalcount = (bool)$newvalue; 394 } 395 396 /** 397 * Initialises the JavaScript that enchances the comment API. 398 * 399 * @param moodle_page $page The moodle page object that the JavaScript should be 400 * initialised for. 401 */ 402 public function initialise_javascript(moodle_page $page) { 403 404 $options = new stdClass; 405 $options->client_id = $this->cid; 406 $options->commentarea = $this->commentarea; 407 $options->itemid = $this->itemid; 408 $options->page = 0; 409 $options->courseid = $this->courseid; 410 $options->contextid = $this->contextid; 411 $options->component = $this->component; 412 $options->notoggle = $this->notoggle; 413 $options->autostart = $this->autostart; 414 415 $page->requires->js_init_call('M.core_comment.init', array($options), true); 416 417 return true; 418 } 419 420 /** 421 * Prepare comment code in html 422 * @param boolean $return 423 * @return string|void 424 */ 425 public function output($return = true) { 426 global $PAGE, $OUTPUT; 427 static $template_printed; 428 429 $this->initialise_javascript($PAGE); 430 431 if (!empty(self::$nonjs)) { 432 // return non js comments interface 433 return $this->print_comments(self::$comment_page, $return, true); 434 } 435 436 $html = ''; 437 438 // print html template 439 // Javascript will use the template to render new comments 440 if (empty($template_printed) && $this->can_view()) { 441 $html .= html_writer::tag('div', $this->template, array('style' => 'display:none', 'id' => 'cmt-tmpl')); 442 $template_printed = true; 443 } 444 445 if ($this->can_view()) { 446 // print commenting icon and tooltip 447 $html .= html_writer::start_tag('div', array('class' => 'mdl-left')); 448 $html .= html_writer::link($this->get_nojslink($PAGE), get_string('showcommentsnonjs'), array('class' => 'showcommentsnonjs')); 449 450 if (!$this->notoggle) { 451 // If toggling is enabled (notoggle=false) then print the controls to toggle 452 // comments open and closed 453 $countstring = ''; 454 if ($this->displaytotalcount) { 455 $countstring = '('.$this->count().')'; 456 } 457 $collapsedimage= 't/collapsed'; 458 if (right_to_left()) { 459 $collapsedimage= 't/collapsed_rtl'; 460 } else { 461 $collapsedimage= 't/collapsed'; 462 } 463 $html .= html_writer::start_tag('a', array( 464 'class' => 'comment-link', 465 'id' => 'comment-link-'.$this->cid, 466 'href' => '#', 467 'role' => 'button', 468 'aria-expanded' => 'false') 469 ); 470 $html .= $OUTPUT->pix_icon($collapsedimage, $this->linktext); 471 $html .= html_writer::tag('span', $this->linktext.' '.$countstring, array('id' => 'comment-link-text-'.$this->cid)); 472 $html .= html_writer::end_tag('a'); 473 } 474 475 $html .= html_writer::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid, 'class' => 'comment-ctrl')); 476 477 if ($this->autostart) { 478 // If autostart has been enabled print the comments list immediatly 479 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list comments-loaded')); 480 $html .= html_writer::tag('li', '', array('class' => 'first')); 481 $html .= $this->print_comments(0, true, false); 482 $html .= html_writer::end_tag('ul'); // .comment-list 483 $html .= $this->get_pagination(0); 484 } else { 485 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list')); 486 $html .= html_writer::tag('li', '', array('class' => 'first')); 487 $html .= html_writer::end_tag('ul'); // .comment-list 488 $html .= html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination')); 489 } 490 491 if ($this->can_post()) { 492 // print posting textarea 493 $textareaattrs = array( 494 'name' => 'content', 495 'rows' => 2, 496 'id' => 'dlg-content-'.$this->cid, 497 'aria-label' => get_string('addcomment') 498 ); 499 if (!$this->fullwidth) { 500 $textareaattrs['cols'] = '20'; 501 } else { 502 $textareaattrs['class'] = 'fullwidth'; 503 } 504 505 $html .= html_writer::start_tag('div', array('class' => 'comment-area')); 506 $html .= html_writer::start_tag('div', array('class' => 'db')); 507 $html .= html_writer::tag('textarea', '', $textareaattrs); 508 $html .= html_writer::end_tag('div'); // .db 509 510 $html .= html_writer::start_tag('div', array('class' => 'fd', 'id' => 'comment-action-'.$this->cid)); 511 $html .= html_writer::link('#', get_string('savecomment'), array('id' => 'comment-action-post-'.$this->cid)); 512 513 if ($this->displaycancel) { 514 $html .= html_writer::tag('span', ' | '); 515 $html .= html_writer::link('#', get_string('cancel'), array('id' => 'comment-action-cancel-'.$this->cid)); 516 } 517 518 $html .= html_writer::end_tag('div'); // .fd 519 $html .= html_writer::end_tag('div'); // .comment-area 520 $html .= html_writer::tag('div', '', array('class' => 'clearer')); 521 } 522 523 $html .= html_writer::end_tag('div'); // .comment-ctrl 524 $html .= html_writer::end_tag('div'); // .mdl-left 525 } else { 526 $html = ''; 527 } 528 529 if ($return) { 530 return $html; 531 } else { 532 echo $html; 533 } 534 } 535 536 /** 537 * Return matched comments 538 * 539 * @param int $page 540 * @param str $sortdirection sort direction, ASC or DESC 541 * @return array 542 */ 543 public function get_comments($page = '', $sortdirection = 'DESC') { 544 global $DB, $CFG, $USER, $OUTPUT; 545 if (!$this->can_view()) { 546 return false; 547 } 548 if (!is_numeric($page)) { 549 $page = 0; 550 } 551 $params = array(); 552 $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15; 553 $start = $page * $perpage; 554 $userfieldsapi = \core_user\fields::for_userpic(); 555 $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 556 557 list($componentwhere, $component) = $this->get_component_select_sql('c'); 558 if ($component) { 559 $params['component'] = $component; 560 } 561 562 $sortdirection = ($sortdirection === 'ASC') ? 'ASC' : 'DESC'; 563 $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated 564 FROM {comments} c 565 JOIN {user} u ON u.id = c.userid 566 WHERE c.contextid = :contextid AND 567 c.commentarea = :commentarea AND 568 c.itemid = :itemid AND 569 $componentwhere 570 ORDER BY c.timecreated $sortdirection, c.id $sortdirection"; 571 $params['contextid'] = $this->contextid; 572 $params['commentarea'] = $this->commentarea; 573 $params['itemid'] = $this->itemid; 574 575 $comments = array(); 576 $formatoptions = array('overflowdiv' => true, 'blanktarget' => true); 577 $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage); 578 foreach ($rs as $u) { 579 $c = new stdClass(); 580 $c->id = $u->cid; 581 $c->content = $u->ccontent; 582 $c->format = $u->cformat; 583 $c->timecreated = $u->ctimecreated; 584 $c->strftimeformat = get_string('strftimerecentfull', 'langconfig'); 585 $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid)); 586 $c->profileurl = $url->out(false); // URL should not be escaped just yet. 587 $c->fullname = fullname($u); 588 $c->time = userdate($c->timecreated, $c->strftimeformat); 589 $c->content = format_text($c->content, $c->format, $formatoptions); 590 $c->avatar = $OUTPUT->user_picture($u, array('size' => 16)); 591 $c->userid = $u->id; 592 593 if ($this->can_delete($c)) { 594 $c->delete = true; 595 } 596 $comments[] = $c; 597 } 598 $rs->close(); 599 600 if (!empty($this->plugintype)) { 601 // moodle module will filter comments 602 $comments = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'display', array($comments, $this->comment_param), $comments); 603 } 604 605 return $comments; 606 } 607 608 /** 609 * Returns an SQL fragment and param for selecting on component. 610 * @param string $alias 611 * @return array 612 */ 613 protected function get_component_select_sql($alias = '') { 614 $component = $this->get_component(); 615 if ($alias) { 616 $alias = $alias.'.'; 617 } 618 if (empty($component)) { 619 $componentwhere = "{$alias}component IS NULL"; 620 $component = null; 621 } else { 622 $componentwhere = "({$alias}component IS NULL OR {$alias}component = :component)"; 623 } 624 return array($componentwhere, $component); 625 } 626 627 /** 628 * Returns the number of comments associated with the details of this object 629 * 630 * @global moodle_database $DB 631 * @return int 632 */ 633 public function count() { 634 global $DB; 635 if ($this->totalcommentcount === null) { 636 list($where, $component) = $this->get_component_select_sql(); 637 $where .= ' AND itemid = :itemid AND commentarea = :commentarea AND contextid = :contextid'; 638 $params = array( 639 'itemid' => $this->itemid, 640 'commentarea' => $this->commentarea, 641 'contextid' => $this->context->id, 642 ); 643 if ($component) { 644 $params['component'] = $component; 645 } 646 647 $this->totalcommentcount = $DB->count_records_select('comments', $where, $params); 648 } 649 return $this->totalcommentcount; 650 } 651 652 /** 653 * Returns HTML to display a pagination bar 654 * 655 * @global stdClass $CFG 656 * @global core_renderer $OUTPUT 657 * @param int $page 658 * @return string 659 */ 660 public function get_pagination($page = 0) { 661 global $CFG, $OUTPUT; 662 $count = $this->count(); 663 $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15; 664 $pages = (int)ceil($count/$perpage); 665 if ($pages == 1 || $pages == 0) { 666 return html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination')); 667 } 668 if (!empty(self::$nonjs)) { 669 // used in non-js interface 670 return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page'); 671 } else { 672 // return ajax paging bar 673 $str = ''; 674 $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid.'">'; 675 for ($p=0; $p<$pages; $p++) { 676 if ($p == $page) { 677 $class = 'curpage'; 678 } else { 679 $class = 'pageno'; 680 } 681 $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid.'-'.$p.'">'.($p+1).'</a> '; 682 } 683 $str .= '</div>'; 684 } 685 return $str; 686 } 687 688 /** 689 * Add a new comment 690 * 691 * @global moodle_database $DB 692 * @param string $content 693 * @param int $format 694 * @return stdClass 695 */ 696 public function add($content, $format = FORMAT_MOODLE) { 697 global $CFG, $DB, $USER, $OUTPUT; 698 if (!$this->can_post()) { 699 throw new comment_exception('nopermissiontocomment'); 700 } 701 $now = time(); 702 $newcmt = new stdClass; 703 $newcmt->contextid = $this->contextid; 704 $newcmt->commentarea = $this->commentarea; 705 $newcmt->itemid = $this->itemid; 706 $newcmt->component = !empty($this->component) ? $this->component : null; 707 $newcmt->content = $content; 708 $newcmt->format = $format; 709 $newcmt->userid = $USER->id; 710 $newcmt->timecreated = $now; 711 712 // This callback allow module to modify the content of comment, such as filter or replacement 713 plugin_callback($this->plugintype, $this->pluginname, 'comment', 'add', array(&$newcmt, $this->comment_param)); 714 715 $cmt_id = $DB->insert_record('comments', $newcmt); 716 if (!empty($cmt_id)) { 717 $newcmt->id = $cmt_id; 718 $newcmt->strftimeformat = get_string('strftimerecentfull', 'langconfig'); 719 $newcmt->fullname = fullname($USER); 720 $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid)); 721 $newcmt->profileurl = $url->out(); 722 $formatoptions = array('overflowdiv' => true, 'blanktarget' => true); 723 $newcmt->content = format_text($newcmt->content, $newcmt->format, $formatoptions); 724 $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16)); 725 726 $commentlist = array($newcmt); 727 728 if (!empty($this->plugintype)) { 729 // Call the display callback to allow the plugin to format the newly added comment. 730 $commentlist = plugin_callback($this->plugintype, 731 $this->pluginname, 732 'comment', 733 'display', 734 array($commentlist, $this->comment_param), 735 $commentlist); 736 $newcmt = $commentlist[0]; 737 } 738 $newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat); 739 740 // Trigger comment created event. 741 if (core_component::is_core_subsystem($this->component)) { 742 $eventclassname = '\\core\\event\\' . $this->component . '_comment_created'; 743 } else { 744 $eventclassname = '\\' . $this->component . '\\event\comment_created'; 745 } 746 if (class_exists($eventclassname)) { 747 $event = $eventclassname::create( 748 array( 749 'context' => $this->context, 750 'objectid' => $newcmt->id, 751 'other' => array( 752 'itemid' => $this->itemid 753 ) 754 )); 755 $event->trigger(); 756 } 757 758 return $newcmt; 759 } else { 760 throw new comment_exception('dbupdatefailed'); 761 } 762 } 763 764 /** 765 * delete by context, commentarea and itemid 766 * @param stdClass|array $param { 767 * contextid => int the context in which the comments exist [required] 768 * commentarea => string the comment area [optional] 769 * itemid => int comment itemid [optional] 770 * } 771 * @return boolean 772 */ 773 public static function delete_comments($param) { 774 global $DB; 775 $param = (array)$param; 776 if (empty($param['contextid'])) { 777 return false; 778 } 779 $DB->delete_records('comments', $param); 780 return true; 781 } 782 783 /** 784 * Delete page_comments in whole course, used by course reset 785 * 786 * @param stdClass $context course context 787 */ 788 public static function reset_course_page_comments($context) { 789 global $DB; 790 $contexts = array(); 791 $contexts[] = $context->id; 792 $children = $context->get_child_contexts(); 793 foreach ($children as $c) { 794 $contexts[] = $c->id; 795 } 796 list($ids, $params) = $DB->get_in_or_equal($contexts); 797 $DB->delete_records_select('comments', "commentarea='page_comments' AND contextid $ids", $params); 798 } 799 800 /** 801 * Delete a comment 802 * 803 * @param int|stdClass $comment The id of a comment, or a comment record. 804 * @return bool 805 */ 806 public function delete($comment) { 807 global $DB; 808 if (is_object($comment)) { 809 $commentid = $comment->id; 810 } else { 811 $commentid = $comment; 812 $comment = $DB->get_record('comments', ['id' => $commentid]); 813 } 814 815 if (!$comment) { 816 throw new comment_exception('dbupdatefailed'); 817 } 818 if (!$this->can_delete($comment)) { 819 throw new comment_exception('nopermissiontocomment'); 820 } 821 $DB->delete_records('comments', array('id'=>$commentid)); 822 // Trigger comment delete event. 823 if (core_component::is_core_subsystem($this->component)) { 824 $eventclassname = '\\core\\event\\' . $this->component . '_comment_deleted'; 825 } else { 826 $eventclassname = '\\' . $this->component . '\\event\comment_deleted'; 827 } 828 if (class_exists($eventclassname)) { 829 $event = $eventclassname::create( 830 array( 831 'context' => $this->context, 832 'objectid' => $commentid, 833 'other' => array( 834 'itemid' => $this->itemid 835 ) 836 )); 837 $event->add_record_snapshot('comments', $comment); 838 $event->trigger(); 839 } 840 return true; 841 } 842 843 /** 844 * Print comments 845 * 846 * @param int $page 847 * @param bool $return return comments list string or print it out 848 * @param bool $nonjs print nonjs comments list or not? 849 * @return string|void 850 */ 851 public function print_comments($page = 0, $return = true, $nonjs = true) { 852 global $DB, $CFG, $PAGE; 853 854 if (!$this->can_view()) { 855 return ''; 856 } 857 858 if (!(self::$comment_itemid == $this->itemid && 859 self::$comment_context == $this->context->id && 860 self::$comment_area == $this->commentarea && 861 self::$comment_component == $this->component 862 )) { 863 $page = 0; 864 } 865 $comments = $this->get_comments($page); 866 867 $html = ''; 868 if ($nonjs) { 869 $html .= html_writer::tag('h3', get_string('comments')); 870 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list')); 871 } 872 // Reverse the comments array to display them in the correct direction 873 foreach (array_reverse($comments) as $cmt) { 874 $html .= html_writer::tag('li', $this->print_comment($cmt, $nonjs), array('id' => 'comment-'.$cmt->id.'-'.$this->cid)); 875 } 876 if ($nonjs) { 877 $html .= html_writer::end_tag('ul'); 878 $html .= $this->get_pagination($page); 879 } 880 if ($nonjs && $this->can_post()) { 881 // Form to add comments 882 $html .= html_writer::start_tag('form', array('method' => 'post', 'action' => new moodle_url('/comment/comment_post.php'))); 883 // Comment parameters 884 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'contextid', 'value' => $this->contextid)); 885 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'add')); 886 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'area', 'value' => $this->commentarea)); 887 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'component', 'value' => $this->component)); 888 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'itemid', 'value' => $this->itemid)); 889 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'courseid', 'value' => $this->courseid)); 890 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); 891 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'returnurl', 'value' => $PAGE->url)); 892 // Textarea for the actual comment 893 $html .= html_writer::tag('textarea', '', array('name' => 'content', 'rows' => 2)); 894 // Submit button to add the comment 895 $html .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('submit'))); 896 $html .= html_writer::end_tag('form'); 897 } 898 if ($return) { 899 return $html; 900 } else { 901 echo $html; 902 } 903 } 904 905 /** 906 * Returns an array containing comments in HTML format. 907 * 908 * @global core_renderer $OUTPUT 909 * @param stdClass $cmt { 910 * id => int comment id 911 * content => string comment content 912 * format => int comment text format 913 * timecreated => int comment's timecreated 914 * profileurl => string link to user profile 915 * fullname => comment author's full name 916 * avatar => string user's avatar 917 * delete => boolean does user have permission to delete comment? 918 * } 919 * @param bool $nonjs 920 * @return array 921 */ 922 public function print_comment($cmt, $nonjs = true) { 923 global $OUTPUT; 924 $patterns = array(); 925 $replacements = array(); 926 927 if (!empty($cmt->delete) && empty($nonjs)) { 928 $strdelete = get_string('deletecommentbyon', 'moodle', (object)['user' => $cmt->fullname, 'time' => $cmt->time]); 929 $deletelink = html_writer::start_tag('div', array('class'=>'comment-delete')); 930 $deletelink .= html_writer::start_tag('a', array('href' => '#', 'id' => 'comment-delete-'.$this->cid.'-'.$cmt->id, 931 'class' => 'icon-no-margin', 'title' => $strdelete)); 932 933 $deletelink .= $OUTPUT->pix_icon('t/delete', $strdelete); 934 $deletelink .= html_writer::end_tag('a'); 935 $deletelink .= html_writer::end_tag('div'); 936 $cmt->content = $deletelink . $cmt->content; 937 } 938 $patterns[] = '___picture___'; 939 $patterns[] = '___name___'; 940 $patterns[] = '___content___'; 941 $patterns[] = '___time___'; 942 $replacements[] = $cmt->avatar; 943 $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname); 944 $replacements[] = $cmt->content; 945 $replacements[] = $cmt->time; 946 947 // use html template to format a single comment. 948 return str_replace($patterns, $replacements, $this->template); 949 } 950 951 /** 952 * Revoke validate callbacks 953 * 954 * @param stdClass $params addtionall parameters need to add to callbacks 955 */ 956 protected function validate($params=array()) { 957 foreach ($params as $key=>$value) { 958 $this->comment_param->$key = $value; 959 } 960 $validation = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'validate', array($this->comment_param), false); 961 if (!$validation) { 962 throw new comment_exception('invalidcommentparam'); 963 } 964 } 965 966 /** 967 * Returns true if the user is able to view comments 968 * @return bool 969 */ 970 public function can_view() { 971 $this->validate(); 972 return !empty($this->viewcap); 973 } 974 975 /** 976 * Returns true if the user can add comments against this comment description 977 * @return bool 978 */ 979 public function can_post() { 980 $this->validate(); 981 return isloggedin() && !empty($this->postcap); 982 } 983 984 /** 985 * Returns true if the user can delete this comment. 986 * 987 * The user can delete comments if it is one they posted and they can still make posts, 988 * or they have the capability to delete comments. 989 * 990 * A database call is avoided if a comment record is passed. 991 * 992 * @param int|stdClass $comment The id of a comment, or a comment record. 993 * @return bool 994 */ 995 public function can_delete($comment) { 996 global $USER, $DB; 997 if (is_object($comment)) { 998 $commentid = $comment->id; 999 } else { 1000 $commentid = $comment; 1001 } 1002 1003 $this->validate(array('commentid'=>$commentid)); 1004 1005 if (!is_object($comment)) { 1006 // Get the comment record from the database. 1007 $comment = $DB->get_record('comments', array('id' => $commentid), 'id, userid', MUST_EXIST); 1008 } 1009 1010 $hascapability = has_capability('moodle/comment:delete', $this->context); 1011 $owncomment = $USER->id == $comment->userid; 1012 1013 return ($hascapability || ($owncomment && $this->can_post())); 1014 } 1015 1016 /** 1017 * Returns the component associated with the comment. 1018 * 1019 * @return string 1020 */ 1021 public function get_component() { 1022 return $this->component; 1023 } 1024 1025 /** 1026 * Do not call! I am a deprecated method because of the typo in my name. 1027 * @deprecated since 2.9 1028 * @see comment::get_component() 1029 * @return string 1030 */ 1031 public function get_compontent() { 1032 return $this->get_component(); 1033 } 1034 1035 /** 1036 * Returns the context associated with the comment 1037 * @return stdClass 1038 */ 1039 public function get_context() { 1040 return $this->context; 1041 } 1042 1043 /** 1044 * Returns the course id associated with the comment 1045 * @return int 1046 */ 1047 public function get_courseid() { 1048 return $this->courseid; 1049 } 1050 1051 /** 1052 * Returns the course module associated with the comment 1053 * 1054 * @return stdClass 1055 */ 1056 public function get_cm() { 1057 return $this->cm; 1058 } 1059 1060 /** 1061 * Returns the item id associated with the comment 1062 * 1063 * @return int 1064 */ 1065 public function get_itemid() { 1066 return $this->itemid; 1067 } 1068 1069 /** 1070 * Returns the comment area associated with the commentarea 1071 * 1072 * @return stdClass 1073 */ 1074 public function get_commentarea() { 1075 return $this->commentarea; 1076 } 1077 1078 /** 1079 * Make the comments textarea fullwidth. 1080 * 1081 * @since 2.8.1 + 2.7.4 1082 * @param bool $fullwidth 1083 */ 1084 public function set_fullwidth($fullwidth = true) { 1085 $this->fullwidth = (bool)$fullwidth; 1086 } 1087 1088 /** 1089 * Return the template. 1090 * 1091 * @since 3.1 1092 * @return string 1093 */ 1094 public function get_template() { 1095 return $this->template; 1096 } 1097 1098 /** 1099 * Return the cid. 1100 * 1101 * @since 3.1 1102 * @return string 1103 */ 1104 public function get_cid() { 1105 return $this->cid; 1106 } 1107 1108 /** 1109 * Return the link text. 1110 * 1111 * @since 3.1 1112 * @return string 1113 */ 1114 public function get_linktext() { 1115 return $this->linktext; 1116 } 1117 1118 /** 1119 * Return no toggle. 1120 * 1121 * @since 3.1 1122 * @return bool 1123 */ 1124 public function get_notoggle() { 1125 return $this->notoggle; 1126 } 1127 1128 /** 1129 * Return display total count. 1130 * 1131 * @since 3.1 1132 * @return bool 1133 */ 1134 public function get_displaytotalcount() { 1135 return $this->displaytotalcount; 1136 } 1137 1138 /** 1139 * Return display cancel. 1140 * 1141 * @since 3.1 1142 * @return bool 1143 */ 1144 public function get_displaycancel() { 1145 return $this->displaycancel; 1146 } 1147 1148 /** 1149 * Return fullwidth. 1150 * 1151 * @since 3.1 1152 * @return bool 1153 */ 1154 public function get_fullwidth() { 1155 return $this->fullwidth; 1156 } 1157 1158 /** 1159 * Return autostart. 1160 * 1161 * @since 3.1 1162 * @return bool 1163 */ 1164 public function get_autostart() { 1165 return $this->autostart; 1166 } 1167 1168 } 1169 1170 /** 1171 * Comment exception class 1172 * 1173 * @package core 1174 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} 1175 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1176 */ 1177 class comment_exception extends moodle_exception { 1178 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body