Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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
                'commentscount',
267
                'commentsrequirelogin',
268
                'deletecommentbyon'
269
            ),
270
            'moodle'
271
        );
272
    }
273
 
274
    /**
275
     * Sets the component.
276
     *
277
     * This method shouldn't be public, changing the component once it has been set potentially
278
     * invalidates permission checks.
279
     * A coding_error is now thrown if code attempts to change the component.
280
     *
281
     * @throws coding_exception if you try to change the component after it has been set.
282
     * @param string $component
283
     */
284
    public function set_component($component) {
285
        if (!empty($this->component) && $this->component !== $component) {
286
            throw new coding_exception('You cannot change the component of a comment once it has been set');
287
        }
288
        $this->component = $component;
289
        list($this->plugintype, $this->pluginname) = core_component::normalize_component($component);
290
    }
291
 
292
    /**
293
     * Determines if the user can view the comment.
294
     *
295
     * @param bool $value
296
     */
297
    public function set_view_permission($value) {
298
        $this->viewcap = (bool)$value;
299
    }
300
 
301
    /**
302
     * Determines if the user can post a comment
303
     *
304
     * @param bool $value
305
     */
306
    public function set_post_permission($value) {
307
        $this->postcap = (bool)$value;
308
    }
309
 
310
    /**
311
     * check posting comments permission
312
     * It will check based on user roles and ask modules
313
     * If you need to check permission by modules, a
314
     * function named $pluginname_check_comment_post must be implemented
315
     */
316
    private function check_permissions() {
317
        $this->postcap = has_capability('moodle/comment:post', $this->context);
318
        $this->viewcap = has_capability('moodle/comment:view', $this->context);
319
        if (!empty($this->plugintype)) {
320
            $permissions = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'permissions', array($this->comment_param), array('post'=>false, 'view'=>false));
321
            $this->postcap = $this->postcap && $permissions['post'];
322
            $this->viewcap = $this->viewcap && $permissions['view'];
323
        }
324
    }
325
 
326
    /**
327
     * Gets a link for this page that will work with JS disabled.
328
     *
329
     * @global moodle_page $PAGE
330
     * @param moodle_page $page
331
     * @return moodle_url
332
     */
333
    public function get_nojslink(moodle_page $page = null) {
334
        if ($page === null) {
335
            global $PAGE;
336
            $page = $PAGE;
337
        }
338
 
339
        $link = new moodle_url($page->url, array(
340
            'nonjscomment'    => true,
341
            'comment_itemid'  => $this->itemid,
342
            'comment_context' => $this->context->id,
343
            'comment_component' => $this->get_component(),
344
            'comment_area'    => $this->commentarea,
345
        ));
346
        $link->remove_params(array('comment_page'));
347
        return $link;
348
    }
349
 
350
    /**
351
     * Sets the value of the notoggle option.
352
     *
353
     * If set to true then the user will not be able to expand and collase
354
     * the comment section.
355
     *
356
     * @param bool $newvalue
357
     */
358
    public function set_notoggle($newvalue = true) {
359
        $this->notoggle = (bool)$newvalue;
360
    }
361
 
362
    /**
363
     * Sets the value of the autostart option.
364
     *
365
     * If set to true then the comments will be loaded during page load.
366
     * Normally this happens only once the user expands the comment section.
367
     *
368
     * @param bool $newvalue
369
     */
370
    public function set_autostart($newvalue = true) {
371
        $this->autostart = (bool)$newvalue;
372
    }
373
 
374
    /**
375
     * Sets the displaycancel option
376
     *
377
     * If set to true then a cancel button will be shown when using the form
378
     * to post comments.
379
     *
380
     * @param bool $newvalue
381
     */
382
    public function set_displaycancel($newvalue = true) {
383
        $this->displaycancel = (bool)$newvalue;
384
    }
385
 
386
    /**
387
     * Sets the displaytotalcount option
388
     *
389
     * If set to true then the total number of comments will be displayed
390
     * when printing comments.
391
     *
392
     * @param bool $newvalue
393
     */
