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