Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Functions and classes for commenting
19
 *
20
 * @package   core
21
 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
defined('MOODLE_INTERNAL') || die();
25
 
26
/**
27
 * Comment is helper class to add/delete comments anywhere in moodle
28
 *
29
 * @package   core
30
 * @category  comment
31
 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
32
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 */
34
class comment {
35
    /** @var int there may be several comment box in one page so we need a client_id to recognize them */
36
    private $cid;
37
    /** @var string commentarea is used to specify different parts shared the same itemid */
38
    private $commentarea;
39
    /** @var int itemid is used to associate with commenting content */
40
    private $itemid;
41
    /** @var string this html snippet will be used as a template to build comment content */
42
    private $template;
43
    /** @var int The context id for comments */
44
    private $contextid;
45
    /** @var stdClass The context itself */
46
    private $context;
47
    /** @var int The course id for comments */
48
    private $courseid;
49
    /** @var stdClass course module object, only be used to help find pluginname automatically */
50
    private $cm;
51
    /**
52
     * The component that this comment is for.
53
     *
54
     * It is STRONGLY recommended to set this.
55
     * Added as a database field in 2.9, old comments will have a null component.
56
     *
57
     * @var string
58
     */
59
    private $component;
60
    /** @var string This is calculated by normalising the component */
61
    private $pluginname;
62
    /** @var string This is calculated by normalising the component */
63
    private $plugintype;
64
    /** @var bool Whether the user has the required capabilities/permissions to view comments. */
65
    private $viewcap = false;
66
    /** @var bool Whether the user has the required capabilities/permissions to post comments. */
67
    private $postcap = false;
68
    /** @var string to customize link text */
69
    private $linktext;
70
    /** @var bool If set to true then comment sections won't be able to be opened and closed instead they will always be visible. */
71
    protected $notoggle = false;
72
    /** @var bool If set to true comments are automatically loaded as soon as the page loads. */
73
    protected $autostart = false;
74
    /** @var bool If set to true the total count of comments is displayed when displaying comments. */
75
    protected $displaytotalcount = false;
76
    /** @var bool If set to true a cancel button will be shown on the form used to submit comments. */
77
    protected $displaycancel = false;
78
    /** @var int The number of comments associated with this comments params */
79
    protected $totalcommentcount = null;
80
 
81
    /**
82
     * Set to true to remove the col attribute from the textarea making it full width.
83
     * @var bool
84
     */
85
    protected $fullwidth = false;
86
 
87
    /** @var bool Use non-javascript UI */
88
    private static $nonjs = false;
89
    /** @var int comment itemid used in non-javascript UI */
90
    private static $comment_itemid = null;
91
    /** @var int comment context used in non-javascript UI */
92
    private static $comment_context = null;
93
    /** @var string comment area used in non-javascript UI */
94
    private static $comment_area = null;
95
    /** @var string comment page used in non-javascript UI */
96
    private static $comment_page = null;
97
    /** @var string comment itemid component in non-javascript UI */
98
    private static $comment_component = null;
99
    /** @var stdClass comment paramaters for callback. */
100
    protected $comment_param;
101
 
102
    /**
103
     * Construct function of comment class, initialise
104
     * class members
105
     *
106
     * @param stdClass $options {
107
     *            context => context context to use for the comment [required]
108
     *            component => string which plugin will comment being added to [required]
109
     *            itemid  => int the id of the associated item (forum post, glossary item etc) [required]
110
     *            area    => string comment area
111
     *            cm      => stdClass course module
112
     *            course  => course course object
113
     *            client_id => string an unique id to identify comment area
114
     *            autostart => boolean automatically expend comments
115
     *            showcount => boolean display the number of comments
116
     *            displaycancel => boolean display cancel button
117
     *            notoggle => boolean don't show/hide button
118
     *            linktext => string title of show/hide button
119
     * }
120
     */
121
    public function __construct(stdClass $options) {
122
        $this->viewcap = false;
123
        $this->postcap = false;
124
 
125
        // setup client_id
126
        if (!empty($options->client_id)) {
127
            $this->cid = $options->client_id;
128
        } else {
129
            $this->cid = uniqid();
130
        }
131
 
132
        // setup context
133
        if (!empty($options->context)) {
134
            $this->context = $options->context;
135
            $this->contextid = $this->context->id;
136
        } else if(!empty($options->contextid)) {
137
            $this->contextid = $options->contextid;
138
            $this->context = context::instance_by_id($this->contextid);
139
        } else {
140
            throw new \moodle_exception('invalidcontext');
141
        }
142
 
143
        if (!empty($options->component)) {
144
            // set and validate component
145
            $this->set_component($options->component);
146
        } else {
147
            // component cannot be empty
148
            throw new comment_exception('invalidcomponent');
149
        }
150
 
151
        // setup course
152
        // course will be used to generate user profile link
153
        if (!empty($options->course)) {
154
            $this->courseid = $options->course->id;
155
        } else if (!empty($options->courseid)) {
156
            $this->courseid = $options->courseid;
157
        } else {
158
            if ($coursecontext = $this->context->get_course_context(false)) {
159
                $this->courseid = $coursecontext->instanceid;
160
            } else {
161
                $this->courseid = SITEID;
162
            }
163
        }
164
 
165
        // setup coursemodule
166
        if (!empty($options->cm)) {
167
            $this->cm = $options->cm;
168
        } else {
169
            $this->cm = null;
170
        }
171
 
172
        // setup commentarea
173
        if (!empty($options->area)) {
174
            $this->commentarea = $options->area;
175
        }
176
 
177
        // setup itemid
178
        if (!empty($options->itemid)) {
179
            $this->itemid = $options->itemid;
180
        } else {
181
            $this->itemid = 0;
182
        }
183
 
184
        // setup customized linktext
185
        if (!empty($options->linktext)) {
186
            $this->linktext = $options->linktext;
187
        } else {
188
            $this->linktext = get_string('comments');
189
        }
190
 
191
        // setup options for callback functions
192
        $this->comment_param = new stdClass();
193
        $this->comment_param->context     = $this->context;
194
        $this->comment_param->courseid    = $this->courseid;
195
        $this->comment_param->cm          = $this->cm;
196
        $this->comment_param->commentarea = $this->commentarea;
197
        $this->comment_param->itemid      = $this->itemid;
198
 
199
        // setup notoggle
200
        if (!empty($options->notoggle)) {
201
            $this->set_notoggle($options->notoggle);
202
        }
203
 
204
        // setup notoggle
205
        if (!empty($options->autostart)) {
206
            $this->set_autostart($options->autostart);
207
        }
208
 
209
        // setup displaycancel
210
        if (!empty($options->displaycancel)) {
211
            $this->set_displaycancel($options->displaycancel);
212
        }
213
 
214
        // setup displaytotalcount
215
        if (!empty($options->showcount)) {
216
            $this->set_displaytotalcount($options->showcount);
217
        }
218
 
219
        // setting post and view permissions
220
        $this->check_permissions();
221
 
222
        // load template
223
        $this->template = html_writer::start_tag('div', array('class' => 'comment-message'));
224
 
225
        $this->template .= html_writer::start_tag('div', array('class' => 'comment-message-meta mr-3'));
226
 
227
        $this->template .= html_writer::tag('span', '___picture___', array('class' => 'picture'));
228
        $this->template .= html_writer::tag('span', '___name___', array('class' => 'user')) . ' - ';
229
        $this->template .= html_writer::tag('span', '___time___', array('class' => 'time'));
230
 
231
        $this->template .= html_writer::end_tag('div'); // .comment-message-meta
232
        $this->template .= html_writer::tag('div', '___content___', array('class' => 'text'));
233
 
234
        $this->template .= html_writer::end_tag('div'); // .comment-message
235
 
236
        if (!empty($this->plugintype)) {
237
            $this->template = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'template', array($this->comment_param), $this->template);
238
        }
239
 
240
        unset($options);
241
    }