394
    public function set_displaytotalcount($newvalue = true) {
395
        $this->displaytotalcount = (bool)$newvalue;
396
    }
397
 
398
    /**
399
     * Initialises the JavaScript that enchances the comment API.
400
     *
401
     * @param moodle_page $page The moodle page object that the JavaScript should be
402
     *                          initialised for.
403
     */
404
    public function initialise_javascript(moodle_page $page) {
405
 
406
        $options = new stdClass;
407
        $options->client_id   = $this->cid;
408
        $options->commentarea = $this->commentarea;
409
        $options->itemid      = $this->itemid;
410
        $options->page        = 0;
411
        $options->courseid    = $this->courseid;
412
        $options->contextid   = $this->contextid;
413
        $options->component   = $this->component;
414
        $options->notoggle    = $this->notoggle;
415
        $options->autostart   = $this->autostart;
416
 
417
        $page->requires->js_init_call('M.core_comment.init', array($options), true);
418
 
419
        return true;
420
    }
421
 
422
    /**
423
     * Prepare comment code in html
424
     * @param  boolean $return
425
     * @return string|void
426
     */
427
    public function output($return = true) {
428
        global $PAGE, $OUTPUT;
429
        static $template_printed;
430
 
431
        $this->initialise_javascript($PAGE);
432
 
433
        if (!empty(self::$nonjs)) {
434
            // return non js comments interface
435
            return $this->print_comments(self::$comment_page, $return, true);
436
        }
437
 
438
        $html = '';
439
 
440
        // print html template
441
        // Javascript will use the template to render new comments
442
        if (empty($template_printed) && $this->can_view()) {
443
            $html .= html_writer::tag('div', $this->template, array('style' => 'display:none', 'id' => 'cmt-tmpl'));
444
            $template_printed = true;
445
        }
446
 
447
        if ($this->can_view()) {
448
            // print commenting icon and tooltip
449
            $html .= html_writer::start_tag('div', array('class' => 'mdl-left'));
450
            $html .= html_writer::link($this->get_nojslink($PAGE), get_string('showcommentsnonjs'), array('class' => 'showcommentsnonjs'));
451
 
452
            if (!$this->notoggle) {
453
                // If toggling is enabled (notoggle=false) then print the controls to toggle
454
                // comments open and closed
455
                $countstring = '';
456
                if ($this->displaytotalcount) {
457
                    $countstring = '('.$this->count().')';
458
                }
459
                $collapsedimage= 't/collapsed';
460
                if (right_to_left()) {
461
                    $collapsedimage= 't/collapsed_rtl';
462
                } else {
463
                    $collapsedimage= 't/collapsed';
464
                }
465
                $html .= html_writer::start_tag('a', array(
466
                    'class' => 'comment-link',
467
                    'id' => 'comment-link-'.$this->cid,
468
                    'href' => '#',
469
                    'role' => 'button',
470
                    'aria-expanded' => 'false')
471
                );
472
                $html .= $OUTPUT->pix_icon($collapsedimage, $this->linktext);
473
                $html .= html_writer::tag('span', $this->linktext.' '.$countstring, array('id' => 'comment-link-text-'.$this->cid));
474
                $html .= html_writer::end_tag('a');
475
            }
476
 
477
            $html .= html_writer::start_tag('div', array('id' => 'comment-ctrl-'.$this->cid, 'class' => 'comment-ctrl'));
478
 
479
            if ($this->autostart) {
480
                // If autostart has been enabled print the comments list immediatly
481
                $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list comments-loaded'));
482
                $html .= html_writer::tag('li', '', array('class' => 'first'));
483
                $html .= $this->print_comments(0, true, false);
484
                $html .= html_writer::end_tag('ul'); // .comment-list
485
                $html .= $this->get_pagination(0);
486
            } else {
487
                $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list'));
488
                $html .= html_writer::tag('li', '', array('class' => 'first'));
489
                $html .= html_writer::end_tag('ul'); // .comment-list
490
                $html .= html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
491
            }
492
 
493
            if ($this->can_post()) {
494
                // print posting textarea
495
                $textareaattrs = array(
496
                    'name' => 'content',
497
                    'rows' => 2,
498
                    'id' => 'dlg-content-'.$this->cid,
499
                    'aria-label' => get_string('addcomment')
500
                );
501
                if (!$this->fullwidth) {
502
                    $textareaattrs['cols'] = '20';
503
                } else {
504
                    $textareaattrs['class'] = 'fullwidth';
505
                }
506
 
507
                $html .= html_writer::start_tag('div', array('class' => 'comment-area'));
508
                $html .= html_writer::start_tag('div', array('class' => 'db'));
509
                $html .= html_writer::tag('label',
510
                        get_string('comment', 'comment'),
511
                        ['for' => 'dlg-content-'.$this->cid, 'class' => 'sr-only']);
512
                $html .= html_writer::tag('textarea', '', $textareaattrs);
513
                $html .= html_writer::end_tag('div'); // .db
514
 
515
                $html .= html_writer::start_tag('div', array('class' => 'fd', 'id' => 'comment-action-'.$this->cid));
516
                $html .= html_writer::link('#', get_string('savecomment'), array('id' => 'comment-action-post-'.$this->cid));
517
 
518
                if ($this->displaycancel) {
519
                    $html .= html_writer::tag('span', ' | ');
520
                    $html .= html_writer::link('#', get_string('cancel'), array('id' => 'comment-action-cancel-'.$this->cid));
521
                }
522
 
523
                $html .= html_writer::end_tag('div'); // .fd
524
                $html .= html_writer::end_tag('div'); // .comment-area
525
                $html .= html_writer::tag('div', '', array('class' => 'clearer'));
526
            }
527
 
528
            $html .= html_writer::end_tag('div'); // .comment-ctrl
529
            $html .= html_writer::end_tag('div'); // .mdl-left
530
        } else {
531
            $html = '';
532
        }
533
 
534
        if ($return) {
535
            return $html;
536
        } else {
537
            echo $html;
538
        }
539
    }
