Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/rating/ -> lib.php (source)

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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          $userfields = user_picture::fields('u', null, 'userid');
 459          $sql = "SELECT r.id, r.rating, r.itemid, r.userid, r.timemodified, r.component, r.ratingarea, $userfields
 460                    FROM {rating} r
 461               LEFT JOIN {user} u ON r.userid = u.id
 462                   WHERE r.contextid = :contextid AND
 463                         r.itemid  = :itemid AND
 464                         r.component = :component AND
 465                         r.ratingarea = :ratingarea
 466                         {$sortclause}";
 467  
 468          return $DB->get_records_sql($sql, $params);
 469      }
 470  
 471      /**
 472       * Adds rating objects to an array of items (forum posts, glossary entries etc). Rating objects are available at $item->rating
 473       *
 474       * @param stdClass $options {
 475       *      context          => context the context in which the ratings exists [required]
 476       *      component        => the component name ie mod_forum [required]
 477       *      ratingarea       => the ratingarea we are interested in [required]
 478       *      items            => array items like forum posts or glossary items. Each item needs an 'id' ie $items[0]->id [required]
 479       *      aggregate        => int aggregation method to apply. RATING_AGGREGATE_AVERAGE, RATING_AGGREGATE_MAXIMUM etc [required]
 480       *      scaleid          => int the scale from which the user can select a rating [required]
 481       *      userid           => int the id of the current user [optional]
 482       *      returnurl        => string the url to return the user to after submitting a rating. Null for ajax requests [optional]
 483       *      assesstimestart  => int only allow rating of items created after this timestamp [optional]
 484       *      assesstimefinish => int only allow rating of items created before this timestamp [optional]
 485       * @return array the array of items with their ratings attached at $items[0]->rating
 486       */
 487      public function get_ratings($options) {
 488          global $DB, $USER;
 489  
 490          if (!isset($options->context)) {
 491              throw new coding_exception('The context option is a required option when getting ratings.');
 492          }
 493  
 494          if (!isset($options->component)) {
 495              throw new coding_exception('The component option is a required option when getting ratings.');
 496          }
 497  
 498          if (!isset($options->ratingarea)) {
 499              throw new coding_exception('The ratingarea option is a required option when getting ratings.');
 500          }
 501  
 502          if (!isset($options->scaleid)) {
 503              throw new coding_exception('The scaleid option is a required option when getting ratings.');
 504          }
 505  
 506          if (!isset($options->items)) {
 507              throw new coding_exception('The items option is a required option when getting ratings.');
 508          } else if (empty($options->items)) {
 509              return array();
 510          }
 511  
 512          if (!isset($options->aggregate)) {
 513              throw new coding_exception('The aggregate option is a required option when getting ratings.');
 514          } else if ($options->aggregate == RATING_AGGREGATE_NONE) {
 515              // Ratings are not enabled.
 516              return $options->items;
 517          }
 518          $aggregatestr = $this->get_aggregation_method($options->aggregate);
 519  
 520          // Default the userid to the current user if it is not set.
 521          if (empty($options->userid)) {
 522              $userid = $USER->id;
 523          } else {
 524              $userid = $options->userid;
 525          }
 526  
 527          // Get the item table name, the item id field, and the item user field for the given rating item
 528          // from the related component.
 529          list($type, $name) = core_component::normalize_component($options->component);
 530          $default = array(null, 'id', 'userid');
 531          list($itemtablename, $itemidcol, $itemuseridcol) = plugin_callback($type,
 532                                                                             $name,
 533                                                                             'rating',
 534                                                                             'get_item_fields',
 535                                                                             array($options),
 536                                                                             $default);
 537  
 538          // Create an array of item IDs.
 539          $itemids = array();
 540          foreach ($options->items as $item) {
 541              $itemids[] = $item->{$itemidcol};
 542          }
 543  
 544          // Get the items from the database.
 545          list($itemidtest, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
 546          $params['contextid'] = $options->context->id;
 547          $params['userid']    = $userid;
 548          $params['component']    = $options->component;
 549          $params['ratingarea'] = $options->ratingarea;
 550  
 551          $sql = "SELECT r.id, r.itemid, r.userid, r.scaleid, r.rating AS usersrating
 552                    FROM {rating} r
 553                   WHERE r.userid = :userid AND
 554                         r.contextid = :contextid AND
 555                         r.itemid {$itemidtest} AND
 556                         r.component = :component AND
 557                         r.ratingarea = :ratingarea
 558                ORDER BY r.itemid";
 559          $userratings = $DB->get_records_sql($sql, $params);
 560  
 561          $sql = "SELECT r.itemid, $aggregatestr(r.rating) AS aggrrating, COUNT(r.rating) AS numratings
 562                    FROM {rating} r
 563                   WHERE r.contextid = :contextid AND
 564                         r.itemid {$itemidtest} AND
 565                         r.component = :component AND
 566                         r.ratingarea = :ratingarea
 567                GROUP BY r.itemid, r.component, r.ratingarea, r.contextid
 568                ORDER BY r.itemid";
 569          $aggregateratings = $DB->get_records_sql($sql, $params);
 570  
 571          $ratingoptions = new stdClass;
 572          $ratingoptions->context = $options->context;
 573          $ratingoptions->component = $options->component;
 574          $ratingoptions->ratingarea = $options->ratingarea;
 575          $ratingoptions->settings = $this->generate_rating_settings_object($options);
 576          foreach ($options->items as $item) {
 577              $founduserrating = false;
 578              foreach ($userratings as $userrating) {
 579                  // Look for an existing rating from this user of this item.
 580                  if ($item->{$itemidcol} == $userrating->itemid) {
 581                      // Note: rec->scaleid = the id of scale at the time the rating was submitted.
 582                      // It may be different from the current scale id.
 583                      $ratingoptions->scaleid = $userrating->scaleid;
 584                      $ratingoptions->userid = $userrating->userid;
 585                      $ratingoptions->id = $userrating->id;
 586                      $ratingoptions->rating = min($userrating->usersrating, $ratingoptions->settings->scale->max);
 587  
 588                      $founduserrating = true;
 589                      break;
 590                  }
 591              }
 592              if (!$founduserrating) {
 593                  $ratingoptions->scaleid = null;
 594                  $ratingoptions->userid = null;
 595                  $ratingoptions->id = null;
 596                  $ratingoptions->rating = null;
 597              }
 598  
 599              if (array_key_exists($item->{$itemidcol}, $aggregateratings)) {
 600                  $rec = $aggregateratings[$item->{$itemidcol}];
 601                  $ratingoptions->itemid = $item->{$itemidcol};
 602                  $ratingoptions->aggregate = min($rec->aggrrating, $ratingoptions->settings->scale->max);
 603                  $ratingoptions->count = $rec->numratings;
 604              } else {
 605                  $ratingoptions->itemid = $item->{$itemidcol};
 606                  $ratingoptions->aggregate = null;
 607                  $ratingoptions->count = 0;
 608              }
 609  
 610              $rating = new rating($ratingoptions);
 611              $rating->itemtimecreated = $this->get_item_time_created($item);
 612              if (!empty($item->{$itemuseridcol})) {
 613                  $rating->itemuserid = $item->{$itemuseridcol};
 614              }
 615              $item->rating = $rating;
 616          }
 617  
 618          return $options->items;
 619      }
 620  
 621      /**
 622       * Generates a rating settings object based upon the options it is provided.
 623       *
 624       * @param stdClass $options {
 625       *      context           => context the context in which the ratings exists [required]
 626       *      component         => string The component the items belong to [required]
 627       *      ratingarea        => string The ratingarea the items belong to [required]
 628       *      aggregate         => int Aggregation method to apply. RATING_AGGREGATE_AVERAGE, RATING_AGGREGATE_MAXIMUM etc [required]
 629       *      scaleid           => int the scale from which the user can select a rating [required]
 630       *      returnurl         => string the url to return the user to after submitting a rating. Null for ajax requests [optional]
 631       *      assesstimestart   => int only allow rating of items created after this timestamp [optional]
 632       *      assesstimefinish  => int only allow rating of items created before this timestamp [optional]
 633       *      plugintype        => string plugin type ie 'mod' Used to find the permissions callback [optional]
 634       *      pluginname        => string plugin name ie 'forum' Used to find the permissions callback [optional]
 635       * }
 636       * @return stdClass rating settings object
 637       */
 638      protected function generate_rating_settings_object($options) {
 639  
 640          if (!isset($options->context)) {
 641              throw new coding_exception('The context option is a required option when generating a rating settings object.');
 642          }
 643          if (!isset($options->component)) {
 644              throw new coding_exception('The component option is now a required option when generating a rating settings object.');
 645          }
 646          if (!isset($options->ratingarea)) {
 647              throw new coding_exception('The ratingarea option is now a required option when generating a rating settings object.');
 648          }
 649          if (!isset($options->aggregate)) {
 650              throw new coding_exception('The aggregate option is now a required option when generating a rating settings object.');
 651          }
 652          if (!isset($options->scaleid)) {
 653              throw new coding_exception('The scaleid option is now a required option when generating a rating settings object.');
 654          }
 655  
 656          // Settings that are common to all ratings objects in this context.
 657          $settings = new stdClass;
 658          $settings->scale             = $this->generate_rating_scale_object($options->scaleid); // The scale to use now.
 659          $settings->aggregationmethod = $options->aggregate;
 660          $settings->assesstimestart   = null;
 661          $settings->assesstimefinish  = null;
 662  
 663          // Collect options into the settings object.
 664          if (!empty($options->assesstimestart)) {
 665              $settings->assesstimestart = $options->assesstimestart;
 666          }
 667          if (!empty($options->assesstimefinish)) {
 668              $settings->assesstimefinish = $options->assesstimefinish;
 669          }
 670          if (!empty($options->returnurl)) {
 671              $settings->returnurl = $options->returnurl;
 672          }
 673  
 674          // Check site capabilities.
 675          $settings->permissions = new stdClass;
 676          // Can view the aggregate of ratings of their own items.
 677          $settings->permissions->view    = has_capability('moodle/rating:view', $options->context);
 678          // Can view the aggregate of ratings of other people's items.
 679          $settings->permissions->viewany = has_capability('moodle/rating:viewany', $options->context);
 680          // Can view individual ratings.
 681          $settings->permissions->viewall = has_capability('moodle/rating:viewall', $options->context);
 682          // Can submit ratings.
 683          $settings->permissions->rate    = has_capability('moodle/rating:rate', $options->context);
 684  
 685          // Check module capabilities
 686          // This is mostly for backwards compatability with old modules that previously implemented their own ratings.
 687          $pluginpermissionsarray = $this->get_plugin_permissions_array($options->context->id,
 688                                                                        $options->component,
 689                                                                        $options->ratingarea);
 690          $settings->pluginpermissions = new stdClass;
 691          $settings->pluginpermissions->view    = $pluginpermissionsarray['view'];
 692          $settings->pluginpermissions->viewany = $pluginpermissionsarray['viewany'];
 693          $settings->pluginpermissions->viewall = $pluginpermissionsarray['viewall'];
 694          $settings->pluginpermissions->rate    = $pluginpermissionsarray['rate'];
 695  
 696          return $settings;
 697      }
 698  
 699      /**
 700       * Generates a scale object that can be returned
 701       *
 702       * @global moodle_database $DB moodle database object
 703       * @param int $scaleid scale-type identifier
 704       * @return stdClass scale for ratings
 705       */
 706      protected function generate_rating_scale_object($scaleid) {
 707          global $DB;
 708          if (!array_key_exists('s'.$scaleid, $this->scales)) {
 709              $scale = new stdClass;
 710              $scale->id = $scaleid;
 711              $scale->name = null;
 712              $scale->courseid = null;
 713              $scale->scaleitems = array();
 714              $scale->isnumeric = true;
 715              $scale->max = $scaleid;
 716  
 717              if ($scaleid < 0) {
 718                  // It is a proper scale (not numeric).
 719                  $scalerecord = $DB->get_record('scale', array('id' => abs($scaleid)));
 720                  if ($scalerecord) {
 721                      // We need to generate an array with string keys starting at 1.
 722                      $scalearray = explode(',', $scalerecord->scale);
 723                      $c = count($scalearray);
 724                      for ($i = 0; $i < $c; $i++) {
 725                          // Treat index as a string to allow sorting without changing the value.
 726                          $scale->scaleitems[(string)($i + 1)] = $scalearray[$i];
 727                      }
 728                      krsort($scale->scaleitems); // Have the highest grade scale item appear first.
 729                      $scale->isnumeric = false;
 730                      $scale->name = $scalerecord->name;
 731                      $scale->courseid = $scalerecord->courseid;
 732                      $scale->max = count($scale->scaleitems);
 733                  }
 734              } else {
 735                  // Generate an array of values for numeric scales.
 736                  for ($i = 0; $i <= (int)$scaleid; $i++) {
 737                      $scale->scaleitems[(string)$i] = $i;
 738                  }
 739              }
 740              $this->scales['s'.$scaleid] = $scale;
 741          }
 742          return $this->scales['s'.$scaleid];
 743      }
 744  
 745      /**
 746       * Gets the time the given item was created
 747       *
 748       * TODO: MDL-31511 - Find a better solution for this, its not ideal to test for fields really we should be
 749       * asking the component the item belongs to what field to look for or even the value we
 750       * are looking for.
 751       *
 752       * @param stdClass $item
 753       * @return int|null return null if the created time is unavailable, otherwise return a timestamp
 754       */
 755      protected function get_item_time_created($item) {
 756          if (!empty($item->created)) {
 757              return $item->created; // The forum_posts table has created instead of timecreated.
 758          } else if (!empty($item->timecreated)) {
 759              return $item->timecreated;
 760          } else {
 761              return null;
 762          }
 763      }
 764  
 765      /**
 766       * Returns an array of grades calculated by aggregating item ratings.
 767       *
 768       * @param stdClass $options {
 769       *      userid => int the id of the user whose items were rated, NOT the user who submitted ratings. 0 to update all. [required]
 770       *      aggregationmethod => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
 771       *      scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
 772       *      itemtable => int the table containing the items [required]
 773       *      itemtableusercolum => int the column of the user table containing the item owner's user id [required]
 774       *      component => The component for the ratings [required]
 775       *      ratingarea => The ratingarea for the ratings [required]
 776       *      contextid => int the context in which the rated items exist [optional]
 777       *      modulename => string the name of the module [optional]
 778       *      moduleid => int the id of the module instance [optional]
 779       * }
 780       * @return array the array of the user's grades
 781       */
 782      public function get_user_grades($options) {
 783          global $DB;
 784  
 785          $contextid = null;
 786  
 787          if (!isset($options->component)) {
 788              throw new coding_exception('The component option is now a required option when getting user grades from ratings.');
 789          }
 790          if (!isset($options->ratingarea)) {
 791              throw new coding_exception('The ratingarea option is now a required option when getting user grades from ratings.');
 792          }
 793  
 794          // If the calling code doesn't supply a context id we'll have to figure it out.
 795          if (!empty($options->contextid)) {
 796              $contextid = $options->contextid;
 797          } else if (!empty($options->modulename) && !empty($options->moduleid)) {
 798              $modulename = $options->modulename;
 799              $moduleid   = intval($options->moduleid);
 800  
 801              // Going direct to the db for the context id seems wrong.
 802              $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
 803              $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel)";
 804              $sql = "SELECT cm.* $ctxselect
 805                        FROM {course_modules} cm
 806                   LEFT JOIN {modules} mo ON mo.id = cm.module
 807                   LEFT JOIN {{$modulename}} m ON m.id = cm.instance $ctxjoin
 808                       WHERE mo.name=:modulename AND
 809                             m.id=:moduleid";
 810              $params = array('modulename' => $modulename, 'moduleid' => $moduleid, 'contextlevel' => CONTEXT_MODULE);
 811              $contextrecord = $DB->get_record_sql($sql, $params, '*', MUST_EXIST);
 812              $contextid = $contextrecord->ctxid;
 813          }
 814  
 815          $params = array();
 816          $params['contextid']  = $contextid;
 817          $params['component']  = $options->component;
 818          $params['ratingarea'] = $options->ratingarea;
 819          $itemtable            = $options->itemtable;
 820          $itemtableusercolumn  = $options->itemtableusercolumn;
 821          $scaleid              = $options->scaleid;
 822          $aggregationstring    = $this->get_aggregation_method($options->aggregationmethod);
 823  
 824          // If userid is not 0 we only want the grade for a single user.
 825          $singleuserwhere = '';
 826          if ($options->userid != 0) {
 827              $params['userid1'] = intval($options->userid);
 828              $singleuserwhere = "AND i.{$itemtableusercolumn} = :userid1";
 829          }
 830  
 831          // MDL-24648 The where line used to be "WHERE (r.contextid is null or r.contextid=:contextid)".
 832          // r.contextid will be null for users who haven't been rated yet.
 833          // No longer including users who haven't been rated to reduce memory requirements.
 834          $sql = "SELECT u.id as id, u.id AS userid, $aggregationstring(r.rating) AS rawgrade
 835                    FROM {user} u
 836               LEFT JOIN {{$itemtable}} i ON u.id=i.{$itemtableusercolumn}
 837               LEFT JOIN {rating} r ON r.itemid=i.id
 838                   WHERE r.contextid = :contextid AND
 839                         r.component = :component AND
 840                         r.ratingarea = :ratingarea
 841                         $singleuserwhere
 842                GROUP BY u.id";
 843          $results = $DB->get_records_sql($sql, $params);
 844  
 845          if ($results) {
 846  
 847              $scale = null;
 848              $max = 0;
 849              if ($options->scaleid >= 0) {
 850                  // Numeric.
 851                  $max = $options->scaleid;
 852              } else {
 853                  // Custom scales.
 854                  $scale = $DB->get_record('scale', array('id' => -$options->scaleid));
 855                  if ($scale) {
 856                      $scale = explode(',', $scale->scale);
 857                      $max = count($scale);
 858                  } else {
 859                      debugging('rating_manager::get_user_grades() received a scale ID that doesnt exist');
 860                  }
 861              }
 862  
 863              // It could throw off the grading if count and sum returned a rawgrade higher than scale
 864              // so to prevent it we review the results and ensure that rawgrade does not exceed the scale.
 865              // If it does we set rawgrade = scale (i.e. full credit).
 866              foreach ($results as $rid => $result) {
 867                  if ($options->scaleid >= 0) {
 868                      // Numeric.
 869                      if ($result->rawgrade > $options->scaleid) {
 870                          $results[$rid]->rawgrade = $options->scaleid;
 871                      }
 872                  } else {
 873                      // Scales.
 874                      if (!empty($scale) && $result->rawgrade > $max) {
 875                          $results[$rid]->rawgrade = $max;
 876                      }
 877                  }
 878              }
 879          }
 880  
 881          return $results;
 882      }
 883  
 884      /**
 885       * Returns array of aggregate types. Used by ratings.
 886       *
 887       * @return array aggregate types
 888       */
 889      public function get_aggregate_types() {
 890          return array (RATING_AGGREGATE_NONE     => get_string('aggregatenone', 'rating'),
 891                        RATING_AGGREGATE_AVERAGE  => get_string('aggregateavg', 'rating'),
 892                        RATING_AGGREGATE_COUNT    => get_string('aggregatecount', 'rating'),
 893                        RATING_AGGREGATE_MAXIMUM  => get_string('aggregatemax', 'rating'),
 894                        RATING_AGGREGATE_MINIMUM  => get_string('aggregatemin', 'rating'),
 895                        RATING_AGGREGATE_SUM      => get_string('aggregatesum', 'rating'));
 896      }
 897  
 898      /**
 899       * Converts an aggregation method constant into something that can be included in SQL
 900       *
 901       * @param int $aggregate An aggregation constant. For example, RATING_AGGREGATE_AVERAGE.
 902       * @return string an SQL aggregation method
 903       */
 904      public function get_aggregation_method($aggregate) {
 905          $aggregatestr = null;
 906          switch($aggregate){
 907              case RATING_AGGREGATE_AVERAGE:
 908                  $aggregatestr = 'AVG';
 909                  break;
 910              case RATING_AGGREGATE_COUNT:
 911                  $aggregatestr = 'COUNT';
 912                  break;
 913              case RATING_AGGREGATE_MAXIMUM:
 914                  $aggregatestr = 'MAX';
 915                  break;
 916              case RATING_AGGREGATE_MINIMUM:
 917                  $aggregatestr = 'MIN';
 918                  break;
 919              case RATING_AGGREGATE_SUM:
 920                  $aggregatestr = 'SUM';
 921                  break;
 922              default:
 923                  $aggregatestr = 'AVG'; // Default to this to avoid real breakage - MDL-22270.
 924                  debugging('Incorrect call to get_aggregation_method(), incorrect aggregate method ' . $aggregate, DEBUG_DEVELOPER);
 925          }
 926          return $aggregatestr;
 927      }
 928  
 929      /**
 930       * Looks for a callback like forum_rating_permissions() to retrieve permissions from the plugin whose items are being rated
 931       *
 932       * @param int $contextid The current context id
 933       * @param string $component the name of the component that is using ratings ie 'mod_forum'
 934       * @param string $ratingarea The area the rating is associated with
 935       * @return array rating related permissions
 936       */
 937      public function get_plugin_permissions_array($contextid, $component, $ratingarea) {
 938          $pluginpermissionsarray = null;
 939          // Deny by default.
 940          $defaultpluginpermissions = array('rate' => false, 'view' => false, 'viewany' => false, 'viewall' => false);
 941          if (!empty($component)) {
 942              list($type, $name) = core_component::normalize_component($component);
 943              $pluginpermissionsarray = plugin_callback($type,
 944                                                        $name,
 945                                                        'rating',
 946                                                        'permissions',
 947                                                        array($contextid, $component, $ratingarea),
 948                                                        $defaultpluginpermissions);
 949          } else {
 950              $pluginpermissionsarray = $defaultpluginpermissions;
 951          }
 952          return $pluginpermissionsarray;
 953      }
 954  
 955      /**
 956       * Validates a submitted rating
 957       *
 958       * @param array $params submitted data
 959       *      context => object the context in which the rated items exists [required]
 960       *      component => The component the rating belongs to [required]
 961       *      ratingarea => The ratingarea the rating is associated with [required]
 962       *      itemid => int the ID of the object being rated [required]
 963       *      scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
 964       *      rating => int the submitted rating
 965       *      rateduserid => int the id of the user whose items have been rated. 0 to update all. [required]
 966       *      aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [optional]
 967       * @return boolean true if the rating is valid, false if callback not found, throws rating_exception if rating is invalid
 968       */
 969      public function check_rating_is_valid($params) {
 970  
 971          if (!isset($params['context'])) {
 972              throw new coding_exception('The context option is a required option when checking rating validity.');
 973          }
 974          if (!isset($params['component'])) {
 975              throw new coding_exception('The component option is now a required option when checking rating validity');
 976          }
 977          if (!isset($params['ratingarea'])) {
 978              throw new coding_exception('The ratingarea option is now a required option when checking rating validity');
 979          }
 980          if (!isset($params['itemid'])) {
 981              throw new coding_exception('The itemid option is now a required option when checking rating validity');
 982          }
 983          if (!isset($params['scaleid'])) {
 984              throw new coding_exception('The scaleid option is now a required option when checking rating validity');
 985          }
 986          if (!isset($params['rateduserid'])) {
 987              throw new coding_exception('The rateduserid option is now a required option when checking rating validity');
 988          }
 989  
 990          list($plugintype, $pluginname) = core_component::normalize_component($params['component']);
 991  
 992          // This looks for a function like forum_rating_validate() in mod_forum lib.php
 993          // wrapping the params array in another array as call_user_func_array() expands arrays into multiple arguments.
 994          $isvalid = plugin_callback($plugintype, $pluginname, 'rating', 'validate', array($params), null);
 995  
 996          // If null then the callback does not exist.
 997          if ($isvalid === null) {
 998              $isvalid = false;
 999              debugging('rating validation callback not found for component '.  clean_param($component, PARAM_ALPHANUMEXT));
1000          }
1001          return $isvalid;
1002      }
1003  
1004      /**
1005       * Initialises JavaScript to enable AJAX ratings on the provided page
1006       *
1007       * @param moodle_page $page
1008       * @return true always returns true
1009       */
1010      public function initialise_rating_javascript(moodle_page $page) {
1011          global $CFG;
1012  
1013          // Only needs to be initialized once.
1014          static $done = false;
1015          if ($done) {
1016              return true;
1017          }
1018  
1019          $page->requires->js_init_call('M.core_rating.init');
1020          $done = true;
1021  
1022          return true;
1023      }
1024  
1025      /**
1026       * Returns a string that describes the aggregation method that was provided.
1027       *
1028       * @param string $aggregationmethod
1029       * @return string describes the aggregation method that was provided
1030       */
1031      public function get_aggregate_label($aggregationmethod) {
1032          $aggregatelabel = '';
1033          switch ($aggregationmethod) {
1034              case RATING_AGGREGATE_AVERAGE :
1035                  $aggregatelabel .= get_string("aggregateavg", "rating");
1036                  break;
1037              case RATING_AGGREGATE_COUNT :
1038                  $aggregatelabel .= get_string("aggregatecount", "rating");
1039                  break;
1040              case RATING_AGGREGATE_MAXIMUM :
1041                  $aggregatelabel .= get_string("aggregatemax", "rating");
1042                  break;
1043              case RATING_AGGREGATE_MINIMUM :
1044                  $aggregatelabel .= get_string("aggregatemin", "rating");
1045                  break;
1046              case RATING_AGGREGATE_SUM :
1047                  $aggregatelabel .= get_string("aggregatesum", "rating");
1048                  break;
1049          }
1050          $aggregatelabel .= get_string('labelsep', 'langconfig');
1051          return $aggregatelabel;
1052      }
1053  
1054      /**
1055       * Adds a new rating
1056       *
1057       * @param stdClass $cm course module object
1058       * @param stdClass $context context object
1059       * @param string $component component name
1060       * @param string $ratingarea rating area
1061       * @param int $itemid the item id
1062       * @param int $scaleid the scale id
1063       * @param int $userrating the user rating
1064       * @param int $rateduserid the rated user id
1065       * @param int $aggregationmethod the aggregation method
1066       * @since Moodle 3.2
1067       */
1068      public function add_rating($cm, $context, $component, $ratingarea, $itemid, $scaleid, $userrating, $rateduserid,
1069                                  $aggregationmethod) {
1070          global $CFG, $DB, $USER;
1071  
1072          $result = new stdClass;
1073          // Check the module rating permissions.
1074          // Doing this check here rather than within rating_manager::get_ratings() so we can return a error response.
1075          $pluginpermissionsarray = $this->get_plugin_permissions_array($context->id, $component, $ratingarea);
1076  
1077          if (!$pluginpermissionsarray['rate']) {
1078              $result->error = 'ratepermissiondenied';
1079              return $result;
1080          } else {
1081              $params = array(
1082                  'context'     => $context,
1083                  'component'   => $component,
1084                  'ratingarea'  => $ratingarea,
1085                  'itemid'      => $itemid,
1086                  'scaleid'     => $scaleid,
1087                  'rating'      => $userrating,
1088                  'rateduserid' => $rateduserid,
1089                  'aggregation' => $aggregationmethod
1090              );
1091              if (!$this->check_rating_is_valid($params)) {
1092                  $result->error = 'ratinginvalid';
1093                  return $result;
1094              }
1095          }
1096  
1097          // Rating options used to update the rating then retrieve the aggregate.
1098          $ratingoptions = new stdClass;
1099          $ratingoptions->context = $context;
1100          $ratingoptions->ratingarea = $ratingarea;
1101          $ratingoptions->component = $component;
1102          $ratingoptions->itemid  = $itemid;
1103          $ratingoptions->scaleid = $scaleid;
1104          $ratingoptions->userid  = $USER->id;
1105  
1106          if ($userrating != RATING_UNSET_RATING) {
1107              $rating = new rating($ratingoptions);
1108              $rating->update_rating($userrating);
1109          } else { // Delete the rating if the user set to "Rate..."
1110              $options = new stdClass;
1111              $options->contextid = $context->id;
1112              $options->component = $component;
1113              $options->ratingarea = $ratingarea;
1114              $options->userid = $USER->id;
1115              $options->itemid = $itemid;
1116  
1117              $this->delete_ratings($options);
1118          }
1119  
1120          // Future possible enhancement: add a setting to turn grade updating off for those who don't want them in gradebook.
1121          // Note that this would need to be done in both rate.php and rate_ajax.php.
1122          if ($context->contextlevel == CONTEXT_MODULE) {
1123              // Tell the module that its grades have changed.
1124              $modinstance = $DB->get_record($cm->modname, array('id' => $cm->instance));
1125              if ($modinstance) {
1126                  $modinstance->cmidnumber = $cm->id; // MDL-12961.
1127                  $functionname = $cm->modname.'_update_grades';
1128                  require_once($CFG->dirroot."/mod/{$cm->modname}/lib.php");
1129                  if (function_exists($functionname)) {
1130                      $functionname($modinstance, $rateduserid);
1131                  }
1132              }
1133          }
1134  
1135          // Object to return to client as JSON.
1136          $result->success = true;
1137  
1138          // Need to retrieve the updated item to get its new aggregate value.
1139          $item = new stdClass;
1140          $item->id = $itemid;
1141  
1142          // Most of $ratingoptions variables were previously set.
1143          $ratingoptions->items = array($item);
1144          $ratingoptions->aggregate = $aggregationmethod;
1145  
1146          $items = $this->get_ratings($ratingoptions);
1147          $firstrating = $items[0]->rating;
1148  
1149          // See if the user has permission to see the rating aggregate.
1150          if ($firstrating->user_can_view_aggregate()) {
1151  
1152              // For custom scales return text not the value.
1153              // This scales weirdness will go away when scales are refactored.
1154              $scalearray = null;
1155              $aggregatetoreturn = round($firstrating->aggregate, 1);
1156  
1157              // Output a dash if aggregation method == COUNT as the count is output next to the aggregate anyway.
1158              if ($firstrating->settings->aggregationmethod == RATING_AGGREGATE_COUNT or $firstrating->count == 0) {
1159                  $aggregatetoreturn = ' - ';
1160              } else if ($firstrating->settings->scale->id < 0) { // If its non-numeric scale.
1161                  // Dont use the scale item if the aggregation method is sum as adding items from a custom scale makes no sense.
1162                  if ($firstrating->settings->aggregationmethod != RATING_AGGREGATE_SUM) {
1163                      $scalerecord = $DB->get_record('scale', array('id' => -$firstrating->settings->scale->id));
1164                      if ($scalerecord) {
1165                          $scalearray = explode(',', $scalerecord->scale);
1166                          $aggregatetoreturn = $scalearray[$aggregatetoreturn - 1];
1167                      }
1168                  }
1169              }
1170  
1171              $result->aggregate = $aggregatetoreturn;
1172              $result->count = $firstrating->count;
1173              $result->itemid = $itemid;
1174          }
1175          return $result;
1176      }
1177  
1178      /**
1179       * Get ratings created since a given time.
1180       *
1181       * @param  stdClass $context   context object
1182       * @param  string $component  component name
1183       * @param  int $since         the time to check
1184       * @return array list of ratings db records since the given timelimit
1185       * @since Moodle 3.2
1186       */
1187      public function get_component_ratings_since($context, $component, $since) {
1188          global $DB, $USER;
1189  
1190          $ratingssince = array();
1191          $where = 'contextid = ? AND component = ? AND (timecreated > ? OR timemodified > ?)';
1192          $ratings = $DB->get_records_select('rating', $where, array($context->id, $component, $since, $since));
1193          // Check area by area if we have permissions.
1194          $permissions = array();
1195          $rm = new rating_manager();
1196  
1197          foreach ($ratings as $rating) {
1198              // Check if the permission array for the area is cached.
1199              if (!isset($permissions[$rating->ratingarea])) {
1200                  $permissions[$rating->ratingarea] = $rm->get_plugin_permissions_array($context->id, $component,
1201                                                                                          $rating->ratingarea);
1202              }
1203  
1204              if (($permissions[$rating->ratingarea]['view'] and $rating->userid == $USER->id) or
1205                      ($permissions[$rating->ratingarea]['viewany'] or $permissions[$rating->ratingarea]['viewall'])) {
1206                  $ratingssince[$rating->id] = $rating;
1207              }
1208          }
1209          return $ratingssince;
1210      }
1211  } // End rating_manager class definition.
1212  
1213  /**
1214   * The rating_exception class for exceptions specific to the ratings system
1215   *
1216   * @package   core_rating
1217   * @category  rating
1218   * @copyright 2010 Andrew Davis
1219   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1220   * @since     Moodle 2.0
1221   */
1222  class rating_exception extends moodle_exception {
1223      /**
1224       * @var string The message to accompany the thrown exception
1225       */
1226      public $message;
1227      /**
1228       * Generate exceptions that can be easily identified as coming from the ratings system
1229       *
1230       * @param string $errorcode the error code to generate
1231       */
1232      public function __construct($errorcode) {
1233          $this->errorcode = $errorcode;
1234          $this->message = get_string($errorcode, 'error');
1235      }
1236  }