242
 
243
    /**
244
     * Receive nonjs comment parameters
245
     *
246
     * @param moodle_page $page The page object to initialise comments within
247
     *                          If not provided the global $PAGE is used
248
     */
249
    public static function init(moodle_page $page = null) {
250
        global $PAGE;
251
 
252
        if (empty($page)) {
253
            $page = $PAGE;
254
        }
255
        // setup variables for non-js interface
256
        self::$nonjs = optional_param('nonjscomment', '', PARAM_ALPHANUM);
257
        self::$comment_itemid = optional_param('comment_itemid',  '', PARAM_INT);
258
        self::$comment_component = optional_param('comment_component', '', PARAM_COMPONENT);
259
        self::$comment_context = optional_param('comment_context', '', PARAM_INT);
260
        self::$comment_page = optional_param('comment_page',    '', PARAM_INT);
261
        self::$comment_area = optional_param('comment_area',    '', PARAM_AREA);
262
 
263
        $page->requires->strings_for_js(array(
264
                'addcomment',
265
                'comments',
266
                'commentsrequirelogin',
267
                'deletecommentbyon'
268
            ),
269
            'moodle'
270
        );
271
    }
272
 
273
    /**
274
     * Sets the component.
275
     *
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.
279
     *
280
     * @throws coding_exception if you try to change the component after it has been set.
281
     * @param string $component
282
     */
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');
286
        }