540
 
541
    /**
542
     * Return matched comments
543
     *
544
     * @param  int $page
545
     * @param  str $sortdirection sort direction, ASC or DESC
546
     * @return array|false
547
     */
548
    public function get_comments($page = '', $sortdirection = 'DESC') {
549
        global $DB, $CFG, $USER, $OUTPUT;
550
        if (!$this->can_view()) {
551
            return false;
552
        }
553
        if (!is_numeric($page)) {
554
            $page = 0;
555
        }
556
        $params = array();
557
        $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
558
        $start = $page * $perpage;
559
        $userfieldsapi = \core_user\fields::for_userpic();
560
        $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
561
 
562
        list($componentwhere, $component) = $this->get_component_select_sql('c');
563
        if ($component) {
564
            $params['component'] = $component;
565
        }
566
 
567
        $sortdirection = ($sortdirection === 'ASC') ? 'ASC' : 'DESC';
568
        $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
569
                  FROM {comments} c
570
                  JOIN {user} u ON u.id = c.userid
571
                 WHERE c.contextid = :contextid AND
572
                       c.commentarea = :commentarea AND
573
                       c.itemid = :itemid AND
574
                       $componentwhere
575
              ORDER BY c.timecreated $sortdirection, c.id $sortdirection";
576
        $params['contextid'] = $this->contextid;
577
        $params['commentarea'] = $this->commentarea;
578
        $params['itemid'] = $this->itemid;
579
 
580
        $comments = array();
581
        $formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
582
        $rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
583
        foreach ($rs as $u) {
584
            $c = new stdClass();
585
            $c->id          = $u->cid;
586
            $c->content     = $u->ccontent;
587
            $c->format      = $u->cformat;
588
            $c->timecreated = $u->ctimecreated;
589
            $c->strftimeformat = get_string('strftimerecentfull', 'langconfig');
590
            $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
591
            $c->profileurl = $url->out(false); // URL should not be escaped just yet.
592
            $c->fullname = fullname($u);
593
            $c->time = userdate($c->timecreated, $c->strftimeformat);
594
            $c->content = format_text($c->content, $c->format, $formatoptions);
595
            $c->avatar = $OUTPUT->user_picture($u, array('size' => 16));
596
            $c->userid = $u->id;
597
 
598
            if ($this->can_delete($c)) {
599
                $c->delete = true;
600
            }
601
            $comments[] = $c;
602
        }
603
        $rs->close();
604
 
605
        if (!empty($this->plugintype)) {
606
            // moodle module will filter comments
607
            $comments = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'display', array($comments, $this->comment_param), $comments);
608
        }
