MDL-31636 Added strftimeformat to comment object being passed through to xxx_comment_...
[moodle.git] / comment / lib.php
CommitLineData
1bcb7eb5 1<?php
36051c9e
DC
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
1bcb7eb5 17/**
35453657 18 * Functions and classes for commenting
1bcb7eb5 19 *
35453657
DC
20 * @package core
21 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
1bcb7eb5 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
c1951ea9
DC
24defined('MOODLE_INTERNAL') || die();
25
35453657
DC
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 */
36051c9e 34class comment {
35453657 35 /** @var int there may be several comment box in one page so we need a client_id to recognize them */
36051c9e 36 private $cid;
35453657 37 /** @var string commentarea is used to specify different parts shared the same itemid */
36051c9e 38 private $commentarea;
35453657 39 /** @var int itemid is used to associate with commenting content */
36051c9e 40 private $itemid;
35453657 41 /** @var string this html snippet will be used as a template to build comment content */
36051c9e 42 private $template;
35453657 43 /** @var int The context id for comments */
c1951ea9 44 private $contextid;
35453657 45 /** @var stdClass The context itself */
36051c9e 46 private $context;
35453657 47 /** @var int The course id for comments */
866354a9 48 private $courseid;
35453657 49 /** @var stdClass course module object, only be used to help find pluginname automatically */
36051c9e 50 private $cm;
35453657 51 /** @var string The component that this comment is for. It is STRONGLY recommended to set this. */
c1951ea9 52 private $component;
35453657 53 /** @var string This is calculated by normalising the component */
36051c9e 54 private $pluginname;
35453657 55 /** @var string This is calculated by normalising the component */
c1951ea9 56 private $plugintype;
35453657 57 /** @var bool Whether the user has the required capabilities/permissions to view comments. */
c1951ea9 58 private $viewcap = false;
35453657 59 /** @var bool Whether the user has the required capabilities/permissions to post comments. */
c1951ea9 60 private $postcap = false;
35453657 61 /** @var string to customize link text */
36051c9e 62 private $linktext;
35453657 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. */
116a48b1 64 protected $notoggle = false;
35453657 65 /** @var bool If set to true comments are automatically loaded as soon as the page loads. */
116a48b1 66 protected $autostart = false;
35453657 67 /** @var bool If set to true the total count of comments is displayed when displaying comments. */
c1951ea9 68 protected $displaytotalcount = false;
35453657 69 /** @var bool If set to true a cancel button will be shown on the form used to submit comments. */
93b86f62 70 protected $displaycancel = false;
35453657 71 /** @var int The number of comments associated with this comments params */
c1951ea9 72 protected $totalcommentcount = null;
93b86f62 73
35453657 74 /** @var bool Use non-javascript UI */
36051c9e 75 private static $nonjs = false;
35453657 76 /** @var int comment itemid used in non-javascript UI */
36051c9e 77 private static $comment_itemid = null;
35453657 78 /** @var int comment context used in non-javascript UI */
36051c9e 79 private static $comment_context = null;
35453657 80 /** @var string comment area used in non-javascript UI */
36051c9e 81 private static $comment_area = null;
35453657 82 /** @var string comment page used in non-javascript UI */
d846488e 83 private static $comment_page = null;
35453657 84 /** @var string comment itemid component in non-javascript UI */
287ccb31 85 private static $comment_component = null;
2f181b8c 86
36051c9e 87 /**
866354a9 88 * Construct function of comment class, initialise
36051c9e 89 * class members
35453657
DC
90 *
91 * @param stdClass $options {
c1951ea9
DC
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 * }
36051c9e 105 */
c1951ea9 106 public function __construct(stdClass $options) {
36051c9e
DC
107 $this->viewcap = false;
108 $this->postcap = false;
76951100 109
866354a9 110 // setup client_id
36051c9e
DC
111 if (!empty($options->client_id)) {
112 $this->cid = $options->client_id;
113 } else {
114 $this->cid = uniqid();
115 }
3a11c09f 116
866354a9 117 // setup context
36051c9e
DC
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;
d197ea43 123 $this->context = context::instance_by_id($this->contextid);
36051c9e
DC
124 } else {
125 print_error('invalidcontext');
126 }
76951100 127
d846488e 128 if (!empty($options->component)) {
c1951ea9 129 // set and validate component
d846488e 130 $this->set_component($options->component);
c1951ea9
DC
131 } else {
132 // component cannot be empty
133 throw new comment_exception('invalidcomponent');
d846488e
DC
134 }
135
866354a9
DC
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;
5b95daec 142 } else {
fe40925a 143 $this->courseid = SITEID;
866354a9
DC
144 }
145
866354a9
DC
146 // setup coursemodule
147 if (!empty($options->cm)) {
148 $this->cm = $options->cm;
149 } else {
150 $this->cm = null;
151 }
152
153 // setup commentarea
36051c9e
DC
154 if (!empty($options->area)) {
155 $this->commentarea = $options->area;
156 }
76951100 157
866354a9 158 // setup itemid
36051c9e
DC
159 if (!empty($options->itemid)) {
160 $this->itemid = $options->itemid;
467c85d7
DC
161 } else {
162 $this->itemid = 0;
36051c9e 163 }
76951100 164
866354a9 165 // setup customized linktext
36051c9e
DC
166 if (!empty($options->linktext)) {
167 $this->linktext = $options->linktext;
168 } else {
169 $this->linktext = get_string('comments');
170 }
866354a9 171
c1951ea9
DC
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;
179
116a48b1
SH
180 // setup notoggle
181 if (!empty($options->notoggle)) {
182 $this->set_notoggle($options->notoggle);
183 }
d664d673 184
116a48b1
SH
185 // setup notoggle
186 if (!empty($options->autostart)) {
187 $this->set_autostart($options->autostart);
188 }
287ccb31 189
93b86f62
SH
190 // setup displaycancel
191 if (!empty($options->displaycancel)) {
192 $this->set_displaycancel($options->displaycancel);
193 }
194
c1951ea9 195 // setup displaytotalcount
866354a9 196 if (!empty($options->showcount)) {
c1951ea9 197 $this->set_displaytotalcount($options->showcount);
866354a9 198 }
36051c9e 199
d846488e
DC
200 // setting post and view permissions
201 $this->check_permissions();
202
36051c9e 203 // load template
a2592fec
AD
204 $this->template = html_writer::start_tag('div', array('class' => 'comment-message'));
205
206 $this->template .= html_writer::start_tag('div', array('class' => 'comment-message-meta'));
207
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'));
211
212 $this->template .= html_writer::end_tag('div'); // .comment-message-meta
213 $this->template .= html_writer::tag('div', '___content___', array('class' => 'text'));
214
215 $this->template .= html_writer::end_tag('div'); // .comment-message
216
36051c9e 217 if (!empty($this->plugintype)) {
c1951ea9 218 $this->template = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'template', array($this->comment_param), $this->template);
1bcb7eb5 219 }
220
36051c9e
DC
221 unset($options);
222 }
223
224 /**
225 * Receive nonjs comment parameters
c1951ea9
DC
226 *
227 * @param moodle_page $page The page object to initialise comments within
228 * If not provided the global $PAGE is used
36051c9e 229 */
c1951ea9
DC
230 public static function init(moodle_page $page = null) {
231 global $PAGE;
232
233 if (empty($page)) {
234 $page = $PAGE;
235 }
36051c9e 236 // setup variables for non-js interface
c1951ea9 237 self::$nonjs = optional_param('nonjscomment', '', PARAM_ALPHANUM);
36051c9e
DC
238 self::$comment_itemid = optional_param('comment_itemid', '', PARAM_INT);
239 self::$comment_context = optional_param('comment_context', '', PARAM_INT);
287ccb31 240 self::$comment_page = optional_param('comment_page', '', PARAM_INT);
aff24313 241 self::$comment_area = optional_param('comment_area', '', PARAM_AREA);
36051c9e 242
c1951ea9
DC
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');
1bcb7eb5 247 }
248
c1951ea9
DC
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
c1951ea9 257 */
d846488e 258 public function set_component($component) {
c1951ea9
DC
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 }
7ade777c 262 $this->component = $component;
d846488e 263 list($this->plugintype, $this->pluginname) = normalize_component($component);
d846488e 264 }
866354a9 265
c1951ea9
DC
266 /**
267 * Determines if the user can view the comment.
268 *
269 * @param bool $value
270 */
d846488e 271 public function set_view_permission($value) {
c1951ea9 272 $this->viewcap = (bool)$value;
d846488e
DC
273 }
274
c1951ea9
DC
275 /**
276 * Determines if the user can post a comment
277 *
278 * @param bool $value
279 */
d846488e 280 public function set_post_permission($value) {
c1951ea9 281 $this->postcap = (bool)$value;
1bcb7eb5 282 }
283
284 /**
36051c9e
DC
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
1bcb7eb5 289 */
05fe3ab5 290 private function check_permissions() {
36051c9e
DC
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)) {
c1951ea9 294 $permissions = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'permissions', array($this->comment_param), array('post'=>false, 'view'=>false));
5167f4c9
SH
295 $this->postcap = $this->postcap && $permissions['post'];
296 $this->viewcap = $this->viewcap && $permissions['view'];
6f5e0852 297 }
1bcb7eb5 298 }
299
2f181b8c
SH
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 }
312
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 ));
c1951ea9 319 $link->remove_params(array('comment_page'));
2f181b8c
SH
320 return $link;
321 }
d664d673 322
116a48b1
SH
323 /**
324 * Sets the value of the notoggle option.
d664d673 325 *
116a48b1
SH
326 * If set to true then the user will not be able to expand and collase
327 * the comment section.
328 *
d664d673 329 * @param bool $newvalue
116a48b1
SH
330 */
331 public function set_notoggle($newvalue = true) {
332 $this->notoggle = (bool)$newvalue;
333 }
d664d673 334
116a48b1
SH
335 /**
336 * Sets the value of the autostart option.
d664d673 337 *
116a48b1
SH
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 *
d664d673 341 * @param bool $newvalue
116a48b1
SH
342 */
343 public function set_autostart($newvalue = true) {
344 $this->autostart = (bool)$newvalue;
345 }
346
93b86f62
SH
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 }
358
c1951ea9
DC
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 }
370
116a48b1
SH
371 /**
372 * Initialises the JavaScript that enchances the comment API.
d664d673 373 *
116a48b1
SH
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) {
378
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;
116a48b1
SH
386 $options->component = $this->component;
387 $options->notoggle = $this->notoggle;
388 $options->autostart = $this->autostart;
389
390 $page->requires->js_init_call('M.core_comment.init', array($options), true);
391
392 return true;
393 }
2f181b8c 394
1bcb7eb5 395 /**
36051c9e
DC
396 * Prepare comment code in html
397 * @param boolean $return
35453657 398 * @return string|void
1bcb7eb5 399 */
36051c9e 400 public function output($return = true) {
4b201bc6 401 global $PAGE, $OUTPUT;
2f181b8c 402 static $template_printed;
d664d673 403
116a48b1 404 $this->initialise_javascript($PAGE);
36051c9e
DC
405
406 if (!empty(self::$nonjs)) {
76951100 407 // return non js comments interface
a5306210 408 return $this->print_comments(self::$comment_page, $return, true);
36051c9e 409 }
76951100 410
4b201bc6 411 $html = '';
8aab3229 412
36051c9e 413 // print html template
76951100 414 // Javascript will use the template to render new comments
c1951ea9 415 if (empty($template_printed) && $this->can_view()) {
8aab3229 416 $html .= html_writer::tag('div', $this->template, array('style' => 'display:none', 'id' => 'cmt-tmpl'));
4b201bc6 417 $template_printed = true;
36051c9e
DC
418 }
419
c1951ea9 420 if ($this->can_view()) {
36051c9e 421 // print commenting icon and tooltip
8aab3229
SH
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'));
424
93b86f62
SH
425 if (!$this->notoggle) {
426 // If toggling is enabled (notoggle=false) then print the controls to toggle
427 // comments open and closed
c1951ea9
DC
428 $countstring = '';
429 if ($this->displaytotalcount) {
430 $countstring = '('.$this->count().')';
431 }
9fe40de6
NK
432 $collapsedimage= 't/collapsed';
433 if (right_to_left()) {
434 $collapsedimage= 't/collapsed_rtl';
435 } else {
436 $collapsedimage= 't/collapsed';
437 }
8aab3229 438 $html .= html_writer::start_tag('a', array('class' => 'comment-link', 'id' => 'comment-link-'.$this->cid, 'href' => '#'));
9fe40de6 439 $html .= html_writer::empty_tag('img', array('id' => 'comment-img-'.$this->cid, 'src' => $OUTPUT->pix_url($collapsedimage), 'alt' => $this->linktext, 'title' => $this->linktext));
c1951ea9 440 $html .= html_writer::tag('span', $this->linktext.' '.$countstring, array('id' => 'comment-link-text-'.$this->cid));
8aab3229 441 $html .= html_writer::end_tag('a');
034699d2
DC
442 }
443
8aab3229 444 $html .= html_writer::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid, 'class' => 'comment-ctrl'));
8aab3229 445
93b86f62
SH
446 if ($this->autostart) {
447 // If autostart has been enabled print the comments list immediatly
c1951ea9
DC
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'));
76951100 450 $html .= $this->print_comments(0, true, false);
8aab3229 451 $html .= html_writer::end_tag('ul'); // .comment-list
3e34183a
DC
452 $html .= $this->get_pagination(0);
453 } else {
c1951ea9
DC
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'));
8aab3229
SH
456 $html .= html_writer::end_tag('ul'); // .comment-list
457 $html .= html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
3e34183a 458 }
36051c9e 459
c1951ea9 460 if ($this->can_post()) {
8aab3229
SH
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
466
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));
469
93b86f62 470 if ($this->displaycancel) {
8aab3229
SH
471 $html .= html_writer::tag('span', ' | ');
472 $html .= html_writer::link('#', get_string('cancel'), array('id' => 'comment-action-cancel-'.$this->cid));
034699d2 473 }
8aab3229
SH
474
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'));
36051c9e
DC
478 }
479
8aab3229
SH
480 $html .= html_writer::end_tag('div'); // .comment-ctrl
481 $html .= html_writer::end_tag('div'); // .mdl-left
36051c9e
DC
482 } else {
483 $html = '';
484 }
485
486 if ($return) {
487 return $html;
488 } else {
489 echo $html;
1bcb7eb5 490 }
1bcb7eb5 491 }
36051c9e 492
1bcb7eb5 493 /**
36051c9e 494 * Return matched comments
4df53d1a 495 *
36051c9e 496 * @param int $page
35453657 497 * @return array
1bcb7eb5 498 */
36051c9e
DC
499 public function get_comments($page = '') {
500 global $DB, $CFG, $USER, $OUTPUT;
c1951ea9 501 if (!$this->can_view()) {
36051c9e
DC
502 return false;
503 }
36051c9e
DC
504 if (!is_numeric($page)) {
505 $page = 0;
506 }
36051c9e 507 $params = array();
c1951ea9
DC
508 $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
509 $start = $page * $perpage;
2be0842e 510 $ufields = user_picture::fields('u');
25228004 511 $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
3a11c09f
PS
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;
36051c9e
DC
519
520 $comments = array();
367a75fa 521 $formatoptions = array('overflowdiv' => true);
c1951ea9 522 $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
25228004 523 foreach ($rs as $u) {
fbaea88f 524 $c = new stdClass();
3a11c09f
PS
525 $c->id = $u->cid;
526 $c->content = $u->ccontent;
527 $c->format = $u->cformat;
528 $c->timecreated = $u->ctimecreated;
6b969e14 529 $c->strftimeformat = get_string('strftimerecent', 'langconfig');
08a5988a 530 $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
efd6defe 531 $c->profileurl = $url->out(false);
4df53d1a 532 $c->fullname = fullname($u);
3a11c09f 533 $c->time = userdate($c->timecreated, get_string('strftimerecent', 'langconfig'));
367a75fa 534 $c->content = format_text($c->content, $c->format, $formatoptions);
3a11c09f 535 $c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
c1951ea9
DC
536
537 $candelete = $this->can_delete($c->id);
3a11c09f
PS
538 if (($USER->id == $u->id) || !empty($candelete)) {
539 $c->delete = true;
36051c9e 540 }
3a11c09f 541 $comments[] = $c;
36051c9e 542 }
3a11c09f
PS
543 $rs->close();
544
36051c9e
DC
545 if (!empty($this->plugintype)) {
546 // moodle module will filter comments
c1951ea9 547 $comments = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'display', array($comments, $this->comment_param), $comments);
36051c9e
DC
548 }
549
550 return $comments;
551 }
552
c1951ea9
DC
553 /**
554 * Returns the number of comments associated with the details of this object
555 *
556 * @global moodle_database $DB
557 * @return int
558 */
36051c9e 559 public function count() {
1bcb7eb5 560 global $DB;
c1951ea9
DC
561 if ($this->totalcommentcount === null) {
562 $this->totalcommentcount = $DB->count_records('comments', array('itemid' => $this->itemid, 'commentarea' => $this->commentarea, 'contextid' => $this->context->id));
36051c9e 563 }
c1951ea9 564 return $this->totalcommentcount;
36051c9e
DC
565 }
566
c1951ea9
DC
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 */
36051c9e 575 public function get_pagination($page = 0) {
c1951ea9 576 global $CFG, $OUTPUT;
36051c9e 577 $count = $this->count();
c1951ea9
DC
578 $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
579 $pages = (int)ceil($count/$perpage);
36051c9e 580 if ($pages == 1 || $pages == 0) {
1c64a9cd 581 return html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
36051c9e
DC
582 }
583 if (!empty(self::$nonjs)) {
584 // used in non-js interface
c1951ea9 585 return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page');
36051c9e
DC
586 } else {
587 // return ajax paging bar
588 $str = '';
3e34183a 589 $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid.'">';
36051c9e 590 for ($p=0; $p<$pages; $p++) {
36051c9e 591 if ($p == $page) {
c794595d
DC
592 $class = 'curpage';
593 } else {
594 $class = 'pageno';
1bcb7eb5 595 }
1150c1bd 596 $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid.'-'.$p.'">'.($p+1).'</a> ';
36051c9e
DC
597 }
598 $str .= '</div>';
599 }
600 return $str;
601 }
602
603 /**
604 * Add a new comment
c1951ea9
DC
605 *
606 * @global moodle_database $DB
36051c9e 607 * @param string $content
35453657
DC
608 * @param int $format
609 * @return stdClass
36051c9e
DC
610 */
611 public function add($content, $format = FORMAT_MOODLE) {
612 global $CFG, $DB, $USER, $OUTPUT;
c1951ea9 613 if (!$this->can_post()) {
36051c9e
DC
614 throw new comment_exception('nopermissiontocomment');
615 }
616 $now = time();
c1951ea9 617 $newcmt = new stdClass;
36051c9e
DC
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;
625
c1951ea9
DC
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));
36051c9e
DC
628
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'));
4df53d1a 633 $newcmt->fullname = fullname($USER);
c1951ea9 634 $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
4df53d1a 635 $newcmt->profileurl = $url->out();
367a75fa 636 $newcmt->content = format_text($newcmt->content, $format, array('overflowdiv'=>true));
36051c9e
DC
637 $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
638 return $newcmt;
639 } else {
640 throw new comment_exception('dbupdatefailed');
641 }
642 }
643
644 /**
645 * delete by context, commentarea and itemid
c1951ea9 646 * @param stdClass|array $param {
c343ddf8
DC
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 * }
650a0c0a 651 * @return boolean
36051c9e 652 */
9aa09d8f 653 public static function delete_comments($param) {
36051c9e 654 global $DB;
650a0c0a
DC
655 $param = (array)$param;
656 if (empty($param['contextid'])) {
657 return false;
658 }
659 $DB->delete_records('comments', $param);
b9a689e8 660 return true;
36051c9e
DC
661 }
662
c343ddf8
DC
663 /**
664 * Delete page_comments in whole course, used by course reset
c1951ea9
DC
665 *
666 * @param stdClass $context course context
c343ddf8 667 */
54352ac9 668 public static function reset_course_page_comments($context) {
c343ddf8
DC
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 }
679
36051c9e
DC
680 /**
681 * Delete a comment
c1951ea9 682 *
36051c9e 683 * @param int $commentid
35453657 684 * @return bool
36051c9e
DC
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 }
b9a689e8
DC
695 $DB->delete_records('comments', array('id'=>$commentid));
696 return true;
36051c9e
DC
697 }
698
76951100
DC
699 /**
700 * Print comments
c1951ea9 701 *
76951100 702 * @param int $page
35453657
DC
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
76951100
DC
706 */
707 public function print_comments($page = 0, $return = true, $nonjs = true) {
5dce9d1b 708 global $DB, $CFG, $PAGE;
c1951ea9
DC
709
710 if (!$this->can_view()) {
711 return '';
712 }
713
36051c9e 714 $html = '';
866354a9
DC
715 if (!(self::$comment_itemid == $this->itemid &&
716 self::$comment_context == $this->context->id &&
717 self::$comment_area == $this->commentarea)) {
36051c9e
DC
718 $page = 0;
719 }
720 $comments = $this->get_comments($page);
721
76951100
DC
722 $html = '';
723 if ($nonjs) {
c1951ea9
DC
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'));
76951100 726 }
c1951ea9
DC
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));
36051c9e 730 }
76951100 731 if ($nonjs) {
c1951ea9 732 $html .= html_writer::end_tag('ul');
3e34183a 733 $html .= $this->get_pagination($page);
76951100 734 }
c1951ea9
DC
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');
76951100 752 }
36051c9e
DC
753 if ($return) {
754 return $html;
755 } else {
756 echo $html;
757 }
758 }
759
c1951ea9
DC
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 */
76951100
DC
777 public function print_comment($cmt, $nonjs = true) {
778 global $OUTPUT;
36051c9e
DC
779 $patterns = array();
780 $replacements = array();
781
76951100 782 if (!empty($cmt->delete) && empty($nonjs)) {
c1951ea9
DC
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;
76951100 789 }
36051c9e
DC
790 $patterns[] = '___picture___';
791 $patterns[] = '___name___';
792 $patterns[] = '___content___';
793 $patterns[] = '___time___';
794 $replacements[] = $cmt->avatar;
4df53d1a 795 $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
36051c9e 796 $replacements[] = $cmt->content;
6b969e14 797 $replacements[] = userdate($cmt->timecreated, $cmt->strftimeformat);
36051c9e
DC
798
799 // use html template to format a single comment.
800 return str_replace($patterns, $replacements, $this->template);
801 }
c1951ea9
DC
802
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 }
817
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 }
826
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 }
835
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 }
845
846 /**
847 * Returns the component associated with the comment
848 * @return string
849 */
850 public function get_compontent() {
851 return $this->component;
852 }
853
854 /**
855 * Returns the context associated with the comment
856 * @return stdClass
857 */
858 public function get_context() {
859 return $this->context;
860 }
861
862 /**
863 * Returns the course id associated with the comment
864 * @return int
865 */
866 public function get_courseid() {
867 return $this->courseid;
868 }
869
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 }
878
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 }
887
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 }
36051c9e
DC
896}
897
35453657
DC
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 */
36051c9e 905class comment_exception extends moodle_exception {
1bcb7eb5 906}