Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/rating/ -> lib.php (source)

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

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