609
 
610
        return $comments;
611
    }
612
 
613
    /**
614
     * Returns an SQL fragment and param for selecting on component.
615
     * @param string $alias
616
     * @return array
617
     */
618
    protected function get_component_select_sql($alias = '') {
619
        $component = $this->get_component();
620
        if ($alias) {
621
            $alias = $alias.'.';
622
        }
623
        if (empty($component)) {
624
            $componentwhere = "{$alias}component IS NULL";
625
            $component = null;
626
        } else {
627
            $componentwhere = "({$alias}component IS NULL OR {$alias}component = :component)";
628
        }
629
        return array($componentwhere, $component);
630
    }
631
 
632
    /**
633
     * Returns the number of comments associated with the details of this object
634
     *
635
     * @global moodle_database $DB
636
     * @return int
637
     */
638
    public function count() {
639
        global $DB;
640
        if ($this->totalcommentcount === null) {
641
            list($where, $component) = $this->get_component_select_sql();
642
            $where .= ' AND itemid = :itemid AND commentarea = :commentarea AND contextid = :contextid';
643
            $params = array(
644
                'itemid' => $this->itemid,
645
                'commentarea' => $this->commentarea,
646
                'contextid' => $this->context->id,
647
            );
648
            if ($component) {
649
                $params['component'] = $component;
650
            }
651
 
652
            $this->totalcommentcount = $DB->count_records_select('comments', $where, $params);
653
        }
654
        return $this->totalcommentcount;
655
    }
656
 
657
    /**
658
     * Returns HTML to display a pagination bar
659
     *
660
     * @global stdClass $CFG
661
     * @global core_renderer $OUTPUT
662
     * @param int $page
663
     * @return string
664
     */
665
    public function get_pagination($page = 0) {
666
        global $CFG, $OUTPUT;
667
        $count = $this->count();
668
        $perpage = (!empty($CFG->commentsperpage))?$CFG->commentsperpage:15;
669
        $pages = (int)ceil($count/$perpage);
670
        if ($pages == 1 || $pages == 0) {
671
            return html_writer::tag('div', '', array('id' => 'comment-pagination-'.$this->cid, 'class' => 'comment-pagination'));
672
        }
673
        if (!empty(self::$nonjs)) {
674
            // used in non-js interface
675
            return $OUTPUT->paging_bar($count, $page, $perpage, $this->get_nojslink(), 'comment_page');
676
        } else {
677
            // return ajax paging bar
678
            $str = '';
679
            $str .= '<div class="comment-paging" id="comment-pagination-'.$this->cid.'">';
680
            for ($p=0; $p<$pages; $p++) {
681
                if ($p == $page) {
682
                    $class = 'curpage';
683
                } else {
684
                    $class = 'pageno';
685
                }
686
                $str .= '<a href="#" class="'.$class.'" id="comment-page-'.$this->cid.'-'.$p.'">'.($p+1).'</a> ';
687
            }
688
            $str .= '</div>';
689
        }
690
        return $str;
691
    }
692
 
693
    /**
694
     * Add a new comment
695
     *
696
     * @global moodle_database $DB
697
     * @param string $content
698
     * @param int $format
699
     * @return stdClass
700
     */
