Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [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   * Definition of grade scale class
  19   *
  20   * @package   core_grades
  21   * @category  grade
  22   * @copyright 2006 Nicolas Connault
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once ('grade_object.php');
  29  
  30  /**
  31   * Class representing a grade scale.
  32   *
  33   * It is responsible for handling its DB representation, modifying and returning its metadata.
  34   *
  35   * @package   core_grades
  36   * @category  grade
  37   * @copyright 2006 Nicolas Connault
  38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class grade_scale extends grade_object {
  41      /**
  42       * DB Table (used by grade_object).
  43       * @var string $table
  44       */
  45      public $table = 'scale';
  46  
  47      /**
  48       * Array of required table fields, must start with 'id'.
  49       * @var array $required_fields
  50       */
  51      public $required_fields = array('id', 'courseid', 'userid', 'name', 'scale', 'description', 'descriptionformat', 'timemodified');
  52  
  53      /**
  54       * The course this scale belongs to.
  55       * @var int $courseid
  56       */
  57      public $courseid;
  58  
  59      /**
  60       * The ID of the user who created the scale
  61       * @var int $userid
  62       */
  63      public $userid;
  64  
  65      /**
  66       * The name of the scale.
  67       * @var string $name
  68       */
  69      public $name;
  70  
  71      /**
  72       * The items in this scale.
  73       * @var array $scale_items
  74       */
  75      public $scale_items = array();
  76  
  77      /**
  78       * A string representation of the scale items (a comma-separated list).
  79       * @var string $scale
  80       */
  81      public $scale;
  82  
  83      /**
  84       * A description for this scale.
  85       * @var string $description
  86       */
  87      public $description;
  88  
  89      /**
  90       * Standard event.
  91       * @var bool $standard
  92       */
  93      public $standard;
  94  
  95      /**
  96       * Identifier of the text format to be used.
  97       * @var int $descriptionformat
  98       */
  99      public int $descriptionformat;
 100  
 101      /**
 102       * Finds and returns a grade_scale instance based on params.
 103       *
 104       * @static
 105       * @param array $params associative arrays varname=>value
 106       * @return object grade_scale instance or false if none found.
 107       */
 108      public static function fetch($params) {
 109          return grade_object::fetch_helper('scale', 'grade_scale', $params);
 110      }
 111  
 112      /**
 113       * Finds and returns all grade_scale instances based on params.
 114       *
 115       * @static
 116       * @param array $params associative arrays varname=>value
 117       * @return array array of grade_scale instances or false if none found.
 118       */
 119      public static function fetch_all($params) {
 120          return grade_object::fetch_all_helper('scale', 'grade_scale', $params);
 121      }
 122  
 123      /**
 124       * Records this object in the Database, sets its id to the returned value, and returns that value.
 125       * If successful this function also fetches the new object data from database and stores it
 126       * in object properties.
 127       *
 128       * @param string $source from where was the object inserted (mod/forum, manual, etc.)
 129       * @param bool $isbulkupdate If bulk grade update is happening.
 130       * @return int PK ID if successful, false otherwise
 131       */
 132      public function insert($source = null, $isbulkupdate = false) {
 133          $this->timecreated = time();
 134          $this->timemodified = time();
 135  
 136          $result = parent::insert($source);
 137          if ($result) {
 138              // Trigger the scale created event.
 139              if (!empty($this->standard)) {
 140                  $eventcontext = context_system::instance();
 141              } else {
 142                  if (!empty($this->courseid)) {
 143                      $eventcontext = context_course::instance($this->courseid);
 144                  } else {
 145                      $eventcontext = context_system::instance();
 146                  }
 147              }
 148              $event = \core\event\scale_created::create(array(
 149                  'objectid' => $result,
 150                  'context' => $eventcontext
 151              ));
 152              $event->trigger();
 153          }
 154          return $result;
 155      }
 156  
 157      /**
 158       * In addition to update() it also updates grade_outcomes_courses if needed
 159       *
 160       * @param string $source from where was the object inserted
 161       * @param bool $isbulkupdate If bulk grade update is happening.
 162       * @return bool success
 163       */
 164      public function update($source = null, $isbulkupdate = false) {
 165          $this->timemodified = time();
 166  
 167          $result = parent::update($source);
 168          if ($result) {
 169              // Trigger the scale updated event.
 170              if (!empty($this->standard)) {
 171                  $eventcontext = context_system::instance();
 172              } else {
 173                  if (!empty($this->courseid)) {
 174                      $eventcontext = context_course::instance($this->courseid);
 175                  } else {
 176                      $eventcontext = context_system::instance();
 177                  }
 178              }
 179              $event = \core\event\scale_updated::create(array(
 180                  'objectid' => $this->id,
 181                  'context' => $eventcontext
 182              ));
 183              $event->trigger();
 184          }
 185          return $result;
 186      }
 187  
 188      /**
 189       * Deletes this scale from the database.
 190       *
 191       * @param string $source from where was the object deleted (mod/forum, manual, etc.)
 192       * @return bool success
 193       */
 194      public function delete($source=null) {
 195          global $DB;
 196  
 197          // Trigger the scale deleted event.
 198          if (!empty($this->standard)) {
 199              $eventcontext = context_system::instance();
 200          } else {
 201              if (!empty($this->courseid)) {
 202                  $eventcontext = context_course::instance($this->courseid);
 203              } else {
 204                  $eventcontext = context_system::instance();
 205              }
 206          }
 207          $event = \core\event\scale_deleted::create(array(
 208              'objectid' => $this->id,
 209              'context' => $eventcontext
 210          ));
 211          $event->trigger();
 212          if (parent::delete($source)) {
 213              $context = context_system::instance();
 214              $fs = get_file_storage();
 215              $files = $fs->get_area_files($context->id, 'grade', 'scale', $this->id);
 216              foreach ($files as $file) {
 217                  $file->delete();
 218              }
 219              return true;
 220          }
 221          return false;
 222      }
 223  
 224      /**
 225       * Returns the most descriptive field for this object. This is a standard method used
 226       * when we do not know the exact type of an object.
 227       *
 228       * @return string name
 229       */
 230      public function get_name() {
 231          // Grade scales can be created at site or course context, so set the filter context appropriately.
 232          $context = empty($this->courseid) ? context_system::instance() : context_course::instance($this->courseid);
 233          return format_string($this->name, false, ['context' => $context]);
 234      }
 235  
 236      /**
 237       * Loads the scale's items into the $scale_items array.
 238       * There are three ways to achieve this:
 239       * 1. No argument given: The $scale string is already loaded and exploded to an array of items.
 240       * 2. A string is given: A comma-separated list of items is exploded into an array of items.
 241       * 3. An array of items is given and saved directly as the array of items for this scale.
 242       *
 243       * @param mixed $items Could be null, a string or an array. The method behaves differently for each case.
 244       * @return array The resulting array of scale items or null if the method failed to produce one.
 245       */
 246      public function load_items($items=NULL) {
 247          if (empty($items)) {
 248              $this->scale_items = explode(',', $this->scale);
 249          } elseif (is_array($items)) {
 250              $this->scale_items = $items;
 251          } else {
 252              $this->scale_items = explode(',', $items);
 253          }
 254  
 255          // Trim whitespace around each value
 256          foreach ($this->scale_items as $key => $val) {
 257              $this->scale_items[$key] = trim($val);
 258          }
 259  
 260          return $this->scale_items;
 261      }
 262  
 263      /**
 264       * Compacts (implodes) the array of items in $scale_items into a comma-separated string, $scale.
 265       * There are three ways to achieve this:
 266       * 1. No argument given: The $scale_items array is already loaded and imploded to a string of items.
 267       * 2. An array is given and is imploded into a string of items.
 268       * 3. A string of items is given and saved directly as the $scale variable.
 269       * NOTE: This method is the exact reverse of load_items, and their input/output should be interchangeable. However,
 270       * because load_items() trims the whitespace around the items, when the string is reconstructed these whitespaces will
 271       * be missing. This is not an issue, but should be kept in mind when comparing the two strings.
 272       *
 273       * @param mixed $items Could be null, a string or an array. The method behaves differently for each case.
 274       * @return array The resulting string of scale items or null if the method failed to produce one.
 275       */
 276      public function compact_items($items=NULL) {
 277          if (empty($items)) {
 278              $this->scale = implode(',', $this->scale_items);
 279          } elseif (is_array($items)) {
 280              $this->scale = implode(',', $items);
 281          } else {
 282              $this->scale = $items;
 283          }
 284  
 285          return $this->scale;
 286      }
 287  
 288      /**
 289       * When called on a loaded scale object (with a valid id) and given a float grade between
 290       * the grademin and grademax, this method returns the scale item that falls closest to the
 291       * float given (which is usually an average of several grades on a scale). If the float falls
 292       * below 1 but above 0, it will be rounded up to 1.
 293       *
 294       * @param float $grade
 295       * @return string
 296       */
 297      public function get_nearest_item($grade) {
 298          global $DB;
 299          // Obtain nearest scale item from average
 300          $scales_array = $DB->get_records('scale', array('id' => $this->id));
 301          $scale = $scales_array[$this->id];
 302          $scales = explode(",", $scale->scale);
 303  
 304          // this could be a 0 when summed and rounded, e.g, 1, no grade, no grade, no grade
 305          if ($grade < 1) {
 306              $grade = 1;
 307          }
 308  
 309          return $scales[$grade-1];
 310      }
 311  
 312      /**
 313       * Static function returning all global scales
 314       *
 315       * @return object
 316       */
 317      public static function fetch_all_global() {
 318          return grade_scale::fetch_all(array('courseid'=>0));
 319      }
 320  
 321      /**
 322       * Static function returning all local course scales
 323       *
 324       * @param int $courseid The course ID
 325       * @return array Returns an array of grade_scale instances
 326       */
 327      public static function fetch_all_local($courseid) {
 328          return grade_scale::fetch_all(array('courseid'=>$courseid));
 329      }
 330  
 331      /**
 332       * Checks if this is the last scale on the site.
 333       *
 334       * @return bool
 335       */
 336      public function is_last_global_scale() {
 337          return ($this->courseid == 0) && (count(self::fetch_all_global()) == 1);
 338      }
 339  
 340      /**
 341       * Checks if scale can be deleted.
 342       *
 343       * @return bool
 344       */
 345      public function can_delete() {
 346          return !$this->is_used() && !$this->is_last_global_scale();
 347      }
 348  
 349      /**
 350       * Returns if scale used anywhere - activities, grade items, outcomes, etc.
 351       *
 352       * @return bool
 353       */
 354      public function is_used() {
 355          global $DB;
 356          global $CFG;
 357  
 358          // count grade items excluding the
 359          $params = array($this->id);
 360          $sql = "SELECT COUNT(id) FROM {grade_items} WHERE scaleid = ? AND outcomeid IS NULL";
 361          if ($DB->count_records_sql($sql, $params)) {
 362              return true;
 363          }
 364  
 365          // count outcomes
 366          $sql = "SELECT COUNT(id) FROM {grade_outcomes} WHERE scaleid = ?";
 367          if ($DB->count_records_sql($sql, $params)) {
 368              return true;
 369          }
 370  
 371          // Ask the competency subsystem.
 372          if (\core_competency\api::is_scale_used_anywhere($this->id)) {
 373              return true;
 374          }
 375  
 376          // Ask all plugins if the scale is used anywhere.
 377          $pluginsfunction = get_plugins_with_function('scale_used_anywhere');
 378          foreach ($pluginsfunction as $plugintype => $plugins) {
 379              foreach ($plugins as $pluginfunction) {
 380                  if ($pluginfunction($this->id)) {
 381                      return true;
 382                  }
 383              }
 384          }
 385  
 386          return false;
 387      }
 388  
 389      /**
 390       * Returns the formatted grade description with URLs converted
 391       *
 392       * @return string
 393       */
 394      public function get_description() {
 395          global $CFG;
 396          require_once($CFG->libdir . '/filelib.php');
 397  
 398          $systemcontext = context_system::instance();
 399          $options = new stdClass;
 400          $options->noclean = true;
 401          $description = file_rewrite_pluginfile_urls($this->description, 'pluginfile.php', $systemcontext->id, 'grade', 'scale', $this->id);
 402          return format_text($description, $this->descriptionformat, $options);
 403      }
 404  }