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.

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