701
    public function add($content, $format = FORMAT_MOODLE) {
702
        global $CFG, $DB, $USER, $OUTPUT;
703
        if (!$this->can_post()) {
704
            throw new comment_exception('nopermissiontocomment');
705
        }
706
        $now = time();
707
        $newcmt = new stdClass;
708
        $newcmt->contextid    = $this->contextid;
709
        $newcmt->commentarea  = $this->commentarea;
710
        $newcmt->itemid       = $this->itemid;
711
        $newcmt->component    = !empty($this->component) ? $this->component : null;
712
        $newcmt->content      = $content;
713
        $newcmt->format       = $format;
714
        $newcmt->userid       = $USER->id;
715
        $newcmt->timecreated  = $now;
716
 
717
        // This callback allow module to modify the content of comment, such as filter or replacement
718
        plugin_callback($this->plugintype, $this->pluginname, 'comment', 'add', array(&$newcmt, $this->comment_param));
719
 
720
        $cmt_id = $DB->insert_record('comments', $newcmt);
721
        if (!empty($cmt_id)) {
722
            $newcmt->id = $cmt_id;
723
            $newcmt->strftimeformat = get_string('strftimerecentfull', 'langconfig');
724
            $newcmt->fullname = fullname($USER);
725
            $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
726
            $newcmt->profileurl = $url->out();
727
            $formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
728
            $newcmt->content = format_text($newcmt->content, $newcmt->format, $formatoptions);
729
            $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
730
 
731
            $commentlist = array($newcmt);
732
 
733
            if (!empty($this->plugintype)) {
734
                // Call the display callback to allow the plugin to format the newly added comment.
735
                $commentlist = plugin_callback($this->plugintype,
736
                                               $this->pluginname,
737
                                               'comment',
738
                                               'display',
739
                                               array($commentlist, $this->comment_param),
740
                                               $commentlist);
741
                $newcmt = $commentlist[0];
742
            }
743
            $newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat);
744
 
745
            // Trigger comment created event.
746
            if (core_component::is_core_subsystem($this->component)) {
747
                $eventclassname = '\\core\\event\\' . $this->component . '_comment_created';
748
            } else {
749
                $eventclassname = '\\' . $this->component . '\\event\comment_created';
750
            }
751
            if (class_exists($eventclassname)) {
752
                $event = $eventclassname::create(
753
                        array(
754
                            'context' => $this->context,
755
                            'objectid' => $newcmt->id,
756
                            'other' => array(
757
                                'itemid' => $this->itemid
758
                                )
759
                            ));
760
                $event->trigger();
761
            }
762
 
763
            return $newcmt;
764
        } else {
765
            throw new comment_exception('dbupdatefailed');
766
        }
767
    }
768
 
769
    /**
770
     * delete by context, commentarea and itemid
771
     * @param stdClass|array $param {
772
     *            contextid => int the context in which the comments exist [required]
773
     *            commentarea => string the comment area [optional]
774
     *            itemid => int comment itemid [optional]
775
     * }
776
     * @return boolean
777
     */
778
    public static function delete_comments($param) {
779
        global $DB;
780
        $param = (array)$param;
781
        if (empty($param['contextid'])) {
782
            return false;
783
        }
784
        $DB->delete_records('comments', $param);
785
        return true;
786
    }
787
 
788
    /**
789
     * Delete page_comments in whole course, used by course reset
790
     *
791
     * @param stdClass $context course context
792
     */
793
    public static function reset_course_page_comments($context) {
794
        global $DB;
795
        $contexts = array();
796
        $contexts[] = $context->id;
797
        $children = $context->get_child_contexts();
798
        foreach ($children as $c) {
799
            $contexts[] = $c->id;
800
        }
801
        list($ids, $params) = $DB->get_in_or_equal($contexts);
802
        $DB->delete_records_select('comments', "commentarea='page_comments' AND contextid $ids", $params);
803
    }
804
 
805
    /**
806
     * Delete a comment
807
     *
808
     * @param  int|stdClass $comment The id of a comment, or a comment record.
809
     * @return bool
810
     */