287
        $this->component = $component;
288
        list($this->plugintype, $this->pluginname) = core_component::normalize_component($component);
289
    }
290
 
291
    /**
292
     * Determines if the user can view the comment.
293
     *
294
     * @param bool $value
295
     */
296
    public function set_view_permission($value) {
297
        $this->viewcap = (bool)$value;
298
    }
299
 
300
    /**
301
     * Determines if the user can post a comment
302
     *
303
     * @param bool $value
304
     */
305
    public function set_post_permission($value) {
306
        $this->postcap = (bool)$value;
307
    }
308
 
309
    /**
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
314
     */
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'];
322
        }
323
    }
324
 
325
    /**
326
     * Gets a link for this page that will work with JS disabled.
327
     *
328
     * @global moodle_page $PAGE
329
     * @param moodle_page $page
330
     * @return moodle_url
331
     */
332
    public function get_nojslink(moodle_page $page = null) {
333
        if ($page === null) {
334
            global $PAGE;
335
            $page = $PAGE;
336
        }
337
 
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,
344
        ));
345
        $link->remove_params(array('comment_page'));
346
        return $link;
347
    }
348
 
349
    /**
350
     * Sets the value of the notoggle option.
351
     *
352
     * If set to true then the user will not be able to expand and collase
353
     * the comment section.
354
     *
355
     * @param bool $newvalue
356
     */
357
    public function set_notoggle($newvalue = true) {
358
        $this->notoggle = (bool)$newvalue;
359
    }
360
 
361
    /**
362
     * Sets the value of the autostart option.
363
     *
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.
366
     *
367
     * @param bool $newvalue
368
     */
369
    public function set_autostart($newvalue = true) {
370
        $this->autostart = (bool)$newvalue;
371
    }
372
 
373
    /**
374
     * Sets the displaycancel option
375
     *
376
     * If set to true then a cancel button will be shown when using the form
377
     * to post comments.
378
     *
379
     * @param bool $newvalue
380
     */
381
    public function set_displaycancel($newvalue = true) {
382
        $this->displaycancel = (bool)$newvalue;
383
    }
384
 
385
    /**
386
     * Sets the displaytotalcount option
387
     *
388
     * If set to true then the total number of comments will be displayed
389
     * when printing comments.
390
     *
391
     * @param bool $newvalue
392
     */
393
    public function set_displaytotalcount($newvalue = true) {
394
        $this->displaytotalcount = (bool)$newvalue;
395
    }
396
 
397
    /**
398
     * Initialises the JavaScript that enchances the comment API.
399
     *
400
     * @param moodle_page $page The moodle page object that the JavaScript should be
401
     *                          initialised for.
402
     */
403
    public function initialise_javascript(moodle_page $page) {
404
 
405
        $options = new stdClass;
406
        $options->client_id   = $this->cid;
407
        $options->commentarea = $this->commentarea;
408
        $options->itemid      = $this->itemid;
409
        $options->page        = 0;
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;
415
 
416
        $page->requires->js_init_call('M.core_comment.init', array($options), true);
417
 
418
        return true;
419
    }
