Merge branch 'MDL-50062-28' of git://github.com/FMCorz/moodle into MOODLE_28_STABLE
[moodle.git] / comment / lib.php
blob6adc52f75d71c8011fe0b1e6b7bed8f77862c81f
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.
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/>.
17 /**
18 * Functions and classes for commenting
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
24 defined('MOODLE_INTERNAL') || die();
26 /**
27 * Comment is helper class to add/delete comments anywhere in moodle
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
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 /** @var string The component that this comment is for. It is STRONGLY recommended to set this. */
52 private $component;
53 /** @var string This is calculated by normalising the component */
54 private $pluginname;
55 /** @var string This is calculated by normalising the component */
56 private $plugintype;
57 /** @var bool Whether the user has the required capabilities/permissions to view comments. */
58 private $viewcap = false;
59 /** @var bool Whether the user has the required capabilities/permissions to post comments. */
60 private $postcap = false;
61 /** @var string to customize link text */
62 private $linktext;
63 /** @var bool If set to true then comment sections won't be able to be opened and closed instead they will always be visible. */
64 protected $notoggle = false;
65 /** @var bool If set to true comments are automatically loaded as soon as the page loads. */
66 protected $autostart = false;
67 /** @var bool If set to true the total count of comments is displayed when displaying comments. */
68 protected $displaytotalcount = false;
69 /** @var bool If set to true a cancel button will be shown on the form used to submit comments. */
70 protected $displaycancel = false;
71 /** @var int The number of comments associated with this comments params */
72 protected $totalcommentcount = null;
74 /**
75 * Set to true to remove the col attribute from the textarea making it full width.
76 * @var bool
78 protected $fullwidth = false;
80 /** @var bool Use non-javascript UI */
81 private static $nonjs = false;
82 /** @var int comment itemid used in non-javascript UI */
83 private static $comment_itemid = null;
84 /** @var int comment context used in non-javascript UI */
85 private static $comment_context = null;
86 /** @var string comment area used in non-javascript UI */
87 private static $comment_area = null;
88 /** @var string comment page used in non-javascript UI */
89 private static $comment_page = null;
90 /** @var string comment itemid component in non-javascript UI */
91 private static $comment_component = null;
93 /**
94 * Construct function of comment class, initialise
95 * class members
97 * @param stdClass $options {
98 * context => context context to use for the comment [required]
99 * component => string which plugin will comment being added to [required]
100 * itemid => int the id of the associated item (forum post, glossary item etc) [required]
101 * area => string comment area
102 * cm => stdClass course module
103 * course => course course object
104 * client_id => string an unique id to identify comment area
105 * autostart => boolean automatically expend comments
106 * showcount => boolean display the number of comments
107 * displaycancel => boolean display cancel button
108 * notoggle => boolean don't show/hide button
109 * linktext => string title of show/hide button
112 public function __construct(stdClass $options) {
113 $this->viewcap = false;
114 $this->postcap = false;
116 // setup client_id
117 if (!empty($options->client_id)) {
118 $this->cid = $options->client_id;
119 } else {
120 $this->cid = uniqid();
123 // setup context
124 if (!empty($options->context)) {
125 $this->context = $options->context;
126 $this->contextid = $this->context->id;
127 } else if(!empty($options->contextid)) {
128 $this->contextid = $options->contextid;
129 $this->context = context::instance_by_id($this->contextid);
130 } else {
131 print_error('invalidcontext');
134 if (!empty($options->component)) {
135 // set and validate component
136 $this->set_component($options->component);
137 } else {
138 // component cannot be empty
139 throw new comment_exception('invalidcomponent');
142 // setup course
143 // course will be used to generate user profile link
144 if (!empty($options->course)) {
145 $this->courseid = $options->course->id;
146 } else if (!empty($options->courseid)) {
147 $this->courseid = $options->courseid;
148 } else {
149 $this->courseid = SITEID;
152 // setup coursemodule
153 if (!empty($options->cm)) {
154 $this->cm = $options->cm;
155 } else {
156 $this->cm = null;
159 // setup commentarea
160 if (!empty($options->area)) {
161 $this->commentarea = $options->area;
164 // setup itemid
165 if (!empty($options->itemid)) {
166 $this->itemid = $options->itemid;
167 } else {
168 $this->itemid = 0;
171 // setup customized linktext
172 if (!empty($options->linktext)) {
173 $this->linktext = $options->linktext;
174 } else {
175 $this->linktext = get_string('comments');
178 // setup options for callback functions
179 $this->comment_param = new stdClass();
180 $this->comment_param->context = $this->context;
181 $this->comment_param->courseid = $this->courseid;
182 $this->comment_param->cm = $this->cm;
183 $this->comment_param->commentarea = $this->commentarea;
184 $this->comment_param->itemid = $this->itemid;
186 // setup notoggle
187 if (!empty($options->notoggle)) {
188 $this->set_notoggle($options->notoggle);
191 // setup notoggle
192 if (!empty($options->autostart)) {
193 $this->set_autostart($options->autostart);
196 // setup displaycancel
197 if (!empty($options->displaycancel)) {
198 $this->set_displaycancel($options->displaycancel);
201 // setup displaytotalcount
202 if (!empty($options->showcount)) {
203 $this->set_displaytotalcount($options->showcount);
206 // setting post and view permissions
207 $this->check_permissions();
209 // load template
210 $this->template = html_writer::start_tag('div', array('class' => 'comment-message'));
212 $this->template .= html_writer::start_tag('div', array('class' => 'comment-message-meta'));
214 $this->template .= html_writer::tag('span', '___picture___', array('class' => 'picture'));
215 $this->template .= html_writer::tag('span', '___name___', array('class' => 'user')) . ' - ';
216 $this->template .= html_writer::tag('span', '___time___', array('class' => 'time'));
218 $this->template .= html_writer::end_tag('div'); // .comment-message-meta
219 $this->template .= html_writer::tag('div', '___content___', array('class' => 'text'));
221 $this->template .= html_writer::end_tag('div'); // .comment-message
223 if (!empty($this->plugintype)) {
224 $this->template = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'template', array($this->comment_param), $this->template);
227 unset($options);
231 * Receive nonjs comment parameters
233 * @param moodle_page $page The page object to initialise comments within
234 * If not provided the global $PAGE is used
236 public static function init(moodle_page $page = null) {
237 global $PAGE;
239 if (empty($page)) {
240 $page = $PAGE;
242 // setup variables for non-js interface
243 self::$nonjs = optional_param('nonjscomment', '', PARAM_ALPHANUM);
244 self::$comment_itemid = optional_param('comment_itemid', '', PARAM_INT);
245 self::$comment_context = optional_param('comment_context', '', PARAM_INT);
246 self::$comment_page = optional_param('comment_page', '', PARAM_INT);
247 self::$comment_area = optional_param('comment_area', '', PARAM_AREA);
249 $page->requires->strings_for_js(array(
250 'addcomment',
251 'comments',
252 'commentscount',
253 'commentsrequirelogin',
254 'deletecomment',
256 'moodle'
261 * Sets the component.
263 * This method shouldn't be public, changing the component once it has been set potentially
264 * invalidates permission checks.
265 * A coding_error is now thrown if code attempts to change the component.
267 * @param string $component
269 public function set_component($component) {
270 if (!empty($this->component) && $this->component !== $component) {
271 throw new coding_exception('You cannot change the component of a comment once it has been set');
273 $this->component = $component;
274 list($this->plugintype, $this->pluginname) = core_component::normalize_component($component);
278 * Determines if the user can view the comment.
280 * @param bool $value
282 public function set_view_permission($value) {
283 $this->viewcap = (bool)$value;
287 * Determines if the user can post a comment
289 * @param bool $value
291 public function set_post_permission($value) {
292 $this->postcap = (bool)$value;
296 * check posting comments permission
297 * It will check based on user roles and ask modules
298 * If you need to check permission by modules, a
299 * function named $pluginname_check_comment_post must be implemented
301 private function check_permissions() {
302 $this->postcap = has_capability('moodle/comment:post', $this->context);
303 $this->viewcap = has_capability('moodle/comment:view', $this->context);
304 if (!empty($this->plugintype)) {
305 $permissions = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'permissions', array($this->comment_param), array('post'=>false, 'view'=>false));
306 $this->postcap = $this->postcap && $permissions['post'];
307 $this->viewcap = $this->viewcap && $permissions['view'];
312 * Gets a link for this page that will work with JS disabled.
314 * @global moodle_page $PAGE
315 * @param moodle_page $page
316 * @return moodle_url
318 public function get_nojslink(moodle_page $page = null) {
319 if ($page === null) {
320 global $PAGE;
321 $page = $PAGE;
324 $link = new moodle_url($page->url, array(
325 'nonjscomment' => true,
326 'comment_itemid' => $this->itemid,
327 'comment_context' => $this->context->id,
328 'comment_area' => $this->commentarea,
330 $link->remove_params(array('comment_page'));
331 return $link;
335 * Sets the value of the notoggle option.
337 * If set to true then the user will not be able to expand and collase
338 * the comment section.
340 * @param bool $newvalue
342 public function set_notoggle($newvalue = true) {
343 $this->notoggle = (bool)$newvalue;
347 * Sets the value of the autostart option.
349 * If set to true then the comments will be loaded during page load.
350 * Normally this happens only once the user expands the comment section.
352 * @param bool $newvalue
354 public function set_autostart($newvalue = true) {
355 $this->autostart = (bool)$newvalue;
359 * Sets the displaycancel option
361 * If set to true then a cancel button will be shown when using the form
362 * to post comments.
364 * @param bool $newvalue
366 public function set_displaycancel($newvalue = true) {
367 $this->displaycancel = (bool)$newvalue;
371 * Sets the displaytotalcount option
373 * If set to true then the total number of comments will be displayed
374 * when printing comments.
376 * @param bool $newvalue
378 public function set_displaytotalcount($newvalue = true) {
379 $this->displaytotalcount = (bool)$newvalue;
383 * Initialises the JavaScript that enchances the comment API.
385 * @param moodle_page $page The moodle page object that the JavaScript should be
386 * initialised for.
388 public function initialise_javascript(moodle_page $page) {
390 $options = new stdClass;
391 $options->client_id = $this->cid;
392 $options->commentarea = $this->commentarea;
393 $options->itemid = $this->itemid;
394 $options->page = 0;
395 $options->courseid = $this->courseid;
396 $options->contextid = $this->contextid;
397 $options->component = $this->component;
398 $options->notoggle = $this->notoggle;
399 $options->autostart = $this->autostart;
401 $page->requires->js_init_call('M.core_comment.init', array($options), true);
403 return true;
407 * Prepare comment code in html
408 * @param boolean $return
409 * @return string|void
411 public function output($return = true) {
412 global $PAGE, $OUTPUT;
413 static $template_printed;
415 $this->initialise_javascript($PAGE);
417 if (!empty(self::$nonjs)) {
418 // return non js comments interface
419 return $this->print_comments(self::$comment_page, $return, true);
422 $html = '';
424 // print html template
425 // Javascript will use the template to render new comments
426 if (empty($template_printed) && $this->can_view()) {
427 $html .= html_writer::tag('div', $this->template, array('style' => 'display:none', 'id' => 'cmt-tmpl'));
428 $template_printed = true;
431 if ($this->can_view()) {
432 // print commenting icon and tooltip
433 $html .= html_writer::start_tag('div', array('class' => 'mdl-left'));
434 $html .= html_writer::link($this->get_nojslink($PAGE), get_string('showcommentsnonjs'), array('class' => 'showcommentsnonjs'));
436 if (!$this->notoggle) {
437 // If toggling is enabled (notoggle=false) then print the controls to toggle
438 // comments open and closed
439 $countstring = '';
440 if ($this->displaytotalcount) {
441 $countstring = '('.$this->count().')';
443 $collapsedimage= 't/collapsed';
444 if (right_to_left()) {
445 $collapsedimage= 't/collapsed_rtl';
446 } else {
447 $collapsedimage= 't/collapsed';
449 $html .= html_writer::start_tag('a', array('class' => 'comment-link', 'id' => 'comment-link-'.$this->cid, 'href' => '#'));
450 $html .= html_writer::empty_tag('img', array('id' => 'comment-img-'.$this->cid, 'src' => $OUTPUT->pix_url($collapsedimage), 'alt' => $this->linktext, 'title' => $this->linktext));
451 $html .= html_writer::tag('span', $this->linktext.' '.$countstring, array('id' => 'comment-link-text-'.$this->cid));
452 $html .= html_writer::end_tag('a');
455 $html .= html_writer::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid, 'class' => 'comment-ctrl'));
457 if ($this->autostart) {
458 // If autostart has been enabled print the comments list immediatly
459 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list comments-loaded'));
460 $html .= html_writer::tag('li', '', array('class' => 'first'));
461 $html .= $this->print_comments(0, true, false);
462 $html .= html_writer::end_tag('ul'); // .comment-list
463 $html .= $this->get_pagination(0);
464 } else {
465 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list'));
466 $html .= html_writer::tag('li', '', array('class' => 'first'));
467 $html .= html_writer::end_tag('ul'); // .comment-list
468 $html .= html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
471 if ($this->can_post()) {
472 // print posting textarea
473 $textareaattrs = array(
474 'name' => 'content',
475 'rows' => 2,
476 'id' => 'dlg-content-'.$this->cid
478 if (!$this->fullwidth) {
479 $textareaattrs['cols'] = '20';
480 } else {
481 $textareaattrs['class'] = 'fullwidth';
484 $html .= html_writer::start_tag('div', array('class' => 'comment-area'));
485 $html .= html_writer::start_tag('div', array('class' => 'db'));
486 $html .= html_writer::tag('textarea', '', $textareaattrs);
487 $html .= html_writer::end_tag('div'); // .db
489 $html .= html_writer::start_tag('div', array('class' => 'fd', 'id' => 'comment-action-'.$this->cid));
490 $html .= html_writer::link('#', get_string('savecomment'), array('id' => 'comment-action-post-'.$this->cid));
492 if ($this->displaycancel) {
493 $html .= html_writer::tag('span', ' | ');
494 $html .= html_writer::link('#', get_string('cancel'), array('id' => 'comment-action-cancel-'.$this->cid));
497 $html .= html_writer::end_tag('div'); // .fd
498 $html .= html_writer::end_tag('div'); // .comment-area
499 $html .= html_writer::tag('div', '', array('class' => 'clearer'));
502 $html .= html_writer::end_tag('div'); // .comment-ctrl
503 $html .= html_writer::end_tag('div'); // .mdl-left
504 } else {
505 $html = '';
508 if ($return) {
509 return $html;
510 } else {
511 echo $html;
516 * Return matched comments
518 * @param int $page
519 * @return array
521 public function get_comments($page = '') {
522 global $DB, $CFG, $USER, $OUTPUT;
523 if (!$this->can_view()) {
524 return false;
526 if (!is_numeric($page)) {
527 $page = 0;
529 $params = array();
530 $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
531 $start = $page * $perpage;
532 $ufields = user_picture::fields('u');
533 $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
534 FROM {comments} c
535 JOIN {user} u ON u.id = c.userid
536 WHERE c.contextid = :contextid AND c.commentarea = :commentarea AND c.itemid = :itemid
537 ORDER BY c.timecreated DESC";
538 $params['contextid'] = $this->contextid;
539 $params['commentarea'] = $this->commentarea;
540 $params['itemid'] = $this->itemid;
542 $comments = array();
543 $formatoptions = array('overflowdiv' => true);
544 $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
545 foreach ($rs as $u) {
546 $c = new stdClass();
547 $c->id = $u->cid;
548 $c->content = $u->ccontent;
549 $c->format = $u->cformat;
550 $c->timecreated = $u->ctimecreated;
551 $c->strftimeformat = get_string('strftimerecentfull', 'langconfig');
552 $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
553 $c->profileurl = $url->out(false); // URL should not be escaped just yet.
554 $c->fullname = fullname($u);
555 $c->time = userdate($c->timecreated, $c->strftimeformat);
556 $c->content = format_text($c->content, $c->format, $formatoptions);
557 $c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
558 $c->userid = $u->id;
560 $candelete = $this->can_delete($c->id);
561 if (($USER->id == $u->id) || !empty($candelete)) {
562 $c->delete = true;
564 $comments[] = $c;
566 $rs->close();
568 if (!empty($this->plugintype)) {
569 // moodle module will filter comments
570 $comments = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'display', array($comments, $this->comment_param), $comments);
573 return $comments;
577 * Returns the number of comments associated with the details of this object
579 * @global moodle_database $DB
580 * @return int
582 public function count() {
583 global $DB;
584 if ($this->totalcommentcount === null) {
585 $this->totalcommentcount = $DB->count_records('comments', array('itemid' => $this->itemid, 'commentarea' => $this->commentarea, 'contextid' => $this->context->id));
587 return $this->totalcommentcount;
591 * Returns HTML to display a pagination bar
593 * @global stdClass $CFG
594 * @global core_renderer $OUTPUT
595 * @param int $page
596 * @return string
598 public function get_pagination($page = 0) {
599 global $CFG, $OUTPUT;
600 $count = $this->count();
601 $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
602 $pages = (int)ceil($count/$perpage);
603 if ($pages == 1 || $pages == 0) {
604 return html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
606 if (!empty(self::$nonjs)) {
607 // used in non-js interface
608 return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page');
609 } else {
610 // return ajax paging bar
611 $str = '';
612 $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid.'">';
613 for ($p=0; $p<$pages; $p++) {
614 if ($p == $page) {
615 $class = 'curpage';
616 } else {
617 $class = 'pageno';
619 $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid.'-'.$p.'">'.($p+1).'</a> ';
621 $str .= '</div>';
623 return $str;
627 * Add a new comment
629 * @global moodle_database $DB
630 * @param string $content
631 * @param int $format
632 * @return stdClass
634 public function add($content, $format = FORMAT_MOODLE) {
635 global $CFG, $DB, $USER, $OUTPUT;
636 if (!$this->can_post()) {
637 throw new comment_exception('nopermissiontocomment');
639 $now = time();
640 $newcmt = new stdClass;
641 $newcmt->contextid = $this->contextid;
642 $newcmt->commentarea = $this->commentarea;
643 $newcmt->itemid = $this->itemid;
644 $newcmt->content = $content;
645 $newcmt->format = $format;
646 $newcmt->userid = $USER->id;
647 $newcmt->timecreated = $now;
649 // This callback allow module to modify the content of comment, such as filter or replacement
650 plugin_callback($this->plugintype, $this->pluginname, 'comment', 'add', array(&$newcmt, $this->comment_param));
652 $cmt_id = $DB->insert_record('comments', $newcmt);
653 if (!empty($cmt_id)) {
654 $newcmt->id = $cmt_id;
655 $newcmt->strftimeformat = get_string('strftimerecent', 'langconfig');
656 $newcmt->fullname = fullname($USER);
657 $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
658 $newcmt->profileurl = $url->out();
659 $newcmt->content = format_text($newcmt->content, $newcmt->format, array('overflowdiv'=>true));
660 $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
662 $commentlist = array($newcmt);
664 if (!empty($this->plugintype)) {
665 // Call the display callback to allow the plugin to format the newly added comment.
666 $commentlist = plugin_callback($this->plugintype,
667 $this->pluginname,
668 'comment',
669 'display',
670 array($commentlist, $this->comment_param),
671 $commentlist);
672 $newcmt = $commentlist[0];
674 $newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat);
676 // Trigger comment created event.
677 if (core_component::is_core_subsystem($this->component)) {
678 $eventclassname = '\\core\\event\\' . $this->component . '_comment_created';
679 } else {
680 $eventclassname = '\\' . $this->component . '\\event\comment_created';
682 if (class_exists($eventclassname)) {
683 $event = $eventclassname::create(
684 array(
685 'context' => $this->context,
686 'objectid' => $newcmt->id,
687 'other' => array(
688 'itemid' => $this->itemid
691 $event->trigger();
694 return $newcmt;
695 } else {
696 throw new comment_exception('dbupdatefailed');
701 * delete by context, commentarea and itemid
702 * @param stdClass|array $param {
703 * contextid => int the context in which the comments exist [required]
704 * commentarea => string the comment area [optional]
705 * itemid => int comment itemid [optional]
707 * @return boolean
709 public static function delete_comments($param) {
710 global $DB;
711 $param = (array)$param;
712 if (empty($param['contextid'])) {
713 return false;
715 $DB->delete_records('comments', $param);
716 return true;
720 * Delete page_comments in whole course, used by course reset
722 * @param stdClass $context course context
724 public static function reset_course_page_comments($context) {
725 global $DB;
726 $contexts = array();
727 $contexts[] = $context->id;
728 $children = $context->get_child_contexts();
729 foreach ($children as $c) {
730 $contexts[] = $c->id;
732 list($ids, $params) = $DB->get_in_or_equal($contexts);
733 $DB->delete_records_select('comments', "commentarea='page_comments' AND contextid $ids", $params);
737 * Delete a comment
739 * @param int $commentid
740 * @return bool
742 public function delete($commentid) {
743 global $DB, $USER;
744 $candelete = has_capability('moodle/comment:delete', $this->context);
745 if (!$comment = $DB->get_record('comments', array('id'=>$commentid))) {
746 throw new comment_exception('dbupdatefailed');
748 if (!($USER->id == $comment->userid || !empty($candelete))) {
749 throw new comment_exception('nopermissiontocomment');
751 $DB->delete_records('comments', array('id'=>$commentid));
752 // Trigger comment delete event.
753 if (core_component::is_core_subsystem($this->component)) {
754 $eventclassname = '\\core\\event\\' . $this->component . '_comment_deleted';
755 } else {
756 $eventclassname = '\\' . $this->component . '\\event\comment_deleted';
758 if (class_exists($eventclassname)) {
759 $event = $eventclassname::create(
760 array(
761 'context' => $this->context,
762 'objectid' => $commentid,
763 'other' => array(
764 'itemid' => $this->itemid
767 $event->add_record_snapshot('comments', $comment);
768 $event->trigger();
770 return true;
774 * Print comments
776 * @param int $page
777 * @param bool $return return comments list string or print it out
778 * @param bool $nonjs print nonjs comments list or not?
779 * @return string|void
781 public function print_comments($page = 0, $return = true, $nonjs = true) {
782 global $DB, $CFG, $PAGE;
784 if (!$this->can_view()) {
785 return '';
788 $html = '';
789 if (!(self::$comment_itemid == $this->itemid &&
790 self::$comment_context == $this->context->id &&
791 self::$comment_area == $this->commentarea)) {
792 $page = 0;
794 $comments = $this->get_comments($page);
796 $html = '';
797 if ($nonjs) {
798 $html .= html_writer::tag('h3', get_string('comments'));
799 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list'));
801 // Reverse the comments array to display them in the correct direction
802 foreach (array_reverse($comments) as $cmt) {
803 $html .= html_writer::tag('li', $this->print_comment($cmt, $nonjs), array('id' => 'comment-'.$cmt->id.'-'.$this->cid));
805 if ($nonjs) {
806 $html .= html_writer::end_tag('ul');
807 $html .= $this->get_pagination($page);
809 if ($nonjs && $this->can_post()) {
810 // Form to add comments
811 $html .= html_writer::start_tag('form', array('method' => 'post', 'action' => new moodle_url('/comment/comment_post.php')));
812 // Comment parameters
813 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'contextid', 'value' => $this->contextid));
814 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'add'));
815 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'area', 'value' => $this->commentarea));
816 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'component', 'value' => $this->component));
817 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'itemid', 'value' => $this->itemid));
818 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'courseid', 'value' => $this->courseid));
819 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
820 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'returnurl', 'value' => $PAGE->url));
821 // Textarea for the actual comment
822 $html .= html_writer::tag('textarea', '', array('name' => 'content', 'rows' => 2));
823 // Submit button to add the comment
824 $html .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('submit')));
825 $html .= html_writer::end_tag('form');
827 if ($return) {
828 return $html;
829 } else {
830 echo $html;
835 * Returns an array containing comments in HTML format.
837 * @global core_renderer $OUTPUT
838 * @param stdClass $cmt {
839 * id => int comment id
840 * content => string comment content
841 * format => int comment text format
842 * timecreated => int comment's timecreated
843 * profileurl => string link to user profile
844 * fullname => comment author's full name
845 * avatar => string user's avatar
846 * delete => boolean does user have permission to delete comment?
848 * @param bool $nonjs
849 * @return array
851 public function print_comment($cmt, $nonjs = true) {
852 global $OUTPUT;
853 $patterns = array();
854 $replacements = array();
856 if (!empty($cmt->delete) && empty($nonjs)) {
857 $deletelink = html_writer::start_tag('div', array('class'=>'comment-delete'));
858 $deletelink .= html_writer::start_tag('a', array('href' => '#', 'id' => 'comment-delete-'.$this->cid.'-'.$cmt->id));
859 $deletelink .= $OUTPUT->pix_icon('t/delete', get_string('delete'));
860 $deletelink .= html_writer::end_tag('a');
861 $deletelink .= html_writer::end_tag('div');
862 $cmt->content = $deletelink . $cmt->content;
864 $patterns[] = '___picture___';
865 $patterns[] = '___name___';
866 $patterns[] = '___content___';
867 $patterns[] = '___time___';
868 $replacements[] = $cmt->avatar;
869 $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
870 $replacements[] = $cmt->content;
871 $replacements[] = $cmt->time;
873 // use html template to format a single comment.
874 return str_replace($patterns, $replacements, $this->template);
878 * Revoke validate callbacks
880 * @param stdClass $params addtionall parameters need to add to callbacks
882 protected function validate($params=array()) {
883 foreach ($params as $key=>$value) {
884 $this->comment_param->$key = $value;
886 $validation = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'validate', array($this->comment_param), false);
887 if (!$validation) {
888 throw new comment_exception('invalidcommentparam');
893 * Returns true if the user is able to view comments
894 * @return bool
896 public function can_view() {
897 $this->validate();
898 return !empty($this->viewcap);
902 * Returns true if the user can add comments against this comment description
903 * @return bool
905 public function can_post() {
906 $this->validate();
907 return isloggedin() && !empty($this->postcap);
911 * Returns true if the user can delete this comment
912 * @param int $commentid
913 * @return bool
915 public function can_delete($commentid) {
916 $this->validate(array('commentid'=>$commentid));
917 return has_capability('moodle/comment:delete', $this->context);
921 * Returns the component associated with the comment
922 * @return string
924 public function get_compontent() {
925 return $this->component;
929 * Returns the context associated with the comment
930 * @return stdClass
932 public function get_context() {
933 return $this->context;
937 * Returns the course id associated with the comment
938 * @return int
940 public function get_courseid() {
941 return $this->courseid;
945 * Returns the course module associated with the comment
947 * @return stdClass
949 public function get_cm() {
950 return $this->cm;
954 * Returns the item id associated with the comment
956 * @return int
958 public function get_itemid() {
959 return $this->itemid;
963 * Returns the comment area associated with the commentarea
965 * @return stdClass
967 public function get_commentarea() {
968 return $this->commentarea;
972 * Make the comments textarea fullwidth.
974 * @since 2.8.1 + 2.7.4
975 * @param bool $fullwidth
977 public function set_fullwidth($fullwidth = true) {
978 $this->fullwidth = (bool)$fullwidth;
983 * Comment exception class
985 * @package core
986 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
987 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
989 class comment_exception extends moodle_exception {