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 |
}
|