811
    public function delete($comment) {
812
        global $DB;
813
        if (is_object($comment)) {
814
            $commentid = $comment->id;
815
        } else {
816
            $commentid = $comment;
817
            $comment = $DB->get_record('comments', ['id' => $commentid]);
818
        }
819
 
820
        if (!$comment) {
821
            throw new comment_exception('dbupdatefailed');
822
        }
823
        if (!$this->can_delete($comment)) {
824
            throw new comment_exception('nopermissiontocomment');
825
        }
826
        $DB->delete_records('comments', array('id'=>$commentid));
827
        // Trigger comment delete event.
828
        if (core_component::is_core_subsystem($this->component)) {
829
            $eventclassname = '\\core\\event\\' . $this->component . '_comment_deleted';
830
        } else {
831
            $eventclassname = '\\' . $this->component . '\\event\comment_deleted';
832
        }
833
        if (class_exists($eventclassname)) {
834
            $event = $eventclassname::create(
835
                    array(
836
                        'context' => $this->context,
837
                        'objectid' => $commentid,
838
                        'other' => array(
839
                            'itemid' => $this->itemid
840
                            )
841
                        ));
842
            $event->add_record_snapshot('comments', $comment);
843
            $event->trigger();
844
        }
845
        return true;
846
    }
847
 
848
    /**
849
     * Print comments
850
     *
851
     * @param int $page
852
     * @param bool $return return comments list string or print it out
853
     * @param bool $nonjs print nonjs comments list or not?
854
     * @return string|void
855
     */
856
    public function print_comments($page = 0, $return = true, $nonjs = true) {
857
        global $DB, $CFG, $PAGE;
858
 
859
        if (!$this->can_view()) {
860
            return '';
861
        }
862
 
863
        if (!(self::$comment_itemid == $this->itemid &&
864
            self::$comment_context == $this->context->id &&
865
            self::$comment_area == $this->commentarea &&
866
            self::$comment_component == $this->component
867
        )) {
868
            $page = 0;
869
        }
870
        $comments = $this->get_comments($page);
871
 
872
        $html = '';
873
        if ($nonjs) {
874
            $html .= html_writer::tag('h3', get_string('comments'));
875
            $html .= html_writer::start_tag('ul', array('id' => 'comment-list-'.$this->cid, 'class' => 'comment-list'));
876
        }
877
        // Reverse the comments array to display them in the correct direction
878
        foreach (array_reverse($comments) as $cmt) {
879
            $html .= html_writer::tag('li', $this->print_comment($cmt, $nonjs), array('id' => 'comment-'.$cmt->id.'-'.$this->cid));
880
        }
881
        if ($nonjs) {
882
            $html .= html_writer::end_tag('ul');
883
            $html .= $this->get_pagination($page);
884
        }
885
        if ($nonjs && $this->can_post()) {
886
            // Form to add comments
887
            $html .= html_writer::start_tag('form', array('method' => 'post', 'action' => new moodle_url('/comment/comment_post.php')));
888
            // Comment parameters
889
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'contextid', 'value' => $this->contextid));
890
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action',    'value' => 'add'));
891
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'area',      'value' => $this->commentarea));
892
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'component', 'value' => $this->component));
893
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'itemid',    'value' => $this->itemid));
894
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'courseid',  'value' => $this->courseid));
895
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey',   'value' => sesskey()));
896
            $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'returnurl', 'value' => $PAGE->url));
897
            // Textarea for the actual comment
898
            $html .= html_writer::tag('textarea', '', array('name' => 'content', 'rows' => 2));
899
            // Submit button to add the comment
900
            $html .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('submit')));
901
            $html .= html_writer::end_tag('form');
902
        }
903
        if ($return) {
904
            return $html;
905
        } else {
906
            echo $html;
907
        }
908
    }
909
 