420
 
421
    /**
422
     * Prepare comment code in html
423
     * @param  boolean $return
424
     * @return string|void
425
     */
426
    public function output($return = true) {
427
        global $PAGE, $OUTPUT;
428
        static $template_printed;
429
 
430
        $this->initialise_javascript($PAGE);
431
 
432
        if (!empty(self::$nonjs)) {
433
            // return non js comments interface
434
            return $this->print_comments(self::$comment_page, $return, true);
435
        }
436
 
437
        $html = '';
438
 
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;
444
        }
445
 
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'));
450
 
451
            if (!$this->notoggle) {
452
                // If toggling is enabled (notoggle=false) then print the controls to toggle
453
                // comments open and closed
454
                $countstring = '';
455
                if ($this->displaytotalcount) {
11 efrain 456
                    $countstring = '(' . html_writer::span($this->count(), 'comment-link-count') . ')';
1 efrain 457
                }
458
                $collapsedimage= 't/collapsed';
459
                if (right_to_left()) {
460
                    $collapsedimage= 't/collapsed_rtl';
461
                } else {
462
                    $collapsedimage= 't/collapsed';
463
                }
464
                $html .= html_writer::start_tag('a', array(
465
                    'class' => 'comment-link',
466
                    'id' => 'comment-link-'.$this->cid,
467
                    'href' => '#',
468
                    'role' => 'button',
469
                    'aria-expanded' => 'false')
470
                );
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');
474
            }
475
 
476
            $html .= html_writer::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid, 'class' => 'comment-ctrl'));
477
 
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);
485
            } else {
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'));
490
            }
491
 
492
            if ($this->can_post()) {
493
                // print posting textarea
494
                $textareaattrs = array(
495
                    'name' => 'content',
496
                    'rows' => 2,
497
                    'id' => 'dlg-content-'.$this->cid,
498
                    'aria-label' => get_string('addcomment')
499
                );
500
                if (!$this->fullwidth) {
501
                    $textareaattrs['cols'] = '20';
502
                } else {
503
                    $textareaattrs['class'] = 'fullwidth';
504
                }
505
 
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
513
 
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));
516
 
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));
520
                }
521
 
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'));
525
            }
526
 
527
            $html .= html_writer::end_tag('div'); // .comment-ctrl
528
            $html .= html_writer::end_tag('div'); // .mdl-left
529
        } else {
530
            $html = '';
531
        }
532
 
533
        if ($return) {
534
            return $html;
535
        } else {
536
            echo $html;
537
        }
538
    }
539
 
540
    /**
541
     * Return matched comments
542
     *
543
     * @param  int $page
544
     * @param  str $sortdirection sort direction, ASC or DESC
545
     * @return array|false
546
     */
547
    public function get_comments($page = '', $sortdirection = 'DESC') {
548
        global $DB, $CFG, $USER, $OUTPUT;
549
        if (!$this->can_view()) {
550
            return false;
551
        }
552
        if (!is_numeric($page)) {
553
            $page = 0;
554
        }
555
        $params = array();
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;
560
 
561
        list($componentwhere, $component) = $this->get_component_select_sql('c');
562
        if ($component) {
563
            $params['component'] = $component;
564
        }
565
 
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
568
                  FROM {comments} c
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
573
                       $componentwhere
574
              ORDER BY c.timecreated $sortdirection, c.id $sortdirection";
575
        $params['contextid'] = $this->contextid;
576
        $params['commentarea'] = $this->commentarea;
577
        $params['itemid'] = $this->itemid;
578
 
579
        $comments = array();
580
        $formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
581
        $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
582
        foreach ($rs as $u) {
583
            $c = new stdClass();
584
            $c->id          = $u->cid;
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));
595
            $c->userid = $u->id;
596
 
597
            if ($this->can_delete($c)) {
598
                $c->delete = true;
599
            }
600
            $comments[] = $c;
601
        }
602
        $rs->close();
603
 
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);
607
        }
608
 
609
        return $comments;
610
    }
611
 
