2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
18 * Functions and classes for commenting
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();
27 * Comment is helper class to add/delete comments anywhere in moodle
31 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 /** @var int there may be several comment box in one page so we need a client_id to recognize them */
37 /** @var string commentarea is used to specify different parts shared the same itemid */
39 /** @var int itemid is used to associate with commenting content */
41 /** @var string this html snippet will be used as a template to build comment content */
43 /** @var int The context id for comments */
45 /** @var stdClass The context itself */
47 /** @var int The course id for comments */
49 /** @var stdClass course module object, only be used to help find pluginname automatically */
52 * The component that this comment is for.
54 * It is STRONGLY recommended to set this.
55 * Added as a database field in 2.9, old comments will have a null component.
60 /** @var string This is calculated by normalising the component */
62 /** @var string This is calculated by normalising the component */
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 */
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;
82 * Set to true to remove the col attribute from the textarea making it full width.
85 protected $fullwidth = false;
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;
103 * Construct function of comment class, initialise
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
121 public function __construct(stdClass
$options) {
122 $this->viewcap
= false;
123 $this->postcap
= false;
126 if (!empty($options->client_id
)) {
127 $this->cid
= $options->client_id
;
129 $this->cid
= uniqid();
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
);
140 throw new \
moodle_exception('invalidcontext');
143 if (!empty($options->component
)) {
144 // set and validate component
145 $this->set_component($options->component
);
147 // component cannot be empty
148 throw new comment_exception('invalidcomponent');
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
;
158 if ($coursecontext = $this->context
->get_course_context(false)) {
159 $this->courseid
= $coursecontext->instanceid
;
161 $this->courseid
= SITEID
;
165 // setup coursemodule
166 if (!empty($options->cm
)) {
167 $this->cm
= $options->cm
;
173 if (!empty($options->area
)) {
174 $this->commentarea
= $options->area
;
178 if (!empty($options->itemid
)) {
179 $this->itemid
= $options->itemid
;
184 // setup customized linktext
185 if (!empty($options->linktext
)) {
186 $this->linktext
= $options->linktext
;
188 $this->linktext
= get_string('comments');
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
;
200 if (!empty($options->notoggle
)) {
201 $this->set_notoggle($options->notoggle
);
205 if (!empty($options->autostart
)) {
206 $this->set_autostart($options->autostart
);
209 // setup displaycancel
210 if (!empty($options->displaycancel
)) {
211 $this->set_displaycancel($options->displaycancel
);
214 // setup displaytotalcount
215 if (!empty($options->showcount
)) {
216 $this->set_displaytotalcount($options->showcount
);
219 // setting post and view permissions
220 $this->check_permissions();
223 $this->template
= html_writer
::start_tag('div', array('class' => 'comment-message'));
225 $this->template
.= html_writer
::start_tag('div', array('class' => 'comment-message-meta me-3'));
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'));
231 $this->template
.= html_writer
::end_tag('div'); // .comment-message-meta
232 $this->template
.= html_writer
::tag('div', '___content___', array('class' => 'text'));
234 $this->template
.= html_writer
::end_tag('div'); // .comment-message
236 if (!empty($this->plugintype
)) {
237 $this->template
= plugin_callback($this->plugintype
, $this->pluginname
, 'comment', 'template', array($this->comment_param
), $this->template
);
244 * Receive nonjs comment parameters
246 * @param moodle_page $page The page object to initialise comments within
247 * If not provided the global $PAGE is used
249 public static function init(?moodle_page
$page = null) {
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
);
263 $page->requires
->strings_for_js(array(
266 'commentsrequirelogin',
274 * Sets the component.
276 * This method shouldn't be public, changing the component once it has been set potentially
277 * invalidates permission checks.
278 * A coding_error is now thrown if code attempts to change the component.
280 * @throws coding_exception if you try to change the component after it has been set.
281 * @param string $component
283 public function set_component($component) {
284 if (!empty($this->component
) && $this->component
!== $component) {
285 throw new coding_exception('You cannot change the component of a comment once it has been set');
287 $this->component
= $component;
288 list($this->plugintype
, $this->pluginname
) = core_component
::normalize_component($component);
292 * Determines if the user can view the comment.
296 public function set_view_permission($value) {
297 $this->viewcap
= (bool)$value;
301 * Determines if the user can post a comment
305 public function set_post_permission($value) {
306 $this->postcap
= (bool)$value;
310 * check posting comments permission
311 * It will check based on user roles and ask modules
312 * If you need to check permission by modules, a
313 * function named $pluginname_check_comment_post must be implemented
315 private function check_permissions() {
316 $this->postcap
= has_capability('moodle/comment:post', $this->context
);
317 $this->viewcap
= has_capability('moodle/comment:view', $this->context
);
318 if (!empty($this->plugintype
)) {
319 $permissions = plugin_callback($this->plugintype
, $this->pluginname
, 'comment', 'permissions', array($this->comment_param
), array('post'=>false, 'view'=>false));
320 $this->postcap
= $this->postcap
&& $permissions['post'];
321 $this->viewcap
= $this->viewcap
&& $permissions['view'];
326 * Gets a link for this page that will work with JS disabled.
328 * @global moodle_page $PAGE
329 * @param moodle_page $page
332 public function get_nojslink(?moodle_page
$page = null) {
333 if ($page === null) {
338 $link = new moodle_url($page->url
, array(
339 'nonjscomment' => true,
340 'comment_itemid' => $this->itemid
,
341 'comment_context' => $this->context
->id
,
342 'comment_component' => $this->get_component(),
343 'comment_area' => $this->commentarea
,
345 $link->remove_params(array('comment_page'));
350 * Sets the value of the notoggle option.
352 * If set to true then the user will not be able to expand and collase
353 * the comment section.
355 * @param bool $newvalue
357 public function set_notoggle($newvalue = true) {
358 $this->notoggle
= (bool)$newvalue;
362 * Sets the value of the autostart option.
364 * If set to true then the comments will be loaded during page load.
365 * Normally this happens only once the user expands the comment section.
367 * @param bool $newvalue
369 public function set_autostart($newvalue = true) {
370 $this->autostart
= (bool)$newvalue;
374 * Sets the displaycancel option
376 * If set to true then a cancel button will be shown when using the form
379 * @param bool $newvalue
381 public function set_displaycancel($newvalue = true) {
382 $this->displaycancel
= (bool)$newvalue;
386 * Sets the displaytotalcount option
388 * If set to true then the total number of comments will be displayed
389 * when printing comments.
391 * @param bool $newvalue
393 public function set_displaytotalcount($newvalue = true) {
394 $this->displaytotalcount
= (bool)$newvalue;
398 * Initialises the JavaScript that enchances the comment API.
400 * @param moodle_page $page The moodle page object that the JavaScript should be
403 public function initialise_javascript(moodle_page
$page) {
405 $options = new stdClass
;
406 $options->client_id
= $this->cid
;
407 $options->commentarea
= $this->commentarea
;
408 $options->itemid
= $this->itemid
;
410 $options->courseid
= $this->courseid
;
411 $options->contextid
= $this->contextid
;
412 $options->component
= $this->component
;
413 $options->notoggle
= $this->notoggle
;
414 $options->autostart
= $this->autostart
;
416 $page->requires
->js_init_call('M.core_comment.init', array($options), true);
422 * Prepare comment code in html
423 * @param boolean $return
424 * @return string|void
426 public function output($return = true) {
427 global $PAGE, $OUTPUT;
428 static $template_printed;
430 $this->initialise_javascript($PAGE);
432 if (!empty(self
::$nonjs)) {
433 // return non js comments interface
434 return $this->print_comments(self
::$comment_page, $return, true);
439 // print html template
440 // Javascript will use the template to render new comments
441 if (empty($template_printed) && $this->can_view()) {
442 $html .= html_writer
::tag('div', $this->template
, array('style' => 'display:none', 'id' => 'cmt-tmpl'));
443 $template_printed = true;
446 if ($this->can_view()) {
447 // print commenting icon and tooltip
448 $html .= html_writer
::start_tag('div', array('class' => 'mdl-left'));
449 $html .= html_writer
::link($this->get_nojslink($PAGE), get_string('showcommentsnonjs'), array('class' => 'showcommentsnonjs'));
451 if (!$this->notoggle
) {
452 // If toggling is enabled (notoggle=false) then print the controls to toggle
453 // comments open and closed
455 if ($this->displaytotalcount
) {
456 $countstring = '(' . html_writer
::span($this->count(), 'comment-link-count') . ')';
458 $collapsedimage= 't/collapsed';
459 if (right_to_left()) {
460 $collapsedimage= 't/collapsed_rtl';
462 $collapsedimage= 't/collapsed';
464 $html .= html_writer
::start_tag('a', array(
465 'class' => 'comment-link',
466 'id' => 'comment-link-'.$this->cid
,
469 'aria-expanded' => 'false')
471 $html .= $OUTPUT->pix_icon($collapsedimage, $this->linktext
);
472 $html .= html_writer
::tag('span', $this->linktext
.' '.$countstring, array('id' => 'comment-link-text-'.$this->cid
));
473 $html .= html_writer
::end_tag('a');
476 $html .= html_writer
::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid
, 'class' => 'comment-ctrl'));
478 if ($this->autostart
) {
479 // If autostart has been enabled print the comments list immediatly
480 $html .= html_writer
::start_tag('ul', array('id' => 'comment-list-'.$this->cid
, 'class' => 'comment-list comments-loaded'));
481 $html .= html_writer
::tag('li', '', array('class' => 'first'));
482 $html .= $this->print_comments(0, true, false);
483 $html .= html_writer
::end_tag('ul'); // .comment-list
484 $html .= $this->get_pagination(0);
486 $html .= html_writer
::start_tag('ul', array('id' => 'comment-list-'.$this->cid
, 'class' => 'comment-list'));
487 $html .= html_writer
::tag('li', '', array('class' => 'first'));
488 $html .= html_writer
::end_tag('ul'); // .comment-list
489 $html .= html_writer
::tag('div', '', array('id' => 'comment-pagination-'.$this->cid
, 'class' => 'comment-pagination'));
492 if ($this->can_post()) {
493 // print posting textarea
494 $textareaattrs = array(
497 'id' => 'dlg-content-'.$this->cid
,
498 'aria-label' => get_string('addcomment')
500 if (!$this->fullwidth
) {
501 $textareaattrs['cols'] = '20';
503 $textareaattrs['class'] = 'fullwidth';
506 $html .= html_writer
::start_tag('div', array('class' => 'comment-area'));
507 $html .= html_writer
::start_tag('div', array('class' => 'db'));
508 $html .= html_writer
::tag('label',
509 get_string('comment', 'comment'),
510 ['for' => 'dlg-content-'.$this->cid
, 'class' => 'sr-only']);
511 $html .= html_writer
::tag('textarea', '', $textareaattrs);
512 $html .= html_writer
::end_tag('div'); // .db
514 $html .= html_writer
::start_tag('div', array('class' => 'fd', 'id' => 'comment-action-'.$this->cid
));
515 $html .= html_writer
::link('#', get_string('savecomment'), array('id' => 'comment-action-post-'.$this->cid
));
517 if ($this->displaycancel
) {
518 $html .= html_writer
::tag('span', ' | ');
519 $html .= html_writer
::link('#', get_string('cancel'), array('id' => 'comment-action-cancel-'.$this->cid
));
522 $html .= html_writer
::end_tag('div'); // .fd
523 $html .= html_writer
::end_tag('div'); // .comment-area
524 $html .= html_writer
::tag('div', '', array('class' => 'clearer'));
527 $html .= html_writer
::end_tag('div'); // .comment-ctrl
528 $html .= html_writer
::end_tag('div'); // .mdl-left
541 * Return matched comments
544 * @param str $sortdirection sort direction, ASC or DESC
545 * @return array|false
547 public function get_comments($page = '', $sortdirection = 'DESC') {
548 global $DB, $CFG, $USER, $OUTPUT;
549 if (!$this->can_view()) {
552 if (!is_numeric($page)) {
556 $perpage = (!empty($CFG->commentsperpage
))?
$CFG->commentsperpage
:15;
557 $start = $page * $perpage;
558 $userfieldsapi = \core_user\fields
::for_userpic();
559 $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects
;
561 list($componentwhere, $component) = $this->get_component_select_sql('c');
563 $params['component'] = $component;
566 $sortdirection = ($sortdirection === 'ASC') ?
'ASC' : 'DESC';
567 $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
569 JOIN {user} u ON u.id = c.userid
570 WHERE c.contextid = :contextid AND
571 c.commentarea = :commentarea AND
572 c.itemid = :itemid AND
574 ORDER BY c.timecreated $sortdirection, c.id $sortdirection";
575 $params['contextid'] = $this->contextid
;
576 $params['commentarea'] = $this->commentarea
;
577 $params['itemid'] = $this->itemid
;
580 $formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
581 $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
582 foreach ($rs as $u) {
585 $c->content
= $u->ccontent
;
586 $c->format
= $u->cformat
;
587 $c->timecreated
= $u->ctimecreated
;
588 $c->strftimeformat
= get_string('strftimerecentfull', 'langconfig');
589 $url = new moodle_url('/user/view.php', array('id'=>$u->id
, 'course'=>$this->courseid
));
590 $c->profileurl
= $url->out(false); // URL should not be escaped just yet.
591 $c->fullname
= fullname($u);
592 $c->time
= userdate($c->timecreated
, $c->strftimeformat
);
593 $c->content
= format_text($c->content
, $c->format
, $formatoptions);
594 $c->avatar
= $OUTPUT->user_picture($u, array('size' => 16));
597 if ($this->can_delete($c)) {
604 if (!empty($this->plugintype
)) {
605 // moodle module will filter comments
606 $comments = plugin_callback($this->plugintype
, $this->pluginname
, 'comment', 'display', array($comments, $this->comment_param
), $comments);
613 * Returns an SQL fragment and param for selecting on component.
614 * @param string $alias
617 protected function get_component_select_sql($alias = '') {
618 $component = $this->get_component();
622 if (empty($component)) {
623 $componentwhere = "{$alias}component IS NULL";
626 $componentwhere = "({$alias}component IS NULL OR {$alias}component = :component)";
628 return array($componentwhere, $component);
632 * Returns the number of comments associated with the details of this object
634 * @global moodle_database $DB
637 public function count() {
639 if ($this->totalcommentcount
=== null) {
640 list($where, $component) = $this->get_component_select_sql();
641 $where .= ' AND itemid = :itemid AND commentarea = :commentarea AND contextid = :contextid';
643 'itemid' => $this->itemid
,
644 'commentarea' => $this->commentarea
,
645 'contextid' => $this->context
->id
,
648 $params['component'] = $component;
651 $this->totalcommentcount
= $DB->count_records_select('comments', $where, $params);
653 return $this->totalcommentcount
;
657 * Returns HTML to display a pagination bar
659 * @global stdClass $CFG
660 * @global core_renderer $OUTPUT
664 public function get_pagination($page = 0) {
665 global $CFG, $OUTPUT;
666 $count = $this->count();
667 $perpage = (!empty($CFG->commentsperpage
))?
$CFG->commentsperpage
:15;
668 $pages = (int)ceil($count/$perpage);
669 if ($pages == 1 ||
$pages == 0) {
670 return html_writer
::tag('div', '', array('id' => 'comment-pagination-'.$this->cid
, 'class' => 'comment-pagination'));
672 if (!empty(self
::$nonjs)) {
673 // used in non-js interface
674 return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page');
676 // return ajax paging bar
678 $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid
.'">';
679 for ($p=0; $p<$pages; $p++
) {
685 $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid
.'-'.$p.'">'.($p+
1).'</a> ';
695 * @global moodle_database $DB
696 * @param string $content
700 public function add($content, $format = FORMAT_MOODLE
) {
701 global $CFG, $DB, $USER, $OUTPUT;
702 if (!$this->can_post()) {
703 throw new comment_exception('nopermissiontocomment');
706 $newcmt = new stdClass
;
707 $newcmt->contextid
= $this->contextid
;
708 $newcmt->commentarea
= $this->commentarea
;
709 $newcmt->itemid
= $this->itemid
;
710 $newcmt->component
= !empty($this->component
) ?
$this->component
: null;
711 $newcmt->content
= $content;
712 $newcmt->format
= $format;
713 $newcmt->userid
= $USER->id
;
714 $newcmt->timecreated
= $now;
716 // This callback allow module to modify the content of comment, such as filter or replacement
717 plugin_callback($this->plugintype
, $this->pluginname
, 'comment', 'add', array(&$newcmt, $this->comment_param
));
719 $cmt_id = $DB->insert_record('comments', $newcmt);
720 if (!empty($cmt_id)) {
721 $newcmt->id
= $cmt_id;
722 $newcmt->strftimeformat
= get_string('strftimerecentfull', 'langconfig');
723 $newcmt->fullname
= fullname($USER);
724 $url = new moodle_url('/user/view.php', array('id' => $USER->id
, 'course' => $this->courseid
));
725 $newcmt->profileurl
= $url->out();
726 $formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
727 $newcmt->content
= format_text($newcmt->content
, $newcmt->format
, $formatoptions);
728 $newcmt->avatar
= $OUTPUT->user_picture($USER, array('size'=>16));
730 $commentlist = array($newcmt);
732 if (!empty($this->plugintype
)) {
733 // Call the display callback to allow the plugin to format the newly added comment.
734 $commentlist = plugin_callback($this->plugintype
,
738 array($commentlist, $this->comment_param
),
740 $newcmt = $commentlist[0];
742 $newcmt->time
= userdate($newcmt->timecreated
, $newcmt->strftimeformat
);
744 // Trigger comment created event.
745 if (core_component
::is_core_subsystem($this->component
)) {
746 $eventclassname = '\\core\\event\\' . $this->component
. '_comment_created';
748 $eventclassname = '\\' . $this->component
. '\\event\comment_created';
750 if (class_exists($eventclassname)) {
751 $event = $eventclassname::create(
753 'context' => $this->context
,
754 'objectid' => $newcmt->id
,
756 'itemid' => $this->itemid
764 throw new comment_exception('dbupdatefailed');
769 * delete by context, commentarea and itemid
770 * @param stdClass|array $param {
771 * contextid => int the context in which the comments exist [required]
772 * commentarea => string the comment area [optional]
773 * itemid => int comment itemid [optional]
777 public static function delete_comments($param) {
779 $param = (array)$param;
780 if (empty($param['contextid'])) {
783 $DB->delete_records('comments', $param);
788 * Delete page_comments in whole course, used by course reset
790 * @param stdClass $context course context
792 public static function reset_course_page_comments($context) {
795 $contexts[] = $context->id
;
796 $children = $context->get_child_contexts();
797 foreach ($children as $c) {
798 $contexts[] = $c->id
;
800 list($ids, $params) = $DB->get_in_or_equal($contexts);
801 $DB->delete_records_select('comments', "commentarea='page_comments' AND contextid $ids", $params);
807 * @param int|stdClass $comment The id of a comment, or a comment record.
810 public function delete($comment) {
812 if (is_object($comment)) {
813 $commentid = $comment->id
;
815 $commentid = $comment;
816 $comment = $DB->get_record('comments', ['id' => $commentid]);
820 throw new comment_exception('dbupdatefailed');
822 if (!$this->can_delete($comment)) {
823 throw new comment_exception('nopermissiontocomment');
825 $DB->delete_records('comments', array('id'=>$commentid));
826 // Trigger comment delete event.
827 if (core_component
::is_core_subsystem($this->component
)) {
828 $eventclassname = '\\core\\event\\' . $this->component
. '_comment_deleted';
830 $eventclassname = '\\' . $this->component
. '\\event\comment_deleted';
832 if (class_exists($eventclassname)) {
833 $event = $eventclassname::create(
835 'context' => $this->context
,
836 'objectid' => $commentid,
838 'itemid' => $this->itemid
841 $event->add_record_snapshot('comments', $comment);
851 * @param bool $return return comments list string or print it out
852 * @param bool $nonjs print nonjs comments list or not?
853 * @return string|void
855 public function print_comments($page = 0, $return = true, $nonjs = true) {
856 global $DB, $CFG, $PAGE;
858 if (!$this->can_view()) {
862 if (!(self
::$comment_itemid == $this->itemid
&&
863 self
::$comment_context == $this->context
->id
&&
864 self
::$comment_area == $this->commentarea
&&
865 self
::$comment_component == $this->component
869 $comments = $this->get_comments($page);
873 $html .= html_writer
::tag('h3', get_string('comments'));
874 $html .= html_writer
::start_tag('ul', array('id' => 'comment-list-'.$this->cid
, 'class' => 'comment-list'));
876 // Reverse the comments array to display them in the correct direction
877 foreach (array_reverse($comments) as $cmt) {
878 $html .= html_writer
::tag('li', $this->print_comment($cmt, $nonjs), array('id' => 'comment-'.$cmt->id
.'-'.$this->cid
));
881 $html .= html_writer
::end_tag('ul');
882 $html .= $this->get_pagination($page);
884 if ($nonjs && $this->can_post()) {
885 // Form to add comments
886 $html .= html_writer
::start_tag('form', array('method' => 'post', 'action' => new moodle_url('/comment/comment_post.php')));
887 // Comment parameters
888 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'contextid', 'value' => $this->contextid
));
889 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'add'));
890 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'area', 'value' => $this->commentarea
));
891 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'component', 'value' => $this->component
));
892 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'itemid', 'value' => $this->itemid
));
893 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'courseid', 'value' => $this->courseid
));
894 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
895 $html .= html_writer
::empty_tag('input', array('type' => 'hidden', 'name' => 'returnurl', 'value' => $PAGE->url
));
896 // Textarea for the actual comment
897 $html .= html_writer
::tag('textarea', '', array('name' => 'content', 'rows' => 2));
898 // Submit button to add the comment
899 $html .= html_writer
::empty_tag('input', array('type' => 'submit', 'value' => get_string('submit')));
900 $html .= html_writer
::end_tag('form');
910 * Returns an array containing comments in HTML format.
912 * @global core_renderer $OUTPUT
913 * @param stdClass $cmt {
914 * id => int comment id
915 * content => string comment content
916 * format => int comment text format
917 * timecreated => int comment's timecreated
918 * profileurl => string link to user profile
919 * fullname => comment author's full name
920 * avatar => string user's avatar
921 * delete => boolean does user have permission to delete comment?
926 public function print_comment($cmt, $nonjs = true) {
929 $replacements = array();
931 if (!empty($cmt->delete
) && empty($nonjs)) {
932 $strdelete = get_string('deletecommentbyon', 'moodle', (object)['user' => $cmt->fullname
, 'time' => $cmt->time
]);
933 $deletelink = html_writer
::start_tag('div', array('class'=>'comment-delete'));
934 $deletelink .= html_writer
::start_tag('a', array('href' => '#', 'id' => 'comment-delete-'.$this->cid
.'-'.$cmt->id
,
935 'class' => 'icon-no-margin', 'title' => $strdelete));
937 $deletelink .= $OUTPUT->pix_icon('t/delete', $strdelete);
938 $deletelink .= html_writer
::end_tag('a');
939 $deletelink .= html_writer
::end_tag('div');
940 $cmt->content
= $deletelink . $cmt->content
;
942 $patterns[] = '___picture___';
943 $patterns[] = '___name___';
944 $patterns[] = '___content___';
945 $patterns[] = '___time___';
946 $replacements[] = $cmt->avatar
;
947 $replacements[] = html_writer
::link($cmt->profileurl
, $cmt->fullname
);
948 $replacements[] = $cmt->content
;
949 $replacements[] = $cmt->time
;
951 // use html template to format a single comment.
952 return str_replace($patterns, $replacements, $this->template
);
956 * Revoke validate callbacks
958 * @param array $params addtionall parameters need to add to callbacks
960 protected function validate($params=array()) {
961 foreach ($params as $key=>$value) {
962 $this->comment_param
->$key = $value;
964 $validation = plugin_callback($this->plugintype
, $this->pluginname
, 'comment', 'validate', array($this->comment_param
), false);
966 throw new comment_exception('invalidcommentparam');
971 * Returns true if the user is able to view comments
974 public function can_view() {
976 return !empty($this->viewcap
);
980 * Returns true if the user can add comments against this comment description
983 public function can_post() {
985 return isloggedin() && !empty($this->postcap
);
989 * Returns true if the user can delete this comment.
991 * The user can delete comments if it is one they posted and they can still make posts,
992 * or they have the capability to delete comments.
994 * A database call is avoided if a comment record is passed.
996 * @param int|stdClass $comment The id of a comment, or a comment record.
999 public function can_delete($comment) {
1001 if (is_object($comment)) {
1002 $commentid = $comment->id
;
1004 $commentid = $comment;
1007 $this->validate(array('commentid'=>$commentid));
1009 if (!is_object($comment)) {
1010 // Get the comment record from the database.
1011 $comment = $DB->get_record('comments', array('id' => $commentid), 'id, userid', MUST_EXIST
);
1014 $hascapability = has_capability('moodle/comment:delete', $this->context
);
1015 $owncomment = $USER->id
== $comment->userid
;
1017 return ($hascapability ||
($owncomment && $this->can_post()));
1021 * Returns the component associated with the comment.
1025 public function get_component() {
1026 return $this->component
;
1030 * Do not call! I am a deprecated method because of the typo in my name.
1031 * @deprecated since 2.9
1032 * @see comment::get_component()
1035 public function get_compontent() {
1036 return $this->get_component();
1040 * Returns the context associated with the comment
1043 public function get_context() {
1044 return $this->context
;
1048 * Returns the course id associated with the comment
1051 public function get_courseid() {
1052 return $this->courseid
;
1056 * Returns the course module associated with the comment
1060 public function get_cm() {
1065 * Returns the item id associated with the comment
1069 public function get_itemid() {
1070 return $this->itemid
;
1074 * Returns the comment area associated with the commentarea
1078 public function get_commentarea() {
1079 return $this->commentarea
;
1083 * Make the comments textarea fullwidth.
1085 * @since 2.8.1 + 2.7.4
1086 * @param bool $fullwidth
1088 public function set_fullwidth($fullwidth = true) {
1089 $this->fullwidth
= (bool)$fullwidth;
1093 * Return the template.
1098 public function get_template() {
1099 return $this->template
;
1108 public function get_cid() {
1113 * Return the link text.
1118 public function get_linktext() {
1119 return $this->linktext
;
1128 public function get_notoggle() {
1129 return $this->notoggle
;
1133 * Return display total count.
1138 public function get_displaytotalcount() {
1139 return $this->displaytotalcount
;
1143 * Return display cancel.
1148 public function get_displaycancel() {
1149 return $this->displaycancel
;
1158 public function get_fullwidth() {
1159 return $this->fullwidth
;
1168 public function get_autostart() {
1169 return $this->autostart
;
1175 * Comment exception class
1178 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
1179 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1181 class comment_exception
extends moodle_exception
{