MDL-31636 Added strftimeformat to comment object being passed through to xxx_comment_...
[moodle.git] / comment / lib.php
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/>.
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();
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     /** @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     /** @var bool Use non-javascript UI */
75     private static $nonjs = false;
76     /** @var int comment itemid used in non-javascript UI */
77     private static $comment_itemid = null;
78     /** @var int comment context used in non-javascript UI */
79     private static $comment_context = null;
80     /** @var string comment area used in non-javascript UI */
81     private static $comment_area = null;
82     /** @var string comment page used in non-javascript UI */
83     private static $comment_page = null;
84     /** @var string comment itemid component in non-javascript UI */
85     private static $comment_component = null;
87     /**
88      * Construct function of comment class, initialise
89      * class members
90      *
91      * @param stdClass $options {
92      *            context => context context to use for the comment [required]
93      *            component => string which plugin will comment being added to [required]
94      *            itemid  => int the id of the associated item (forum post, glossary item etc) [required]
95      *            area    => string comment area
96      *            cm      => stdClass course module
97      *            course  => course course object
98      *            client_id => string an unique id to identify comment area
99      *            autostart => boolean automatically expend comments
100      *            showcount => boolean display the number of comments
101      *            displaycancel => boolean display cancel button
102      *            notoggle => boolean don't show/hide button
103      *            linktext => string title of show/hide button
104      * }
105      */
106     public function __construct(stdClass $options) {
107         $this->viewcap = false;
108         $this->postcap = false;
110         // setup client_id
111         if (!empty($options->client_id)) {
112             $this->cid = $options->client_id;
113         } else {
114             $this->cid = uniqid();
115         }
117         // setup context
118         if (!empty($options->context)) {
119             $this->context = $options->context;
120             $this->contextid = $this->context->id;
121         } else if(!empty($options->contextid)) {
122             $this->contextid = $options->contextid;
123             $this->context = context::instance_by_id($this->contextid);
124         } else {
125             print_error('invalidcontext');
126         }
128         if (!empty($options->component)) {
129             // set and validate component
130             $this->set_component($options->component);
131         } else {
132             // component cannot be empty
133             throw new comment_exception('invalidcomponent');
134         }
136         // setup course
137         // course will be used to generate user profile link
138         if (!empty($options->course)) {
139             $this->courseid = $options->course->id;
140         } else if (!empty($options->courseid)) {
141             $this->courseid = $options->courseid;
142         } else {
143             $this->courseid = SITEID;
144         }
146         // setup coursemodule
147         if (!empty($options->cm)) {
148             $this->cm = $options->cm;
149         } else {
150             $this->cm = null;
151         }
153         // setup commentarea
154         if (!empty($options->area)) {
155             $this->commentarea = $options->area;
156         }
158         // setup itemid
159         if (!empty($options->itemid)) {
160             $this->itemid = $options->itemid;
161         } else {
162             $this->itemid = 0;
163         }
165         // setup customized linktext
166         if (!empty($options->linktext)) {
167             $this->linktext = $options->linktext;
168         } else {
169             $this->linktext = get_string('comments');
170         }
172         // setup options for callback functions
173         $this->comment_param = new stdClass();
174         $this->comment_param->context     = $this->context;
175         $this->comment_param->courseid    = $this->courseid;
176         $this->comment_param->cm          = $this->cm;
177         $this->comment_param->commentarea = $this->commentarea;
178         $this->comment_param->itemid      = $this->itemid;
180         // setup notoggle
181         if (!empty($options->notoggle)) {
182             $this->set_notoggle($options->notoggle);
183         }
185         // setup notoggle
186         if (!empty($options->autostart)) {
187             $this->set_autostart($options->autostart);
188         }
190         // setup displaycancel
191         if (!empty($options->displaycancel)) {
192             $this->set_displaycancel($options->displaycancel);
193         }
195         // setup displaytotalcount
196         if (!empty($options->showcount)) {
197             $this->set_displaytotalcount($options->showcount);
198         }
200         // setting post and view permissions
201         $this->check_permissions();
203         // load template
204         $this->template = html_writer::start_tag('div', array('class' => 'comment-message'));
206         $this->template .= html_writer::start_tag('div', array('class' => 'comment-message-meta'));
208         $this->template .= html_writer::tag('span', '___picture___', array('class' => 'picture'));
209         $this->template .= html_writer::tag('span', '___name___', array('class' => 'user')) . ' - ';
210         $this->template .= html_writer::tag('span', '___time___', array('class' => 'time'));
212         $this->template .= html_writer::end_tag('div'); // .comment-message-meta
213         $this->template .= html_writer::tag('div', '___content___', array('class' => 'text'));
215         $this->template .= html_writer::end_tag('div'); // .comment-message
217         if (!empty($this->plugintype)) {
218             $this->template = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'template', array($this->comment_param), $this->template);
219         }
221         unset($options);
222     }
224     /**
225      * Receive nonjs comment parameters
226      *
227      * @param moodle_page $page The page object to initialise comments within
228      *                          If not provided the global $PAGE is used
229      */
230     public static function init(moodle_page $page = null) {
231         global $PAGE;
233         if (empty($page)) {
234             $page = $PAGE;
235         }
236         // setup variables for non-js interface
237         self::$nonjs = optional_param('nonjscomment', '', PARAM_ALPHANUM);
238         self::$comment_itemid  = optional_param('comment_itemid',  '', PARAM_INT);
239         self::$comment_context = optional_param('comment_context', '', PARAM_INT);
240         self::$comment_page    = optional_param('comment_page',    '', PARAM_INT);
241         self::$comment_area    = optional_param('comment_area',    '', PARAM_AREA);
243         $page->requires->string_for_js('addcomment', 'moodle');
244         $page->requires->string_for_js('deletecomment', 'moodle');
245         $page->requires->string_for_js('comments', 'moodle');
246         $page->requires->string_for_js('commentsrequirelogin', 'moodle');
247     }
249     /**
250      * Sets the component.
251      *
252      * This method shouldn't be public, changing the component once it has been set potentially
253      * invalidates permission checks.
254      * A coding_error is now thrown if code attempts to change the component.
255      *
256      * @param string $component
257      */
258     public function set_component($component) {
259         if (!empty($this->component) && $this->component !== $component) {
260             throw new coding_exception('You cannot change the component of a comment once it has been set');
261         }
262         $this->component = $component;
263         list($this->plugintype, $this->pluginname) = normalize_component($component);
264     }
266     /**
267      * Determines if the user can view the comment.
268      *
269      * @param bool $value
270      */
271     public function set_view_permission($value) {
272         $this->viewcap = (bool)$value;
273     }
275     /**
276      * Determines if the user can post a comment
277      *
278      * @param bool $value
279      */
280     public function set_post_permission($value) {
281         $this->postcap = (bool)$value;
282     }
284     /**
285      * check posting comments permission
286      * It will check based on user roles and ask modules
287      * If you need to check permission by modules, a
288      * function named $pluginname_check_comment_post must be implemented
289      */
290     private function check_permissions() {
291         $this->postcap = has_capability('moodle/comment:post', $this->context);
292         $this->viewcap = has_capability('moodle/comment:view', $this->context);
293         if (!empty($this->plugintype)) {
294             $permissions = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'permissions', array($this->comment_param), array('post'=>false, 'view'=>false));
295             $this->postcap = $this->postcap && $permissions['post'];
296             $this->viewcap = $this->viewcap && $permissions['view'];
297         }
298     }
300     /**
301      * Gets a link for this page that will work with JS disabled.
302      *
303      * @global moodle_page $PAGE
304      * @param moodle_page $page
305      * @return moodle_url
306      */
307     public function get_nojslink(moodle_page $page = null) {
308         if ($page === null) {
309             global $PAGE;
310             $page = $PAGE;
311         }
313         $link = new moodle_url($page->url, array(
314             'nonjscomment'    => true,
315             'comment_itemid'  => $this->itemid,
316             'comment_context' => $this->context->id,
317             'comment_area'    => $this->commentarea,
318         ));
319         $link->remove_params(array('comment_page'));
320         return $link;
321     }
323     /**
324      * Sets the value of the notoggle option.
325      *
326      * If set to true then the user will not be able to expand and collase
327      * the comment section.
328      *
329      * @param bool $newvalue
330      */
331     public function set_notoggle($newvalue = true) {
332         $this->notoggle = (bool)$newvalue;
333     }
335     /**
336      * Sets the value of the autostart option.
337      *
338      * If set to true then the comments will be loaded during page load.
339      * Normally this happens only once the user expands the comment section.
340      *
341      * @param bool $newvalue
342      */
343     public function set_autostart($newvalue = true) {
344         $this->autostart = (bool)$newvalue;
345     }
347     /**
348      * Sets the displaycancel option
349      *
350      * If set to true then a cancel button will be shown when using the form
351      * to post comments.
352      *
353      * @param bool $newvalue
354      */
355     public function set_displaycancel($newvalue = true) {
356         $this->displaycancel = (bool)$newvalue;
357     }
359     /**
360      * Sets the displaytotalcount option
361      *
362      * If set to true then the total number of comments will be displayed
363      * when printing comments.
364      *
365      * @param bool $newvalue
366      */
367     public function set_displaytotalcount($newvalue = true) {
368         $this->displaytotalcount = (bool)$newvalue;
369     }
371     /**
372      * Initialises the JavaScript that enchances the comment API.
373      *
374      * @param moodle_page $page The moodle page object that the JavaScript should be
375      *                          initialised for.
376      */
377     public function initialise_javascript(moodle_page $page) {
379         $options = new stdClass;
380         $options->client_id   = $this->cid;
381         $options->commentarea = $this->commentarea;
382         $options->itemid      = $this->itemid;
383         $options->page        = 0;
384         $options->courseid    = $this->courseid;
385         $options->contextid   = $this->contextid;
386         $options->component   = $this->component;
387         $options->notoggle    = $this->notoggle;
388         $options->autostart   = $this->autostart;
390         $page->requires->js_init_call('M.core_comment.init', array($options), true);
392         return true;
393     }
395     /**
396      * Prepare comment code in html
397      * @param  boolean $return
398      * @return string|void
399      */
400     public function output($return = true) {
401         global $PAGE, $OUTPUT;
402         static $template_printed;
404         $this->initialise_javascript($PAGE);
406         if (!empty(self::$nonjs)) {
407             // return non js comments interface
408             return $this->print_comments(self::$comment_page, $return, true);
409         }
411         $html = '';
413         // print html template
414         // Javascript will use the template to render new comments
415         if (empty($template_printed) && $this->can_view()) {
416             $html .= html_writer::tag('div', $this->template, array('style' => 'display:none', 'id' => 'cmt-tmpl'));
417             $template_printed = true;
418         }
420         if ($this->can_view()) {
421             // print commenting icon and tooltip
422             $html .= html_writer::start_tag('div', array('class' => 'mdl-left'));
423             $html .= html_writer::link($this->get_nojslink($PAGE), get_string('showcommentsnonjs'), array('class' => 'showcommentsnonjs'));
425             if (!$this->notoggle) {
426                 // If toggling is enabled (notoggle=false) then print the controls to toggle
427                 // comments open and closed
428                 $countstring = '';
429                 if ($this->displaytotalcount) {
430                     $countstring = '('.$this->count().')';
431                 }
432                 $collapsedimage= 't/collapsed';
433                 if (right_to_left()) {
434                     $collapsedimage= 't/collapsed_rtl';
435                 } else {
436                     $collapsedimage= 't/collapsed';
437                 }
438                 $html .= html_writer::start_tag('a', array('class' => 'comment-link', 'id' => 'comment-link-'.$this->cid, 'href' => '#'));
439                 $html .= html_writer::empty_tag('img', array('id' => 'comment-img-'.$this->cid, 'src' => $OUTPUT->pix_url($collapsedimage), 'alt' => $this->linktext, 'title' => $this->linktext));
440                 $html .= html_writer::tag('span', $this->linktext.' '.$countstring, array('id' => 'comment-link-text-'.$this->cid));
441                 $html .= html_writer::end_tag('a');
442             }
444             $html .= html_writer::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid, 'class' => 'comment-ctrl'));
446             if ($this->autostart) {
447                 // If autostart has been enabled print the comments list immediatly
448                 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list comments-loaded'));
449                 $html .= html_writer::tag('li', '', array('class' => 'first'));
450                 $html .= $this->print_comments(0, true, false);
451                 $html .= html_writer::end_tag('ul'); // .comment-list
452                 $html .= $this->get_pagination(0);
453             } else {
454                 $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list'));
455                 $html .= html_writer::tag('li', '', array('class' => 'first'));
456                 $html .= html_writer::end_tag('ul'); // .comment-list
457                 $html .= html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
458             }
460             if ($this->can_post()) {
461                 // print posting textarea
462                 $html .= html_writer::start_tag('div', array('class' => 'comment-area'));
463                 $html .= html_writer::start_tag('div', array('class' => 'db'));
464                 $html .= html_writer::tag('textarea', '', array('name' => 'content', 'rows' => 2, 'cols' => 20, 'id' => 'dlg-content-'.$this->cid));
465                 $html .= html_writer::end_tag('div'); // .db
467                 $html .= html_writer::start_tag('div', array('class' => 'fd', 'id' => 'comment-action-'.$this->cid));
468                 $html .= html_writer::link('#', get_string('savecomment'), array('id' => 'comment-action-post-'.$this->cid));
470                 if ($this->displaycancel) {
471                     $html .= html_writer::tag('span', ' | ');
472                     $html .= html_writer::link('#', get_string('cancel'), array('id' => 'comment-action-cancel-'.$this->cid));
473                 }
475                 $html .= html_writer::end_tag('div'); // .fd
476                 $html .= html_writer::end_tag('div'); // .comment-area
477                 $html .= html_writer::tag('div', '', array('class' => 'clearer'));
478             }
480             $html .= html_writer::end_tag('div'); // .comment-ctrl
481             $html .= html_writer::end_tag('div'); // .mdl-left
482         } else {
483             $html = '';
484         }
486         if ($return) {
487             return $html;
488         } else {
489             echo $html;
490         }
491     }
493     /**
494      * Return matched comments
495      *
496      * @param  int $page
497      * @return array
498      */
499     public function get_comments($page = '') {
500         global $DB, $CFG, $USER, $OUTPUT;
501         if (!$this->can_view()) {
502             return false;
503         }
504         if (!is_numeric($page)) {
505             $page = 0;
506         }
507         $params = array();
508         $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
509         $start = $page * $perpage;
510         $ufields = user_picture::fields('u');
511         $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
512                   FROM {comments} c
513                   JOIN {user} u ON u.id = c.userid
514                  WHERE c.contextid = :contextid AND c.commentarea = :commentarea AND c.itemid = :itemid
515               ORDER BY c.timecreated DESC";
516         $params['contextid'] = $this->contextid;
517         $params['commentarea'] = $this->commentarea;
518         $params['itemid'] = $this->itemid;
520         $comments = array();
521         $formatoptions = array('overflowdiv' => true);
522         $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
523         foreach ($rs as $u) {
524             $c = new stdClass();
525             $c->id          = $u->cid;
526             $c->content     = $u->ccontent;
527             $c->format      = $u->cformat;
528             $c->timecreated = $u->ctimecreated;
529             $c->strftimeformat = get_string('strftimerecent', 'langconfig');
530             $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
531             $c->profileurl = $url->out(false);
532             $c->fullname = fullname($u);
533             $c->time = userdate($c->timecreated, get_string('strftimerecent', 'langconfig'));
534             $c->content = format_text($c->content, $c->format, $formatoptions);
535             $c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
537             $candelete = $this->can_delete($c->id);
538             if (($USER->id == $u->id) || !empty($candelete)) {
539                 $c->delete = true;
540             }
541             $comments[] = $c;
542         }
543         $rs->close();
545         if (!empty($this->plugintype)) {
546             // moodle module will filter comments
547             $comments = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'display', array($comments, $this->comment_param), $comments);
548         }
550         return $comments;
551     }
553     /**
554      * Returns the number of comments associated with the details of this object
555      *
556      * @global moodle_database $DB
557      * @return int
558      */
559     public function count() {
560         global $DB;
561         if ($this->totalcommentcount === null) {
562             $this->totalcommentcount = $DB->count_records('comments', array('itemid' => $this->itemid, 'commentarea' => $this->commentarea, 'contextid' => $this->context->id));
563         }
564         return $this->totalcommentcount;
565     }
567     /**
568      * Returns HTML to display a pagination bar
569      *
570      * @global stdClass $CFG
571      * @global core_renderer $OUTPUT
572      * @param int $page
573      * @return string
574      */
575     public function get_pagination($page = 0) {
576         global $CFG, $OUTPUT;
577         $count = $this->count();
578         $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
579         $pages = (int)ceil($count/$perpage);
580         if ($pages == 1 || $pages == 0) {
581             return html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
582         }
583         if (!empty(self::$nonjs)) {
584             // used in non-js interface
585             return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page');
586         } else {
587             // return ajax paging bar
588             $str = '';
589             $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid.'">';
590             for ($p=0; $p<$pages; $p++) {
591                 if ($p == $page) {
592                     $class = 'curpage';
593                 } else {
594                     $class = 'pageno';
595                 }
596                 $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid.'-'.$p.'">'.($p+1).'</a> ';
597             }
598             $str .= '</div>';
599         }
600         return $str;
601     }
603     /**
604      * Add a new comment
605      *
606      * @global moodle_database $DB
607      * @param string $content
608      * @param int $format
609      * @return stdClass
610      */
611     public function add($content, $format = FORMAT_MOODLE) {
612         global $CFG, $DB, $USER, $OUTPUT;
613         if (!$this->can_post()) {
614             throw new comment_exception('nopermissiontocomment');
615         }
616         $now = time();
617         $newcmt = new stdClass;
618         $newcmt->contextid    = $this->contextid;
619         $newcmt->commentarea  = $this->commentarea;
620         $newcmt->itemid       = $this->itemid;
621         $newcmt->content      = $content;
622         $newcmt->format       = $format;
623         $newcmt->userid       = $USER->id;
624         $newcmt->timecreated  = $now;
626         // This callback allow module to modify the content of comment, such as filter or replacement
627         plugin_callback($this->plugintype, $this->pluginname, 'comment', 'add', array(&$newcmt, $this->comment_param));
629         $cmt_id = $DB->insert_record('comments', $newcmt);
630         if (!empty($cmt_id)) {
631             $newcmt->id = $cmt_id;
632             $newcmt->time = userdate($now, get_string('strftimerecent', 'langconfig'));
633             $newcmt->fullname = fullname($USER);
634             $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
635             $newcmt->profileurl = $url->out();
636             $newcmt->content = format_text($newcmt->content, $format, array('overflowdiv'=>true));
637             $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
638             return $newcmt;
639         } else {
640             throw new comment_exception('dbupdatefailed');
641         }
642     }
644     /**
645      * delete by context, commentarea and itemid
646      * @param stdClass|array $param {
647      *            contextid => int the context in which the comments exist [required]
648      *            commentarea => string the comment area [optional]
649      *            itemid => int comment itemid [optional]
650      * }
651      * @return boolean
652      */
653     public static function delete_comments($param) {
654         global $DB;
655         $param = (array)$param;
656         if (empty($param['contextid'])) {
657             return false;
658         }
659         $DB->delete_records('comments', $param);
660         return true;
661     }
663     /**
664      * Delete page_comments in whole course, used by course reset
665      *
666      * @param stdClass $context course context
667      */
668     public static function reset_course_page_comments($context) {
669         global $DB;
670         $contexts = array();
671         $contexts[] = $context->id;
672         $children = get_child_contexts($context);
673         foreach ($children as $c) {
674             $contexts[] = $c->id;
675         }
676         list($ids, $params) = $DB->get_in_or_equal($contexts);
677         $DB->delete_records_select('comments', "commentarea='page_comments' AND contextid $ids", $params);
678     }
680     /**
681      * Delete a comment
682      *
683      * @param  int $commentid
684      * @return bool
685      */
686     public function delete($commentid) {
687         global $DB, $USER;
688         $candelete = has_capability('moodle/comment:delete', $this->context);
689         if (!$comment = $DB->get_record('comments', array('id'=>$commentid))) {
690             throw new comment_exception('dbupdatefailed');
691         }
692         if (!($USER->id == $comment->userid || !empty($candelete))) {
693             throw new comment_exception('nopermissiontocomment');
694         }
695         $DB->delete_records('comments', array('id'=>$commentid));
696         return true;
697     }
699     /**
700      * Print comments
701      *
702      * @param int $page
703      * @param bool $return return comments list string or print it out
704      * @param bool $nonjs print nonjs comments list or not?
705      * @return string|void
706      */
707     public function print_comments($page = 0, $return = true, $nonjs = true) {
708         global $DB, $CFG, $PAGE;
710         if (!$this->can_view()) {
711             return '';
712         }
714         $html = '';
715         if (!(self::$comment_itemid == $this->itemid &&
716             self::$comment_context == $this->context->id &&
717             self::$comment_area == $this->commentarea)) {
718             $page = 0;
719         }
720         $comments = $this->get_comments($page);
722         $html = '';
723         if ($nonjs) {
724             $html .= html_writer::tag('h3', get_string('comments'));
725             $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list'));
726         }
727         // Reverse the comments array to display them in the correct direction
728         foreach (array_reverse($comments) as $cmt) {
729             $html .= html_writer::tag('li', $this->print_comment($cmt, $nonjs), array('id' => 'comment-'.$cmt->id.'-'.$this->cid));
730         }
731         if ($nonjs) {
732             $html .= html_writer::end_tag('ul');
733             $html .= $this->get_pagination($page);
734         }
735         if ($nonjs && $this->can_post()) {
736             // Form to add comments
737             $html .= html_writer::start_tag('form', array('method' => 'post', 'action' => new moodle_url('/comment/comment_post.php')));
738             // Comment parameters
739             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'contextid', 'value' => $this->contextid));
740             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action',    'value' => 'add'));
741             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'area',      'value' => $this->commentarea));
742             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'component', 'value' => $this->component));
743             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'itemid',    'value' => $this->itemid));
744             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'courseid',  'value' => $this->courseid));
745             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey',   'value' => sesskey()));
746             $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'returnurl', 'value' => $PAGE->url));
747             // Textarea for the actual comment
748             $html .= html_writer::tag('textarea', '', array('name' => 'content', 'rows' => 2));
749             // Submit button to add the comment
750             $html .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('submit')));
751             $html .= html_writer::end_tag('form');
752         }
753         if ($return) {
754             return $html;
755         } else {
756             echo $html;
757         }
758     }
760     /**
761      * Returns an array containing comments in HTML format.
762      *
763      * @global core_renderer $OUTPUT
764      * @param stdClass $cmt {
765      *          id => int comment id
766      *          content => string comment content
767      *          format  => int comment text format
768      *          timecreated => int comment's timecreated
769      *          profileurl  => string link to user profile
770      *          fullname    => comment author's full name
771      *          avatar      => string user's avatar
772      *          delete      => boolean does user have permission to delete comment?
773      * }
774      * @param bool $nonjs
775      * @return array
776      */
777     public function print_comment($cmt, $nonjs = true) {
778         global $OUTPUT;
779         $patterns = array();
780         $replacements = array();
782         if (!empty($cmt->delete) && empty($nonjs)) {
783             $deletelink  = html_writer::start_tag('div', array('class'=>'comment-delete'));
784             $deletelink .= html_writer::start_tag('a', array('href' => '#', 'id' => 'comment-delete-'.$this->cid.'-'.$cmt->id));
785             $deletelink .= $OUTPUT->pix_icon('t/delete', get_string('delete'));
786             $deletelink .= html_writer::end_tag('a');
787             $deletelink .= html_writer::end_tag('div');
788             $cmt->content = $deletelink . $cmt->content;
789         }
790         $patterns[] = '___picture___';
791         $patterns[] = '___name___';
792         $patterns[] = '___content___';
793         $patterns[] = '___time___';
794         $replacements[] = $cmt->avatar;
795         $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
796         $replacements[] = $cmt->content;
797         $replacements[] = userdate($cmt->timecreated, $cmt->strftimeformat);
799         // use html template to format a single comment.
800         return str_replace($patterns, $replacements, $this->template);
801     }
803     /**
804      * Revoke validate callbacks
805      *
806      * @param stdClass $params addtionall parameters need to add to callbacks
807      */
808     protected function validate($params=array()) {
809         foreach ($params as $key=>$value) {
810             $this->comment_param->$key = $value;
811         }
812         $validation = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'validate', array($this->comment_param), false);
813         if (!$validation) {
814             throw new comment_exception('invalidcommentparam');
815         }
816     }
818     /**
819      * Returns true if the user is able to view comments
820      * @return bool
821      */
822     public function can_view() {
823         $this->validate();
824         return !empty($this->viewcap);
825     }
827     /**
828      * Returns true if the user can add comments against this comment description
829      * @return bool
830      */
831     public function can_post() {
832         $this->validate();
833         return isloggedin() && !empty($this->postcap);
834     }
836     /**
837      * Returns true if the user can delete this comment
838      * @param int $commentid
839      * @return bool
840      */
841     public function can_delete($commentid) {
842         $this->validate(array('commentid'=>$commentid));
843         return has_capability('moodle/comment:delete', $this->context);
844     }
846     /**
847      * Returns the component associated with the comment
848      * @return string
849      */
850     public function get_compontent() {
851         return $this->component;
852     }
854     /**
855      * Returns the context associated with the comment
856      * @return stdClass
857      */
858     public function get_context() {
859         return $this->context;
860     }
862     /**
863      * Returns the course id associated with the comment
864      * @return int
865      */
866     public function get_courseid() {
867         return $this->courseid;
868     }
870     /**
871      * Returns the course module associated with the comment
872      *
873      * @return stdClass
874      */
875     public function get_cm() {
876         return $this->cm;
877     }
879     /**
880      * Returns the item id associated with the comment
881      *
882      * @return int
883      */
884     public function get_itemid() {
885         return $this->itemid;
886     }
888     /**
889      * Returns the comment area associated with the commentarea
890      *
891      * @return stdClass
892      */
893     public function get_commentarea() {
894         return $this->commentarea;
895     }
898 /**
899  * Comment exception class
900  *
901  * @package   core
902  * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
903  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
904  */
905 class comment_exception extends moodle_exception {