612
    /**
613
     * Returns an SQL fragment and param for selecting on component.
614
     * @param string $alias
615
     * @return array
616
     */
617
    protected function get_component_select_sql($alias = '') {
618
        $component = $this->get_component();
619
        if ($alias) {
620
            $alias = $alias.'.';
621
        }
622
        if (empty($component)) {
623
            $componentwhere = "{$alias}component IS NULL";
624
            $component = null;
625
        } else {
626
            $componentwhere = "({$alias}component IS NULL OR {$alias}component = :component)";
627
        }
628
        return array($componentwhere, $component);
629
    }
630
 
631
    /**
632
     * Returns the number of comments associated with the details of this object
633
     *
634
     * @global moodle_database $DB
635
     * @return int
636
     */
637
    public function count() {
638
        global $DB;
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';
642
            $params = array(
643
                'itemid' => $this->itemid,
644
                'commentarea' => $this->commentarea,
645
                'contextid' => $this->context->id,
646
            );
647
            if ($component) {
648
                $params['component'] = $component;
649
            }
650
 
651
            $this->totalcommentcount = $DB->count_records_select('comments', $where, $params);
652
        }
653
        return $this->totalcommentcount;
654
    }
655
 
656
    /**
657
     * Returns HTML to display a pagination bar
658
     *
659
     * @global stdClass $CFG
660
     * @global core_renderer $OUTPUT
661
     * @param int $page
662
     * @return string
663
     */
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'));
671
        }
672
        if (!empty(self::$nonjs)) {
673
            // used in non-js interface
674
            return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page');
675
        } else {
676
            // return ajax paging bar
677
            $str = '';
678
            $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid.'">';
679
            for ($p=0; $p<$pages; $p++) {
680
                if ($p == $page) {
681
                    $class = 'curpage';
682
                } else {
683
                    $class = 'pageno';
684
                }
685
                $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid.'-'.$p.'">'.($p+1).'</a> ';
686
            }
687
            $str .= '</div>';
688
        }
689
        return $str;
690
    }
691
 
692
    /**
693
     * Add a new comment
694
     *
695
     * @global moodle_database $DB
696
     * @param string $content
697
     * @param int $format
698
     * @return stdClass
699
     */
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');
704
        }
705
        $now = time();
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;
715
 
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));
718
 
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));
729
 
730
            $commentlist = array($newcmt);
731
 
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,
735
                                               $this->pluginname,
736
                                               'comment',
737
                                               'display',
738
                                               array($commentlist, $this->comment_param),
739
                                               $commentlist);
740
                $newcmt = $commentlist[0];
741
            }
742
            $newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat);
743
 
744
            // Trigger comment created event.
745
            if (core_component::is_core_subsystem($this->component)) {
746
                $eventclassname = '\\core\\event\\' . $this->component . '_comment_created';
747
            } else {
748
                $eventclassname = '\\' . $this->component . '\\event\comment_created';
749
            }
750
            if (class_exists($eventclassname)) {
751
                $event = $eventclassname::create(
752
                        array(
753
                            'context' => $this->context,
754
                            'objectid' => $newcmt->id,
755
                            'other' => array(
756
                                'itemid' => $this->itemid
757
                                )
758
                            ));
759
                $event->trigger();
760
            }
761
 
762
            return $newcmt;
763
        } else {
764
            throw new comment_exception('dbupdatefailed');
765
        }
766
    }
767
 
768
    /**
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]
774
     * }
775
     * @return boolean
776
     */
777
    public static function delete_comments($param) {
778
        global $DB;
779
        $param = (array)$param;
780
        if (empty($param['contextid'])) {
781
            return false;
782
        }
783
        $DB->delete_records('comments', $param);
784
        return true;
785
    }
786
 
787
    /**
788
     * Delete page_comments in whole course, used by course reset
789
     *
790
     * @param stdClass $context course context
791
     */
792
    public static function reset_course_page_comments($context) {
793
        global $DB;
794
        $contexts = array();
795
        $contexts[] = $context->id;
796
        $children = $context->get_child_contexts();
797
        foreach ($children as $c) {
798
            $contexts[] = $c->id;
799
        }
800
        list($ids, $params) = $DB->get_in_or_equal($contexts);
801
        $DB->delete_records_select('comments', "commentarea='page_comments' AND contextid $ids", $params);
802
    }