910
    /**
911
     * Returns an array containing comments in HTML format.
912
     *
913
     * @global core_renderer $OUTPUT
914
     * @param stdClass $cmt {
915
     *          id => int comment id
916
     *          content => string comment content
917
     *          format  => int comment text format
918
     *          timecreated => int comment's timecreated
919
     *          profileurl  => string link to user profile
920
     *          fullname    => comment author's full name
921
     *          avatar      => string user's avatar
922
     *          delete      => boolean does user have permission to delete comment?
923
     * }
924
     * @param bool $nonjs
925
     * @return array
926
     */
927
    public function print_comment($cmt, $nonjs = true) {
928
        global $OUTPUT;
929
        $patterns = array();
930
        $replacements = array();
931
 
932
        if (!empty($cmt->delete) && empty($nonjs)) {
933
            $strdelete = get_string('deletecommentbyon', 'moodle', (object)['user' => $cmt->fullname, 'time' => $cmt->time]);
934
            $deletelink  = html_writer::start_tag('div', array('class'=>'comment-delete'));
935
            $deletelink .= html_writer::start_tag('a', array('href' => '#', 'id' => 'comment-delete-'.$this->cid.'-'.$cmt->id,
936
                'class' => 'icon-no-margin', 'title' => $strdelete));
937
 
938
            $deletelink .= $OUTPUT->pix_icon('t/delete', $strdelete);
939
            $deletelink .= html_writer::end_tag('a');
940
            $deletelink .= html_writer::end_tag('div');
941
            $cmt->content = $deletelink . $cmt->content;
942
        }
943
        $patterns[] = '___picture___';
944
        $patterns[] = '___name___';
945
        $patterns[] = '___content___';
946
        $patterns[] = '___time___';
947
        $replacements[] = $cmt->avatar;
948
        $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
949
        $replacements[] = $cmt->content;
950
        $replacements[] = $cmt->time;
951
 
952
        // use html template to format a single comment.
953
        return str_replace($patterns, $replacements, $this->template);
954
    }
955
 
956
    /**
957
     * Revoke validate callbacks
958
     *
959
     * @param array $params addtionall parameters need to add to callbacks
960
     */
961
    protected function validate($params=array()) {
962
        foreach ($params as $key=>$value) {
963
            $this->comment_param->$key = $value;
964
        }
965
        $validation = plugin_callback($this->plugintype, $this->pluginname, 'comment', 'validate', array($this->comment_param), false);
966
        if (!$validation) {
967
            throw new comment_exception('invalidcommentparam');
968
        }
969
    }
970
 
971
    /**
972
     * Returns true if the user is able to view comments
973
     * @return bool
974
     */
975
    public function can_view() {
976
        $this->validate();
977
        return !empty($this->viewcap);
978
    }
979
 
980
    /**
981
     * Returns true if the user can add comments against this comment description
982
     * @return bool
983
     */
984
    public function can_post() {
985
        $this->validate();
986
        return isloggedin() && !empty($this->postcap);
987
    }
988
 
989
    /**
990
     * Returns true if the user can delete this comment.
991
     *
992
     * The user can delete comments if it is one they posted and they can still make posts,
993
     * or they have the capability to delete comments.
994
     *
995
     * A database call is avoided if a comment record is passed.
996
     *
997
     * @param int|stdClass $comment The id of a comment, or a comment record.
998
     * @return bool
999
     */
1000
    public function can_delete($comment) {
1001
        global $USER, $DB;
1002
        if (is_object($comment)) {
1003
            $commentid = $comment->id;
1004
        } else {
1005
            $commentid = $comment;
1006
        }
1007
 
1008
        $this->validate(array('commentid'=>$commentid));
1009
 
1010
        if (!is_object($comment)) {
1011
            // Get the comment record from the database.
1012
            $comment = $DB->get_record('comments', array('id' => $commentid), 'id, userid', MUST_EXIST);
1013
        }
1014
 
1015
        $hascapability = has_capability('moodle/comment:delete', $this->context);
1016
        $owncomment = $USER->id == $comment->userid;
1017
 
1018
        return ($hascapability || ($owncomment && $this->can_post()));
1019
    }
