Proyectos de Subversion Moodle

Rev

| 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
 * A class representing a single rating and containing some static methods for manipulating ratings
19
 *
20
 * @package    core_rating
21
 * @subpackage rating
22
 * @copyright  2010 Andrew Davis
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
define('RATING_UNSET_RATING', -999);
27
 
28
define ('RATING_AGGREGATE_NONE', 0); // No ratings.
29
define ('RATING_AGGREGATE_AVERAGE', 1);
30
define ('RATING_AGGREGATE_COUNT', 2);
31
define ('RATING_AGGREGATE_MAXIMUM', 3);
32
define ('RATING_AGGREGATE_MINIMUM', 4);
33
define ('RATING_AGGREGATE_SUM', 5);
34
 
35
define ('RATING_DEFAULT_SCALE', 5);
36
 
37
/**
38
 * The rating class represents a single rating by a single user
39
 *
40
 * @package   core_rating
41
 * @category  rating
42
 * @copyright 2010 Andrew Davis
43
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44
 * @since     Moodle 2.0
45
 */
46
class rating implements renderable {
47
 
48
    /**
49
     * @var context The context in which this rating exists
50
     */
51
    public $context;
52
 
53
    /**
54
     * @var string The component using ratings. For example "mod_forum"
55
     */
56
    public $component;
57
 
58
    /**
59
     * @var string The rating area to associate this rating with
60
     *             This allows a plugin to rate more than one thing by specifying different rating areas
61
     */
62
    public $ratingarea = null;
63
 
64
    /**
65
     * @var int The id of the item (forum post, glossary item etc) being rated
66
     */
67
    public $itemid;
68
 
69
    /**
70
     * @var int The id scale (1-5, 0-100) that was in use when the rating was submitted
71
     */
72
    public $scaleid;
73
 
74
    /**
75
     * @var int The id of the user who submitted the rating
76
     */
77
    public $userid;
78
 
79
    /**
80
     * @var stdclass settings for this rating. Necessary to render the rating.
81
     */
82
    public $settings;
83
 
84
    /**
85
     * @var int The Id of this rating within the rating table. This is only set if the rating already exists
86
     */
87
    public $id = null;
88
 
89
    /**
90
     * @var int The aggregate of the combined ratings for the associated item. This is only set if the rating already exists
91
     */
92
    public $aggregate = null;
93
 
94
    /**
95
     * @var int The total number of ratings for the associated item. This is only set if the rating already exists
96
     */
97
    public $count = 0;
98
 
99
    /**
100
     * @var int The rating the associated user gave the associated item. This is only set if the rating already exists
101
     */
102
    public $rating = null;
103
 
104
    /**
105
     * @var int The time the associated item was created
106
     */
107
    public $itemtimecreated = null;
108
 
109
    /**
110
     * @var int The id of the user who submitted the rating
111
     */
112
    public $itemuserid = null;
113
 
114
    /**
115
     * Constructor.
116
     *
117
     * @param stdClass $options {
118
     *            context => context context to use for the rating [required]
119
     *            component => component using ratings ie mod_forum [required]
120
     *            ratingarea => ratingarea to associate this rating with [required]
121
     *            itemid  => int the id of the associated item (forum post, glossary item etc) [required]
122
     *            scaleid => int The scale in use when the rating was submitted [required]
123
     *            userid  => int The id of the user who submitted the rating [required]
124
     *            settings => Settings for the rating object [optional]
125
     *            id => The id of this rating (if the rating is from the db) [optional]
126
     *            aggregate => The aggregate for the rating [optional]
127
     *            count => The number of ratings [optional]
128
     *            rating => The rating given by the user [optional]
129
     * }
130
     */
131
    public function __construct($options) {
132
        $this->context = $options->context;
133
        $this->component = $options->component;
134
        $this->ratingarea = $options->ratingarea;
135
        $this->itemid = $options->itemid;
136
        $this->scaleid = $options->scaleid;
137
        $this->userid = $options->userid;
138
 
139
        if (isset($options->settings)) {
140
            $this->settings = $options->settings;
141
        }
142
        if (isset($options->id)) {
143
            $this->id = $options->id;
144
        }
145
        if (isset($options->aggregate)) {
146
            $this->aggregate = $options->aggregate;
147
        }
148
        if (isset($options->count)) {
149
            $this->count = $options->count;
150
        }
151
        if (isset($options->rating)) {
152
            $this->rating = $options->rating;
153
        }
154
    }
155
 
156
    /**
157
     * Update this rating in the database
158
     *
159
     * @param int $rating the integer value of this rating
160
     */
161
    public function update_rating($rating) {
162
        global $DB;
163
 
164
        $time = time();
165
 
166
        $data = new stdClass;
167
        $data->rating       = $rating;
168
        $data->timemodified = $time;
169
 
170
        $item = new stdclass();
171
        $item->id = $this->itemid;
172
        $items = array($item);
173
 
174
        $ratingoptions = new stdClass;
175
        $ratingoptions->context = $this->context;
176
        $ratingoptions->component = $this->component;
177
        $ratingoptions->ratingarea = $this->ratingarea;
178
        $ratingoptions->items = $items;
179
        $ratingoptions->aggregate = RATING_AGGREGATE_AVERAGE; // We dont actually care what aggregation method is applied.
180
        $ratingoptions->scaleid = $this->scaleid;
181
        $ratingoptions->userid = $this->userid;
182
 
183
        $rm = new rating_manager();
184
        $items = $rm->get_ratings($ratingoptions);
185
        $firstitem = $items[0]->rating;
186
 
187
        if (empty($firstitem->id)) {
188
            // Insert a new rating.
189
            $data->contextid    = $this->context->id;
190
            $data->component    = $this->component;
191
            $data->ratingarea   = $this->ratingarea;
192
            $data->rating       = $rating;
193
            $data->scaleid      = $this->scaleid;
194
            $data->userid       = $this->userid;
195
            $data->itemid       = $this->itemid;
196
            $data->timecreated  = $time;
197
            $data->timemodified = $time;
198
            $DB->insert_record('rating', $data);
199
        } else {
200
            // Update the rating.
201
            $data->id           = $firstitem->id;
202
            $DB->update_record('rating', $data);
203
        }
204
    }
205
 
206
    /**
207
     * Retreive the integer value of this rating
208
     *
209
     * @return int the integer value of this rating object
210
     */
211
    public function get_rating() {
212
        return $this->rating;
213
    }
214
 
215
    /**
216
     * Returns this ratings aggregate value as a string.
217
     *
218
     * @return string ratings aggregate value
219
     */
220
    public function get_aggregate_string() {
221
 
222
        $aggregate = $this->aggregate;
223
        $method = $this->settings->aggregationmethod;
224
 
225
        // Only display aggregate if aggregation method isn't COUNT.
226
        $aggregatestr = '';
227
        if (is_numeric($aggregate) && $method != RATING_AGGREGATE_COUNT) {
228
            if ($method != RATING_AGGREGATE_SUM && !$this->settings->scale->isnumeric) {
229
 
230
                // Round aggregate as we're using it as an index.
231
                $aggregatestr .= $this->settings->scale->scaleitems[round($aggregate)];
232
            } else { // Aggregation is SUM or the scale is numeric.
233
                $aggregatestr .= round($aggregate, 1);
234
            }
235
        }
236
 
237
        return $aggregatestr;
238
    }
239
 
240
    /**
241
     * Returns true if the user is able to rate this rating object
242
     *
243
     * @param int $userid Current user assumed if left empty
244
     * @return bool true if the user is able to rate this rating object
245
     */
246
    public function user_can_rate($userid = null) {
247
        if (empty($userid)) {
248
            global $USER;
249
            $userid = $USER->id;
250
        }
251
        // You can't rate your item.
252
        if ($this->itemuserid == $userid) {
253
            return false;
254
        }
255
        // You can't rate if you don't have the system cap.
256
        if (!$this->settings->permissions->rate) {
257
            return false;
258
        }
259
        // You can't rate if you don't have the plugin cap.
260
        if (!$this->settings->pluginpermissions->rate) {
261
            return false;
262
        }
263
 
264
        // You can't rate if the item was outside of the assessment times.
265
        $timestart = $this->settings->assesstimestart;
266
        $timefinish = $this->settings->assesstimefinish;
267
        $timecreated = $this->itemtimecreated;
268
        if (!empty($timestart) && !empty($timefinish) && ($timecreated < $timestart || $timecreated > $timefinish)) {
269
            return false;
270
        }
271
        return true;
272
    }
273
 
274
    /**
275
     * Returns true if the user is able to view the aggregate for this rating object.
276
     *
277
     * @param int|null $userid If left empty the current user is assumed.
278
     * @return bool true if the user is able to view the aggregate for this rating object
279
     */
280
    public function user_can_view_aggregate($userid = null) {
281
        if (empty($userid)) {
282
            global $USER;
283
            $userid = $USER->id;
284
        }
285
 
286
        // If the item doesnt belong to anyone or its another user's items and they can see the aggregate on items they don't own.
287
        // Note that viewany doesnt mean you can see the aggregate or ratings of your own items.
288
        if ((empty($this->itemuserid) or $this->itemuserid != $userid)
289
            && $this->settings->permissions->viewany
290
            && $this->settings->pluginpermissions->viewany ) {
291
 
292
            return true;
293
        }
294
 
295
        // If its the current user's item and they have permission to view the aggregate on their own items.
296
        if ($this->itemuserid == $userid
297
            && $this->settings->permissions->view
298
            && $this->settings->pluginpermissions->view) {
299
 
300
            return true;
301
        }
302
 
303
        return false;
304
    }
305
 
306
    /**
307
     * Returns a URL to view all of the ratings for the item this rating is for.
308
     *
309
     * If this is a rating of a post then this URL will take the user to a page that shows all of the ratings for the post
310
     * (this one included).
311
     *
312
     * @param bool $popup whether of not the URL should be loaded in a popup
313
     * @return moodle_url URL to view all of the ratings for the item this rating is for.
314
     */
315
    public function get_view_ratings_url($popup = false) {
316
        $attributes = array(
317
            'contextid'  => $this->context->id,
318
            'component'  => $this->component,
319
            'ratingarea' => $this->ratingarea,
320
            'itemid'     => $this->itemid,
321
            'scaleid'    => $this->settings->scale->id
322
        );
323
        if ($popup) {
324
            $attributes['popup'] = 1;
325
        }
326
        return new moodle_url('/rating/index.php', $attributes);
327
    }
328
 
329
    /**
330
     * Returns a URL that can be used to rate the associated item.
331
     *
332
     * @param int|null          $rating    The rating to give the item, if null then no rating param is added.
333
     * @param moodle_url|string $returnurl The URL to return to.
334
     * @return moodle_url can be used to rate the associated item.
335
     */
336
    public function get_rate_url($rating = null, $returnurl = null) {
337
        if (empty($returnurl)) {
338
            if (!empty($this->settings->returnurl)) {
339
                $returnurl = $this->settings->returnurl;
340
            } else {
341
                global $PAGE;
342
                $returnurl = $PAGE->url;
343
            }
344
        }
345
        $args = array(
346
            'contextid'   => $this->context->id,
347
            'component'   => $this->component,
348
            'ratingarea'  => $this->ratingarea,
349
            'itemid'      => $this->itemid,
350
            'scaleid'     => $this->settings->scale->id,
351
            'returnurl'   => $returnurl,
352
            'rateduserid' => $this->itemuserid,
353
            'aggregation' => $this->settings->aggregationmethod,
354
            'sesskey'     => sesskey()
355
        );
356
        if (!empty($rating)) {
357
            $args['rating'] = $rating;
358
        }
359
        $url = new moodle_url('/rating/rate.php', $args);
360
        return $url;
361
    }
362
 
363
} // End rating class definition.
364
 
