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