1020
 
1021
    /**
1022
     * Returns the component associated with the comment.
1023
     *
1024
     * @return string
1025
     */
1026
    public function get_component() {
1027
        return $this->component;
1028
    }
1029
 
1030
    /**
1031
     * Do not call! I am a deprecated method because of the typo in my name.
1032
     * @deprecated since 2.9
1033
     * @see comment::get_component()
1034
     * @return string
1035
     */
1036
    public function get_compontent() {
1037
        return $this->get_component();
1038
    }
1039
 
1040
    /**
1041
     * Returns the context associated with the comment
1042
     * @return stdClass
1043
     */
1044
    public function get_context() {
1045
        return $this->context;
1046
    }
1047
 
1048
    /**
1049
     * Returns the course id associated with the comment
1050
     * @return int
1051
     */
1052
    public function get_courseid() {
1053
        return $this->courseid;
1054
    }
1055
 
1056
    /**
1057
     * Returns the course module associated with the comment
1058
     *
1059
     * @return stdClass
1060
     */
1061
    public function get_cm() {
1062
        return $this->cm;
1063
    }
1064
 
1065
    /**
1066
     * Returns the item id associated with the comment
1067
     *
1068
     * @return int
1069
     */
1070
    public function get_itemid() {
1071
        return $this->itemid;
1072
    }
1073
 
1074
    /**
1075
     * Returns the comment area associated with the commentarea
1076
     *
1077
     * @return string
1078
     */
1079
    public function get_commentarea() {
1080
        return $this->commentarea;
1081
    }
1082
 
1083
    /**
1084
     * Make the comments textarea fullwidth.
1085
     *
1086
     * @since 2.8.1 + 2.7.4
1087
     * @param bool $fullwidth
1088
     */
1089
    public function set_fullwidth($fullwidth = true) {
1090
        $this->fullwidth = (bool)$fullwidth;
1091
    }
1092
 
1093
    /**
1094
     * Return the template.
1095
     *
1096
     * @since 3.1
1097
     * @return string
1098
     */
1099
    public function get_template() {
1100
        return $this->template;
1101
    }
1102
 
1103
    /**
1104
     * Return the cid.
1105
     *
1106
     * @since 3.1
1107
     * @return string
1108
     */
1109
    public function get_cid() {
1110
        return $this->cid;
1111
    }
1112
 
1113
    /**
1114
     * Return the link text.
1115
     *
1116
     * @since 3.1
1117
     * @return string
1118
     */
1119
    public function get_linktext() {
1120
        return $this->linktext;
1121
    }
1122
 
1123
    /**
1124
     * Return no toggle.
1125
     *
1126
     * @since 3.1
1127
     * @return bool
1128
     */
1129
    public function get_notoggle() {
1130
        return $this->notoggle;
1131
    }
1132
 
1133
    /**
1134
     * Return display total count.
1135
     *
1136
     * @since 3.1
1137
     * @return bool
1138
     */
1139
    public function get_displaytotalcount() {
1140
        return $this->displaytotalcount;
1141
    }
1142
 
1143
    /**
1144
     * Return display cancel.
1145
     *
1146
     * @since 3.1
1147
     * @return bool
1148
     */
1149
    public function get_displaycancel() {
1150
        return $this->displaycancel;
1151
    }
1152
 
1153
    /**
1154
     * Return fullwidth.
1155
     *
1156
     * @since 3.1
1157
     * @return bool
1158
     */
1159
    public function get_fullwidth() {
1160
        return $this->fullwidth;
1161
    }
1162
 
1163
    /**
1164
     * Return autostart.
1165
     *
1166
     * @since 3.1
1167
     * @return bool
1168
     */
1169
    public function get_autostart() {
1170
        return $this->autostart;
1171
    }
1172
 
1173
}
1174
 
1175
/**
1176
 * Comment exception class
1177
 *
1178
 * @package   core
1179
 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
1180
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1181
 */
1182
class comment_exception extends moodle_exception {
1183
}