365
/**
366
 * The rating_manager class provides the ability to retrieve sets of ratings from the database
367
 *
368
 * @package   core_rating
369
 * @category  rating
370
 * @copyright 2010 Andrew Davis
371
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
372
 * @since     Moodle 2.0
373
 */
374
class rating_manager {
375
 
376
    /**
377
     * @var array An array of calculated scale options to save us generating them for each request.
378
     */
379
    protected $scales = array();
380
 
381
    /**
382
     * Delete one or more ratings. Specify either a rating id, an item id or just the context id.
383
     *
384
     * @global moodle_database $DB
385
     * @param stdClass $options {
386
     *            contextid => int the context in which the ratings exist [required]
387
     *            ratingid => int the id of an individual rating to delete [optional]
388
     *            userid => int delete the ratings submitted by this user. May be used in conjuction with itemid [optional]
389
     *            itemid => int delete all ratings attached to this item [optional]
390
     *            component => string The component to delete ratings from [optional]
391
     *            ratingarea => string The ratingarea to delete ratings from [optional]
392
     * }
393
     */
394
    public function delete_ratings($options) {
395
        global $DB;
396
 
397
        if (empty($options->contextid)) {
398
            throw new coding_exception('The context option is a required option when deleting ratings.');
399
        }
400
 
401
        $conditions = array('contextid' => $options->contextid);
402
        $possibleconditions = array(
403
            'ratingid'   => 'id',
404
            'userid'     => 'userid',
405
            'itemid'     => 'itemid',
406
            'component'  => 'component',
407
            'ratingarea' => 'ratingarea'
408
        );
409
        foreach ($possibleconditions as $option => $field) {
410
            if (isset($options->{$option})) {
411
                $conditions[$field] = $options->{$option};
412
            }
413
        }
414
        $DB->delete_records('rating', $conditions);
415
    }
416
 
417
    /**
418
     * Returns an array of ratings for a given item (forum post, glossary entry etc).
419
     *
420
     * This returns all users ratings for a single item
421
     *
422
     * @param stdClass $options {
423
     *            context => context the context in which the ratings exists [required]
424
     *            component => component using ratings ie mod_forum [required]
425
     *            ratingarea => ratingarea to associate this rating with [required]
426
     *            itemid  =>  int the id of the associated item (forum post, glossary item etc) [required]
427
     *            sort    => string SQL sort by clause [optional]
428
     * }
429
     * @return array an array of ratings
430
     */
431
    public function get_all_ratings_for_item($options) {
432
        global $DB;
433
 
434
        if (!isset($options->context)) {
435
            throw new coding_exception('The context option is a required option when getting ratings for an item.');
436
        }
437
        if (!isset($options->itemid)) {
438
            throw new coding_exception('The itemid option is a required option when getting ratings for an item.');
439
        }
440
        if (!isset($options->component)) {
441
            throw new coding_exception('The component option is now a required option when getting ratings for an item.');
442
        }
443
        if (!isset($options->ratingarea)) {
444
            throw new coding_exception('The ratingarea option is now a required option when getting ratings for an item.');
445
        }
446
 
447
        $sortclause = '';
448
        if (!empty($options->sort)) {
449
            $sortclause = "ORDER BY $options->sort";
450
        }
451
 
452
        $params = array(
453
            'contextid'  => $options->context->id,
454
            'itemid'     => $options->itemid,
455
            'component'  => $options->component,
456
            'ratingarea' => $options->ratingarea,
457
        );
458
        $userfieldsapi = \core_user\fields::for_userpic();
459
        $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
460
        $sql = "SELECT r.id, r.rating, r.itemid, r.userid, r.timemodified, r.component, r.ratingarea, $userfields
461
                  FROM {rating} r
462
             LEFT JOIN {user} u ON r.userid = u.id
463
                 WHERE r.contextid = :contextid AND
464
                       r.itemid  = :itemid AND
465
                       r.component = :component AND
466
                       r.ratingarea = :ratingarea
467
                       {$sortclause}";
468
 
469
        return $DB->get_records_sql($sql, $params);
470
    }
471
 
472
    /**
473
     * Adds rating objects to an array of items (forum posts, glossary entries etc). Rating objects are available at $item->rating
474
     *
475
     * @param stdClass $options {
476
     *      context          => context the context in which the ratings exists [required]
477
     *      component        => the component name ie mod_forum [required]
478
     *      ratingarea       => the ratingarea we are interested in [required]
479
     *      items            => array items like forum posts or glossary items. Each item needs an 'id' ie $items[0]->id [required]
480
     *      aggregate        => int aggregation method to apply. RATING_AGGREGATE_AVERAGE, RATING_AGGREGATE_MAXIMUM etc [required]
481
     *      scaleid          => int the scale from which the user can select a rating [required]
482
     *      userid           => int the id of the current user [optional]
483
     *      returnurl        => string the url to return the user to after submitting a rating. Null for ajax requests [optional]
484
     *      assesstimestart  => int only allow rating of items created after this timestamp [optional]
485
     *      assesstimefinish => int only allow rating of items created before this timestamp [optional]
486
     * @return array the array of items with their ratings attached at $items[0]->rating
487
     */
488
    public function get_ratings($options) {
489
        global $DB, $USER;
490
 
491
        if (!isset($options->context)) {
492
            throw new coding_exception('The context option is a required option when getting ratings.');
493
        }
494
 
495
        if (!isset($options->component)) {
496
            throw new coding_exception('The component option is a required option when getting ratings.');
497
        }
498
 
499
        if (!isset($options->ratingarea)) {
500
            throw new coding_exception('The ratingarea option is a required option when getting ratings.');
501
        }
502
 
503
        if (!isset($options->scaleid)) {
504
            throw new coding_exception('The scaleid option is a required option when getting ratings.');
505
        }
506
 
507
        if (!isset($options->items)) {
508
            throw new coding_exception('The items option is a required option when getting ratings.');
509
        } else if (empty($options->items)) {
510
            return array();
511
        }
512
 
513
        if (!isset($options->aggregate)) {
514
            throw new coding_exception('The aggregate option is a required option when getting ratings.');
515
        } else if ($options->aggregate == RATING_AGGREGATE_NONE) {
516
            // Ratings are not enabled.
517
            return $options->items;
518
        }
519
 
520
        // Ensure average aggregation returns float.
521
        $aggregatestr = $this->get_aggregation_method($options->aggregate);
522
        $aggregatefield = 'r.rating';
523
        if ($aggregatestr === 'AVG') {
524
            $aggregatefield = "1.0 * {$aggregatefield}";
525
        }
526
 
527
        // Default the userid to the current user if it is not set.
528
        if (empty($options->userid)) {
529
            $userid = $USER->id;
530
        } else {
531
            $userid = $options->userid;
532
        }
533
 
534
        // Get the item table name, the item id field, and the item user field for the given rating item
535
        // from the related component.
536
        list($type, $name) = core_component::normalize_component($options->component);
537
        $default = array(null, 'id', 'userid');
538
        list($itemtablename, $itemidcol, $itemuseridcol) = plugin_callback($type,
539
                                                                           $name,
540
                                                                           'rating',
541
                                                                           'get_item_fields',
542
                                                                           array($options),
543
                                                                           $default);
544
 
545
        // Create an array of item IDs.
546
        $itemids = array();
547
        foreach ($options->items as $item) {
548
            $itemids[] = $item->{$itemidcol};
549
        }
550
 
551
        // Get the items from the database.
552
        list($itemidtest, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
553
        $params['contextid'] = $options->context->id;
554
        $params['userid']    = $userid;
555
        $params['component']    = $options->component;
556
        $params['ratingarea'] = $options->ratingarea;
557
 
558
        $sql = "SELECT r.id, r.itemid, r.userid, r.scaleid, r.rating AS usersrating
559
                  FROM {rating} r
560
                 WHERE r.userid = :userid AND
561
                       r.contextid = :contextid AND
562
                       r.itemid {$itemidtest} AND
563
                       r.component = :component AND
564
                       r.ratingarea = :ratingarea
565
              ORDER BY r.itemid";
566
        $userratings = $DB->get_records_sql($sql, $params);
567
 
568
        $sql = "SELECT r.itemid, {$aggregatestr}({$aggregatefield}) AS aggrrating, COUNT(r.rating) AS numratings
569
                  FROM {rating} r
570
                 WHERE r.contextid = :contextid AND
571
                       r.itemid {$itemidtest} AND
572
                       r.component = :component AND
573
                       r.ratingarea = :ratingarea
574
              GROUP BY r.itemid, r.component, r.ratingarea, r.contextid
575
              ORDER BY r.itemid";
576
        $aggregateratings = $DB->get_records_sql($sql, $params);
577
 
578
        $ratingoptions = new stdClass;
579
        $ratingoptions->context = $options->context;
580
        $ratingoptions->component = $options->component;
581
        $ratingoptions->ratingarea = $options->ratingarea;
582
        $ratingoptions->settings = $this->generate_rating_settings_object($options);
583
        foreach ($options->items as $item) {
584
            $founduserrating = false;
585
            foreach ($userratings as $userrating) {
586
                // Look for an existing rating from this user of this item.
587
                if ($item->{$itemidcol} == $userrating->itemid) {
588
                    // Note: rec->scaleid = the id of scale at the time the rating was submitted.
589
                    // It may be different from the current scale id.
590
                    $ratingoptions->scaleid = $userrating->scaleid;
591
                    $ratingoptions->userid = $userrating->userid;
592
                    $ratingoptions->id = $userrating->id;
593
                    $ratingoptions->rating = min($userrating->usersrating, $ratingoptions->settings->scale->max);
594
 
595
                    $founduserrating = true;
596
                    break;
597
                }
598
            }
599
            if (!$founduserrating) {
600
                $ratingoptions->scaleid = null;
601
                $ratingoptions->userid = null;
602
                $ratingoptions->id = null;
603
                $ratingoptions->rating = null;
604
            }
605
 
606
            if (array_key_exists($item->{$itemidcol}, $aggregateratings)) {
607
                $rec = $aggregateratings[$item->{$itemidcol}];
608
                $ratingoptions->itemid = $item->{$itemidcol};
609
                $ratingoptions->aggregate = min($rec->aggrrating, $ratingoptions->settings->scale->max);
610
                $ratingoptions->count = $rec->numratings;
611
            } else {
612
                $ratingoptions->itemid = $item->{$itemidcol};
613
                $ratingoptions->aggregate = null;
614
                $ratingoptions->count = 0;
615
            }
616
 
617
            $rating = new rating($ratingoptions);
618
            $rating->itemtimecreated = $this->get_item_time_created($item);
619
            if (!empty($item->{$itemuseridcol})) {
620
                $rating->itemuserid = $item->{$itemuseridcol};
621
            }
622
            $item->rating = $rating;
623
        }
624
 
625
        return $options->items;
626
    }
627
 
628
    /**
629
     * Generates a rating settings object based upon the options it is provided.
630
     *
631
     * @param stdClass $options {
632
     *      context           => context the context in which the ratings exists [required]
633
     *      component         => string The component the items belong to [required]
634
     *      ratingarea        => string The ratingarea the items belong to [required]
635
     *      aggregate         => int Aggregation method to apply. RATING_AGGREGATE_AVERAGE, RATING_AGGREGATE_MAXIMUM etc [required]
636
     *      scaleid           => int the scale from which the user can select a rating [required]
637
     *      returnurl         => string the url to return the user to after submitting a rating. Null for ajax requests [optional]
638
     *      assesstimestart   => int only allow rating of items created after this timestamp [optional]
639
     *      assesstimefinish  => int only allow rating of items created before this timestamp [optional]
640
     *      plugintype        => string plugin type ie 'mod' Used to find the permissions callback [optional]
641
     *      pluginname        => string plugin name ie 'forum' Used to find the permissions callback [optional]
642
     * }
643
     * @return stdClass rating settings object
644
     */
645
    protected function generate_rating_settings_object($options) {
646
 
647
        if (!isset($options->context)) {
648
            throw new coding_exception('The context option is a required option when generating a rating settings object.');
649
        }
650
        if (!isset($options->component)) {
651
            throw new coding_exception('The component option is now a required option when generating a rating settings object.');
652
        }
653
        if (!isset($options->ratingarea)) {
654
            throw new coding_exception('The ratingarea option is now a required option when generating a rating settings object.');
655
        }
656
        if (!isset($options->aggregate)) {
657
            throw new coding_exception('The aggregate option is now a required option when generating a rating settings object.');
658
        }
659
        if (!isset($options->scaleid)) {
660
            throw new coding_exception('The scaleid option is now a required option when generating a rating settings object.');
661
        }
662
 
663
        // Settings that are common to all ratings objects in this context.
664
        $settings = new stdClass;
665
        $settings->scale             = $this->generate_rating_scale_object($options->scaleid); // The scale to use now.
666
        $settings->aggregationmethod = $options->aggregate;
667
        $settings->assesstimestart   = null;
668
        $settings->assesstimefinish  = null;
669
 
670
        // Collect options into the settings object.
671
        if (!empty($options->assesstimestart)) {
672
            $settings->assesstimestart = $options->assesstimestart;
673
        }
674
        if (!empty($options->assesstimefinish)) {
675
            $settings->assesstimefinish = $options->assesstimefinish;
676
        }
677
        if (!empty($options->returnurl)) {
678
            $settings->returnurl = $options->returnurl;
679
        }
680
 
681
        // Check site capabilities.
682
        $settings->permissions = new stdClass;
683
        // Can view the aggregate of ratings of their own items.
684
        $settings->permissions->view    = has_capability('moodle/rating:view', $options->context);
685
        // Can view the aggregate of ratings of other people's items.
686
        $settings->permissions->viewany = has_capability('moodle/rating:viewany', $options->context);
687
        // Can view individual ratings.
688
        $settings->permissions->viewall = has_capability('moodle/rating:viewall', $options->context);
689
        // Can submit ratings.
690
        $settings->permissions->rate    = has_capability('moodle/rating:rate', $options->context);
691
 
692
        // Check module capabilities
693
        // This is mostly for backwards compatability with old modules that previously implemented their own ratings.
694
        $pluginpermissionsarray = $this->get_plugin_permissions_array($options->context->id,
695
                                                                      $options->component,
696
                                                                      $options->ratingarea);
697
        $settings->pluginpermissions = new stdClass;
698
        $settings->pluginpermissions->view    = $pluginpermissionsarray['view'];
699
        $settings->pluginpermissions->viewany = $pluginpermissionsarray['viewany'];
700
        $settings->pluginpermissions->viewall = $pluginpermissionsarray['viewall'];
701
        $settings->pluginpermissions->rate    = $pluginpermissionsarray['rate'];
702
 
703
        return $settings;
704
    }
705
 
706
    /**
707
     * Generates a scale object that can be returned
708
     *
709
     * @global moodle_database $DB moodle database object
710
     * @param int $scaleid scale-type identifier
711
     * @return stdClass scale for ratings
712
     */
713
    protected function generate_rating_scale_object($scaleid) {
714
        global $DB;
715
        if (!array_key_exists('s'.$scaleid, $this->scales)) {
716
            $scale = new stdClass;
717
            $scale->id = $scaleid;
718
            $scale->name = null;
719
            $scale->courseid = null;
720
            $scale->scaleitems = array();
721
            $scale->isnumeric = true;
722
            $scale->max = $scaleid;
723
 
724
            if ($scaleid < 0) {
725
                // It is a proper scale (not numeric).
726
                $scalerecord = $DB->get_record('scale', array('id' => abs($scaleid)));
727
                if ($scalerecord) {
728
                    // We need to generate an array with string keys starting at 1.
729
                    $scalearray = explode(',', $scalerecord->scale);
730
                    $c = count($scalearray);
731
                    for ($i = 0; $i < $c; $i++) {
732
                        // Treat index as a string to allow sorting without changing the value.
733
                        $scale->scaleitems[(string)($i + 1)] = $scalearray[$i];
734
                    }
735
                    krsort($scale->scaleitems); // Have the highest grade scale item appear first.
736
                    $scale->isnumeric = false;
737
                    $scale->name = $scalerecord->name;
738
                    $scale->courseid = $scalerecord->courseid;
739
                    $scale->max = count($scale->scaleitems);
740
                }
741
            } else {
742
                // Generate an array of values for numeric scales.
743
                for ($i = 0; $i <= (int)$scaleid; $i++) {
744
                    $scale->scaleitems[(string)$i] = $i;
745
                }
746
            }
747
            $this->scales['s'.$scaleid] = $scale;
748
        }
749
        return $this->scales['s'.$scaleid];
750
    }
751
 
752
    /**
753
     * Gets the time the given item was created
754
     *
755
     * TODO: MDL-31511 - Find a better solution for this, its not ideal to test for fields really we should be
756
     * asking the component the item belongs to what field to look for or even the value we
757
     * are looking for.
758
     *
759
     * @param stdClass $item
760
     * @return int|null return null if the created time is unavailable, otherwise return a timestamp
761
     */
762
    protected function get_item_time_created($item) {
763
        if (!empty($item->created)) {
764
            return $item->created; // The forum_posts table has created instead of timecreated.
765
        } else if (!empty($item->timecreated)) {
766
            return $item->timecreated;
767
        } else {
768
            return null;
769
        }
770
    }
771
 
772
    /**
773
     * Returns an array of grades calculated by aggregating item ratings.
774
     *
775
     * @param stdClass $options {
776
     *      userid => int the id of the user whose items were rated, NOT the user who submitted ratings. 0 to update all. [required]
777
     *      aggregationmethod => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
778
     *      scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
779
     *      itemtable => int the table containing the items [required]
780
     *      itemtableusercolum => int the column of the user table containing the item owner's user id [required]
781
     *      component => The component for the ratings [required]
782
     *      ratingarea => The ratingarea for the ratings [required]
783
     *      contextid => int the context in which the rated items exist [optional]
784
     *      modulename => string the name of the module [optional]
785
     *      moduleid => int the id of the module instance [optional]
786
     * }
787
     * @return array the array of the user's grades
788
     */
789
    public function get_user_grades($options) {
790
        global $DB;
791
 
792
        $contextid = null;
793
 
794
        if (!isset($options->component)) {
795
            throw new coding_exception('The component option is now a required option when getting user grades from ratings.');
796
        }
797
        if (!isset($options->ratingarea)) {
798
            throw new coding_exception('The ratingarea option is now a required option when getting user grades from ratings.');
799
        }
800
 
801
        // If the calling code doesn't supply a context id we'll have to figure it out.
802
        if (!empty($options->contextid)) {
803
            $contextid = $options->contextid;
804
        } else if (!empty($options->modulename) && !empty($options->moduleid)) {
805
            $modulename = $options->modulename;
806
            $moduleid   = intval($options->moduleid);
807
 
808
            // Going direct to the db for the context id seems wrong.
809
            $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
810
            $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel)";
811
            $sql = "SELECT cm.* $ctxselect
812
                      FROM {course_modules} cm
813
                 LEFT JOIN {modules} mo ON mo.id = cm.module
814
                 LEFT JOIN {{$modulename}} m ON m.id = cm.instance $ctxjoin
815
                     WHERE mo.name=:modulename AND
816
                           m.id=:moduleid";
817
            $params = array('modulename' => $modulename, 'moduleid' => $moduleid, 'contextlevel' => CONTEXT_MODULE);
818
            $contextrecord = $DB->get_record_sql($sql, $params, '*', MUST_EXIST);
819
            $contextid = $contextrecord->ctxid;
820
        }
821
 
822
        $params = array();
823
        $params['contextid']  = $contextid;
824
        $params['component']  = $options->component;
825
        $params['ratingarea'] = $options->ratingarea;
826
        $itemtable            = $options->itemtable;
827
        $itemtableusercolumn  = $options->itemtableusercolumn;
828
        $scaleid              = $options->scaleid;
829
 
830
        // Ensure average aggregation returns float.
831
        $aggregationstring = $this->get_aggregation_method($options->aggregationmethod);
832
        $aggregationfield = 'r.rating';
833
        if ($aggregationstring === 'AVG') {
834
            $aggregationfield = "1.0 * {$aggregationfield}";
835
        }
836
 
837
        // If userid is not 0 we only want the grade for a single user.
838
        $singleuserwhere = '';
839
        if ($options->userid != 0) {
840
            $params['userid1'] = intval($options->userid);
841
            $singleuserwhere = "AND i.{$itemtableusercolumn} = :userid1";
842
        }
843
 
844
        // MDL-24648 The where line used to be "WHERE (r.contextid is null or r.contextid=:contextid)".
845
        // r.contextid will be null for users who haven't been rated yet.
846
        // No longer including users who haven't been rated to reduce memory requirements.
847
        $sql = "SELECT u.id as id, u.id AS userid, {$aggregationstring}({$aggregationfield}) AS rawgrade
848
                  FROM {user} u
849
             LEFT JOIN {{$itemtable}} i ON u.id=i.{$itemtableusercolumn}
850
             LEFT JOIN {rating} r ON r.itemid=i.id
851
                 WHERE r.contextid = :contextid AND
852
                       r.component = :component AND
853
                       r.ratingarea = :ratingarea
854
                       $singleuserwhere
855
              GROUP BY u.id";
856
        $results = $DB->get_records_sql($sql, $params);
857
 
858
        if ($results) {
859
 
860
            $scale = null;
861
            $max = 0;
862
            if ($options->scaleid >= 0) {
863
                // Numeric.
864
                $max = $options->scaleid;
865
            } else {
866
                // Custom scales.
867
                $scale = $DB->get_record('scale', array('id' => -$options->scaleid));
868
                if ($scale) {
869
                    $scale = explode(',', $scale->scale);
870
                    $max = count($scale);
871
                } else {
872
                    debugging('rating_manager::get_user_grades() received a scale ID that doesnt exist');
873
                }
874
            }
875
 
876
            // It could throw off the grading if count and sum returned a rawgrade higher than scale
877
            // so to prevent it we review the results and ensure that rawgrade does not exceed the scale.
878
            // If it does we set rawgrade = scale (i.e. full credit).
879
            foreach ($results as $rid => $result) {
880
                if ($options->scaleid >= 0) {
881
                    // Numeric.
882
                    if ($result->rawgrade > $options->scaleid) {
883
                        $results[$rid]->rawgrade = $options->scaleid;
884
                    }
885
                } else {
886
                    // Scales.
887
                    if (!empty($scale) && $result->rawgrade > $max) {
888
                        $results[$rid]->rawgrade = $max;
889
                    }
890
                }
891
            }
892
        }
893
 
894
        return $results;
895
    }
896
 
897
    /**
898
     * Returns array of aggregate types. Used by ratings.
899
     *
900
     * @return array aggregate types
901
     */
902
    public function get_aggregate_types() {
903
        return array (RATING_AGGREGATE_NONE     => get_string('aggregatenone', 'rating'),
904
                      RATING_AGGREGATE_AVERAGE  => get_string('aggregateavg', 'rating'),
905
                      RATING_AGGREGATE_COUNT    => get_string('aggregatecount', 'rating'),
906
                      RATING_AGGREGATE_MAXIMUM  => get_string('aggregatemax', 'rating'),
907
                      RATING_AGGREGATE_MINIMUM  => get_string('aggregatemin', 'rating'),
908
                      RATING_AGGREGATE_SUM      => get_string('aggregatesum', 'rating'));
909
    }
910
 
911
    /**
912
     * Converts an aggregation method constant into something that can be included in SQL
913
     *
914
     * @param int $aggregate An aggregation constant. For example, RATING_AGGREGATE_AVERAGE.
915
     * @return string an SQL aggregation method
916
     */
917
    public function get_aggregation_method($aggregate) {
918
        $aggregatestr = null;
919
        switch($aggregate){
920
            case RATING_AGGREGATE_AVERAGE:
921
                $aggregatestr = 'AVG';
922
                break;
923
            case RATING_AGGREGATE_COUNT:
924
                $aggregatestr = 'COUNT';
925
                break;
926
            case RATING_AGGREGATE_MAXIMUM:
927
                $aggregatestr = 'MAX';
928
                break;
929
            case RATING_AGGREGATE_MINIMUM:
930
                $aggregatestr = 'MIN';
931
                break;
932
            case RATING_AGGREGATE_SUM:
933
                $aggregatestr = 'SUM';
934
                break;
935
            default:
936
                $aggregatestr = 'AVG'; // Default to this to avoid real breakage - MDL-22270.
937
                debugging('Incorrect call to get_aggregation_method(), incorrect aggregate method ' . $aggregate, DEBUG_DEVELOPER);
938
        }
939
        return $aggregatestr;
940
    }
941
 
942
    /**
943
     * Looks for a callback like forum_rating_permissions() to retrieve permissions from the plugin whose items are being rated
944
     *
945
     * @param int $contextid The current context id
946
     * @param string $component the name of the component that is using ratings ie 'mod_forum'
947
     * @param string $ratingarea The area the rating is associated with
948
     * @return array rating related permissions
949
     */
950
    public function get_plugin_permissions_array($contextid, $component, $ratingarea) {
951
        $pluginpermissionsarray = null;
952
        // Deny by default.
953
        $defaultpluginpermissions = array('rate' => false, 'view' => false, 'viewany' => false, 'viewall' => false);
954
        if (!empty($component)) {
955
            list($type, $name) = core_component::normalize_component($component);
956
            $pluginpermissionsarray = plugin_callback($type,
957
                                                      $name,
958
                                                      'rating',
959
                                                      'permissions',
960
                                                      array($contextid, $component, $ratingarea),
961
                                                      $defaultpluginpermissions);
962
        } else {
963
            $pluginpermissionsarray = $defaultpluginpermissions;
964
        }
965
        return $pluginpermissionsarray;
966
    }
967
 
968
    /**
969
     * Validates a submitted rating
970
     *
971
     * @param array $params submitted data
972
     *      context => object the context in which the rated items exists [required]
973
     *      component => The component the rating belongs to [required]
974
     *      ratingarea => The ratingarea the rating is associated with [required]
975
     *      itemid => int the ID of the object being rated [required]
976
     *      scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
977
     *      rating => int the submitted rating
978
     *      rateduserid => int the id of the user whose items have been rated. 0 to update all. [required]
979
     *      aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [optional]
980
     * @return boolean true if the rating is valid, false if callback not found, throws rating_exception if rating is invalid
981
     */
982
    public function check_rating_is_valid($params) {
983
 
984
        if (!isset($params['context'])) {
985
            throw new coding_exception('The context option is a required option when checking rating validity.');
986
        }
987
        if (!isset($params['component'])) {
988
            throw new coding_exception('The component option is now a required option when checking rating validity');
989
        }
990
        if (!isset($params['ratingarea'])) {
991
            throw new coding_exception('The ratingarea option is now a required option when checking rating validity');
992
        }
993
        if (!isset($params['itemid'])) {
994
            throw new coding_exception('The itemid option is now a required option when checking rating validity');
995
        }
996
        if (!isset($params['scaleid'])) {
997
            throw new coding_exception('The scaleid option is now a required option when checking rating validity');
998
        }
999
        if (!isset($params['rateduserid'])) {
1000
            throw new coding_exception('The rateduserid option is now a required option when checking rating validity');
1001
        }
1002
 
1003
        list($plugintype, $pluginname) = core_component::normalize_component($params['component']);
1004
 
1005
        // This looks for a function like forum_rating_validate() in mod_forum lib.php
1006
        // wrapping the params array in another array as call_user_func_array() expands arrays into multiple arguments.
1007
        $isvalid = plugin_callback($plugintype, $pluginname, 'rating', 'validate', array($params), null);
1008
 
1009
        // If null then the callback does not exist.
1010
        if ($isvalid === null) {
1011
            $isvalid = false;
1012
            debugging('rating validation callback not found for component '.  clean_param($component, PARAM_ALPHANUMEXT));
1013
        }
1014
        return $isvalid;
1015
    }
1016
 
1017
    /**
1018
     * Initialises JavaScript to enable AJAX ratings on the provided page
1019
     *
1020
     * @param moodle_page $page
1021
     * @return true always returns true
1022
     */
1023
    public function initialise_rating_javascript(moodle_page $page) {
1024
        global $CFG;
1025
 
1026
        // Only needs to be initialized once.
1027
        static $done = false;
1028
        if ($done) {
1029
            return true;
1030
        }
1031
 
1032
        $page->requires->js_init_call('M.core_rating.init');
1033
        $done = true;
1034
 
1035
        return true;
1036
    }
1037
 
1038
    /**
1039
     * Returns a string that describes the aggregation method that was provided.
1040
     *
1041
     * @param string $aggregationmethod
1042
     * @return string describes the aggregation method that was provided
1043
     */
1044
    public function get_aggregate_label($aggregationmethod) {
1045
        $aggregatelabel = '';
1046
        switch ($aggregationmethod) {
1047
            case RATING_AGGREGATE_AVERAGE :
1048
                $aggregatelabel .= get_string("aggregateavg", "rating");
1049
                break;
1050
            case RATING_AGGREGATE_COUNT :
1051
                $aggregatelabel .= get_string("aggregatecount", "rating");
1052
                break;
1053
            case RATING_AGGREGATE_MAXIMUM :
1054
                $aggregatelabel .= get_string("aggregatemax", "rating");
1055
                break;
1056
            case RATING_AGGREGATE_MINIMUM :
1057
                $aggregatelabel .= get_string("aggregatemin", "rating");
1058
                break;
1059
            case RATING_AGGREGATE_SUM :
1060
                $aggregatelabel .= get_string("aggregatesum", "rating");
1061
                break;
1062
        }
1063
        $aggregatelabel .= get_string('labelsep', 'langconfig');
1064
        return $aggregatelabel;
1065
    }
1066
 
1067
    /**
1068
     * Adds a new rating
1069
     *
1070
     * @param stdClass $cm course module object
1071
     * @param stdClass $context context object
1072
     * @param string $component component name
1073
     * @param string $ratingarea rating area
1074
     * @param int $itemid the item id
1075
     * @param int $scaleid the scale id
1076
     * @param int $userrating the user rating
1077
     * @param int $rateduserid the rated user id
1078
     * @param int $aggregationmethod the aggregation method
1079
     * @since Moodle 3.2
1080
     */
1081
    public function add_rating($cm, $context, $component, $ratingarea, $itemid, $scaleid, $userrating, $rateduserid,
1082
                                $aggregationmethod) {
1083
        global $CFG, $DB, $USER;
1084
 
1085
        $result = new stdClass;
1086
        // Check the module rating permissions.
1087
        // Doing this check here rather than within rating_manager::get_ratings() so we can return a error response.
1088
        $pluginpermissionsarray = $this->get_plugin_permissions_array($context->id, $component, $ratingarea);
1089
 
1090
        if (!$pluginpermissionsarray['rate']) {
1091
            $result->error = 'ratepermissiondenied';
1092
            return $result;
1093
        } else {
1094
            $params = array(
1095
                'context'     => $context,
1096
                'component'   => $component,
1097
                'ratingarea'  => $ratingarea,
1098
                'itemid'      => $itemid,
1099
                'scaleid'     => $scaleid,
1100
                'rating'      => $userrating,
1101
                'rateduserid' => $rateduserid,
1102
                'aggregation' => $aggregationmethod
1103
            );
1104
            if (!$this->check_rating_is_valid($params)) {
1105
                $result->error = 'ratinginvalid';
1106
                return $result;
1107
            }
1108
        }
1109
 
1110
        // Rating options used to update the rating then retrieve the aggregate.
1111
        $ratingoptions = new stdClass;
1112
        $ratingoptions->context = $context;
1113
        $ratingoptions->ratingarea = $ratingarea;
1114
        $ratingoptions->component = $component;
1115
        $ratingoptions->itemid  = $itemid;
1116
        $ratingoptions->scaleid = $scaleid;
1117
        $ratingoptions->userid  = $USER->id;
1118
 
1119
        if ($userrating != RATING_UNSET_RATING) {
1120
            $rating = new rating($ratingoptions);
1121
            $rating->update_rating($userrating);
1122
        } else { // Delete the rating if the user set to "Rate..."
1123
            $options = new stdClass;
1124
            $options->contextid = $context->id;
1125
            $options->component = $component;
1126
            $options->ratingarea = $ratingarea;
1127
            $options->userid = $USER->id;
1128
            $options->itemid = $itemid;
1129
 
1130
            $this->delete_ratings($options);
1131
        }
1132
 
1133
        // Future possible enhancement: add a setting to turn grade updating off for those who don't want them in gradebook.
1134
        // Note that this would need to be done in both rate.php and rate_ajax.php.
1135
        if ($context->contextlevel == CONTEXT_MODULE) {
1136
            // Tell the module that its grades have changed.
1137
            $modinstance = $DB->get_record($cm->modname, array('id' => $cm->instance));
1138
            if ($modinstance) {
1139
                $modinstance->cmidnumber = $cm->id; // MDL-12961.
1140
                $functionname = $cm->modname.'_update_grades';
1141
                require_once($CFG->dirroot."/mod/{$cm->modname}/lib.php");
1142
                if (function_exists($functionname)) {
1143
                    $functionname($modinstance, $rateduserid);
1144
                }
1145
            }
1146
        }
1147
 
1148
        // Object to return to client as JSON.
1149
        $result->success = true;
1150
 
1151
        // Need to retrieve the updated item to get its new aggregate value.
1152
        $item = new stdClass;
1153
        $item->id = $itemid;
1154
 
1155
        // Most of $ratingoptions variables were previously set.
1156
        $ratingoptions->items = array($item);
1157
        $ratingoptions->aggregate = $aggregationmethod;
1158
 
1159
        $items = $this->get_ratings($ratingoptions);
1160
        $firstrating = $items[0]->rating;
1161
 
1162
        // See if the user has permission to see the rating aggregate.
1163
        if ($firstrating->user_can_view_aggregate()) {
1164
 
1165
            // For custom scales return text not the value.
1166
            // This scales weirdness will go away when scales are refactored.
1167
            $scalearray = null;
1168
            $aggregatetoreturn = round($firstrating->aggregate, 1);
1169
 
1170
            // Output a dash if aggregation method == COUNT as the count is output next to the aggregate anyway.
1171
            if ($firstrating->settings->aggregationmethod == RATING_AGGREGATE_COUNT or $firstrating->count == 0) {
1172
                $aggregatetoreturn = ' - ';
1173
            } else if ($firstrating->settings->scale->id < 0) { // If its non-numeric scale.
1174
                // Dont use the scale item if the aggregation method is sum as adding items from a custom scale makes no sense.
1175
                if ($firstrating->settings->aggregationmethod != RATING_AGGREGATE_SUM) {
1176
                    $scalerecord = $DB->get_record('scale', array('id' => -$firstrating->settings->scale->id));
1177
                    if ($scalerecord) {
1178
                        $scalearray = explode(',', $scalerecord->scale);
1179
                        $aggregatetoreturn = $scalearray[$aggregatetoreturn - 1];
1180
                    }
1181
                }
1182
            }
1183
 
1184
            $result->aggregate = $aggregatetoreturn;
1185
            $result->count = $firstrating->count;
1186
            $result->itemid = $itemid;
1187
        }
1188
        return $result;
1189
    }
1190
 
1191
    /**
1192
     * Get ratings created since a given time.
1193
     *
1194
     * @param  stdClass $context   context object
1195
     * @param  string $component  component name
1196
     * @param  int $since         the time to check
1197
     * @return array list of ratings db records since the given timelimit
1198
     * @since Moodle 3.2
1199
     */
1200
    public function get_component_ratings_since($context, $component, $since) {
1201
        global $DB, $USER;
1202
 
1203
        $ratingssince = array();
1204
        $where = 'contextid = ? AND component = ? AND (timecreated > ? OR timemodified > ?)';
1205
        $ratings = $DB->get_records_select('rating', $where, array($context->id, $component, $since, $since));
1206
        // Check area by area if we have permissions.
1207
        $permissions = array();
1208
        $rm = new rating_manager();
1209
 
1210
        foreach ($ratings as $rating) {
1211
            // Check if the permission array for the area is cached.
1212
            if (!isset($permissions[$rating->ratingarea])) {
1213
                $permissions[$rating->ratingarea] = $rm->get_plugin_permissions_array($context->id, $component,
1214
                                                                                        $rating->ratingarea);
1215
            }
1216
 
1217
            if (($permissions[$rating->ratingarea]['view'] and $rating->userid == $USER->id) or
1218
                    ($permissions[$rating->ratingarea]['viewany'] or $permissions[$rating->ratingarea]['viewall'])) {
1219
                $ratingssince[$rating->id] = $rating;
1220
            }
1221
        }
1222
        return $ratingssince;
1223
    }
1224
} // End rating_manager class definition.
1225
 
1226
/**
1227
 * The rating_exception class for exceptions specific to the ratings system
1228
 *
1229
 * @package   core_rating
1230
 * @category  rating
1231
 * @copyright 2010 Andrew Davis
1232
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1233
 * @since     Moodle 2.0
1234
 */
1235
class rating_exception extends moodle_exception {
1236
    /**
1237
     * @var string The message to accompany the thrown exception
1238
     */
1239
    public $message;
1240
    /**
1241
     * Generate exceptions that can be easily identified as coming from the ratings system
1242
     *
1243
     * @param string $errorcode the error code to generate
1244
     */
1245
    public function __construct($errorcode) {
1246
        $this->errorcode = $errorcode;
1247
        $this->message = get_string($errorcode, 'error');
1248
    }
1249
}