803
 
804
    /**
805
     * Delete a comment
806
     *
807
     * @param  int|stdClass $comment The id of a comment, or a comment record.
808
     * @return bool
809
     */
810
    public function delete($comment) {
811
        global $DB;
812
        if (is_object($comment)) {
813
            $commentid = $comment->id;
814
        } else {
815
            $commentid = $comment;
816
            $comment = $DB->get_record('comments', ['id' => $commentid]);
817
        }
818
 
819
        if (!$comment) {
820
            throw new comment_exception('dbupdatefailed');
821
        }
822
        if (!$this->can_delete($comment)) {
823
            throw new comment_exception('nopermissiontocomment');
824
        }
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';
829
        } else {
830
            $eventclassname = '\\' . $this->component . '\\event\comment_deleted';
831
        }
832
        if (class_exists($eventclassname)) {
833
            $event = $eventclassname::create(
834
                    array(
835
                        'context' => $this->context,
836
                        'objectid' => $commentid,
837
                        'other' => array(
838
                            'itemid' => $this->itemid
839
                            )
840
                        ));
841
            $event->add_record_snapshot('comments', $comment);
842
            $event->trigger();
843
        }
844
        return true;
845
    }
846
 
847
    /**
848
     * Print comments
849
     *
850
     * @param int $page
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
854
     */
855
    public function print_comments($page = 0, $return = true, $nonjs = true) {
856
        global $DB, $CFG, $PAGE;
857
 
858
        if (!$this->can_view()) {
859
            return '';
860
        }
861
 
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
866
        )) {
867
            $page = 0;
868
        }
869
        $comments = $this->get_comments($page);
870
 
871
        $html = '';
872
        if ($nonjs) {
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'));
875
        }
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));
879
        }
880
        if ($nonjs) {
881
            $html .= html_writer::end_tag('ul');
882
            $html .= $this->get_pagination($page);
883
        }
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');
901
        }
902
        if ($return) {
903
            return $html;
904
        } else {
905
            echo $html;
906
        }
907
    }
908
 
909
    /**
910
     * Returns an array containing comments in HTML format.
911
     *
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?
922
     * }
923
     * @param bool $nonjs
924
     * @return array
925
     */
926
    public function print_comment($cmt, $nonjs = true) {
927
        global $OUTPUT;
928
        $patterns = array();
929
        $replacements = array();
930
 
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));
936
 
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;
941
        }
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;
950
 
951
        // use html template to format a single comment.
952
        return str_replace($patterns, $replacements, $this->template);
953
    }
954
 
955
    /**
956
     * Revoke validate callbacks
957
     *
958
     * @param array $params addtionall parameters need to add to callbacks
959
     */
960
    protected function validate($params=array()) {
961
        foreach ($params as $key=>$value) {
962
            $this->comment_param->$key = $value;
963
        }
964
        $validation = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'validate', array($this->comment_param), false);
965
        if (!$validation) {
966
            throw new comment_exception('invalidcommentparam');
967
        }
968
    }
969
 
970
    /**
971
     * Returns true if the user is able to view comments
972
     * @return bool
973
     */
974
    public function can_view() {
975
        $this->validate();
976
        return !empty($this->viewcap);
977
    }
978
 
979
    /**
980
     * Returns true if the user can add comments against this comment description
981
     * @return bool
982
     */
983
    public function can_post() {
984
        $this->validate();
985
        return isloggedin() && !empty($this->postcap);
986
    }
987
 
988
    /**
989
     * Returns true if the user can delete this comment.
990
     *
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.
993
     *
994
     * A database call is avoided if a comment record is passed.
995
     *
996
     * @param int|stdClass $comment The id of a comment, or a comment record.
997
     * @return bool
998
     */
999
    public function can_delete($comment) {
1000
        global $USER, $DB;
1001
        if (is_object($comment)) {
1002
            $commentid = $comment->id;
1003
        } else {
1004
            $commentid = $comment;
1005
        }
1006
 
1007
        $this->validate(array('commentid'=>$commentid));
1008
 
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);
1012
        }
1013
 
1014
        $hascapability = has_capability('moodle/comment:delete', $this->context);
1015
        $owncomment = $USER->id == $comment->userid;
1016
 
1017
        return ($hascapability || ($owncomment && $this->can_post()));
1018
    }
1019
 
1020
    /**
1021
     * Returns the component associated with the comment.
1022
     *
1023
     * @return string
1024
     */
1025
    public function get_component() {
1026
        return $this->component;
1027
    }
1028
 
1029
    /**
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()
1033
     * @return string
1034
     */
1035
    public function get_compontent() {
1036
        return $this->get_component();
1037
    }
1038
 
1039
    /**
1040
     * Returns the context associated with the comment
1041
     * @return stdClass
1042
     */
1043
    public function get_context() {
1044
        return $this->context;
1045
    }
1046
 
1047
    /**
1048
     * Returns the course id associated with the comment
1049
     * @return int
1050
     */
1051
    public function get_courseid() {
1052
        return $this->courseid;
1053
    }
1054
 
1055
    /**
1056
     * Returns the course module associated with the comment
1057
     *
1058
     * @return stdClass
1059
     */
1060
    public function get_cm() {
1061
        return $this->cm;
1062
    }
1063
 
1064
    /**
1065
     * Returns the item id associated with the comment
1066
     *
1067
     * @return int
1068
     */
1069
    public function get_itemid() {
1070
        return $this->itemid;
1071
    }
1072
 
1073
    /**
1074
     * Returns the comment area associated with the commentarea
1075
     *
1076
     * @return string
1077
     */
1078
    public function get_commentarea() {
1079
        return $this->commentarea;
1080
    }
1081
 
1082
    /**
1083
     * Make the comments textarea fullwidth.
1084
     *
1085
     * @since 2.8.1 + 2.7.4
1086
     * @param bool $fullwidth
1087
     */
1088
    public function set_fullwidth($fullwidth = true) {
1089
        $this->fullwidth = (bool)$fullwidth;
1090
    }
1091
 
1092
    /**
1093
     * Return the template.
1094
     *
1095
     * @since 3.1
1096
     * @return string
1097
     */
1098
    public function get_template() {
1099
        return $this->template;
1100
    }
1101
 
1102
    /**
1103
     * Return the cid.
1104
     *
1105
     * @since 3.1
1106
     * @return string
1107
     */
1108
    public function get_cid() {
1109
        return $this->cid;
1110
    }
1111
 
1112
    /**
1113
     * Return the link text.
1114
     *
1115
     * @since 3.1
1116
     * @return string
1117
     */
1118
    public function get_linktext() {
1119
        return $this->linktext;
1120
    }
1121
 
1122
    /**
1123
     * Return no toggle.
1124
     *
1125
     * @since 3.1
1126
     * @return bool
1127
     */
1128
    public function get_notoggle() {
1129
        return $this->notoggle;
1130
    }
1131
 
1132
    /**
1133
     * Return display total count.
1134
     *
1135
     * @since 3.1
1136
     * @return bool
1137
     */
1138
    public function get_displaytotalcount() {
1139
        return $this->displaytotalcount;
1140
    }
1141
 
1142
    /**
1143
     * Return display cancel.
1144
     *
1145
     * @since 3.1
1146
     * @return bool
1147
     */
1148
    public function get_displaycancel() {
1149
        return $this->displaycancel;
1150
    }
1151
 
1152
    /**
1153
     * Return fullwidth.
1154
     *
1155
     * @since 3.1
1156
     * @return bool
1157
     */
1158
    public function get_fullwidth() {
1159
        return $this->fullwidth;
1160
    }
1161
 
1162
    /**
1163
     * Return autostart.
1164
     *
1165
     * @since 3.1
1166
     * @return bool
1167
     */
1168
    public function get_autostart() {
1169
        return $this->autostart;
1170
    }
1171
 
1172
}
1173
 
1174
/**
1175
 * Comment exception class
1176
 *
1177
 * @package   core
1178
 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
1179
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1180
 */
1181
class comment_exception extends moodle_exception {
1182
}