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 outcome 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 outcome.
  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_outcome extends grade_object {
  41      /**
  42       * DB Table (used by grade_object).
  43       * @var string $table
  44       */
  45      public $table = 'grade_outcomes';
  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', 'shortname', 'fullname', 'scaleid','description',
  52                                   'descriptionformat', 'timecreated', 'timemodified', 'usermodified');
  53  
  54      /**
  55       * The course this outcome belongs to.
  56       * @var int $courseid
  57       */
  58      public $courseid;
  59  
  60      /**
  61       * The shortname of the outcome.
  62       * @var string $shortname
  63       */
  64      public $shortname;
  65  
  66      /**
  67       * The fullname of the outcome.
  68       * @var string $fullname
  69       */
  70      public $fullname;
  71  
  72      /**
  73       * A full grade_scale object referenced by $this->scaleid.
  74       * @var object $scale
  75       */
  76      public $scale;
  77  
  78      /**
  79       * The id of the scale referenced by this outcome.
  80       * @var int $scaleid
  81       */
  82      public $scaleid;
  83  
  84      /**
  85       * The description of this outcome - FORMAT_MOODLE.
  86       * @var string $description
  87       */
  88      public $description;
  89  
  90      /**
  91       * The userid of the person who last modified this outcome.
  92       *
  93       * @var int $usermodified
  94       */
  95      public $usermodified;
  96  
  97      /**
  98       * Deletes this outcome from the database.
  99       *
 100       * @param string $source from where was the object deleted (mod/forum, manual, etc.)
 101       * @return bool success
 102       */
 103      public function delete($source=null) {
 104          global $DB;
 105          if (!empty($this->courseid)) {
 106              $DB->delete_records('grade_outcomes_courses', array('outcomeid' => $this->id, 'courseid' => $this->courseid));
 107          }
 108          if (parent::delete($source)) {
 109              $context = context_system::instance();
 110              $fs = get_file_storage();
 111              $files = $fs->get_area_files($context->id, 'grade', 'outcome', $this->id);
 112              foreach ($files as $file) {
 113                  $file->delete();
 114              }
 115              return true;
 116          }
 117          return false;
 118      }
 119  
 120      /**
 121       * Records this object in the Database, sets its id to the returned value, and returns that value.
 122       * If successful this function also fetches the new object data from database and stores it
 123       * in object properties.
 124       *
 125       * @param string $source from where was the object inserted (mod/forum, manual, etc.)
 126       * @param bool $isbulkupdate If bulk grade update is happening.
 127       * @return int PK ID if successful, false otherwise
 128       */
 129      public function insert($source = null, $isbulkupdate = false) {
 130          global $DB;
 131  
 132          $this->timecreated = $this->timemodified = time();
 133  
 134          if ($result = parent::insert($source)) {
 135              if (!empty($this->courseid)) {
 136                  $goc = new stdClass();
 137                  $goc->courseid = $this->courseid;
 138                  $goc->outcomeid = $this->id;
 139                  $DB->insert_record('grade_outcomes_courses', $goc);
 140              }
 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          if ($result = parent::update($source)) {
 156              if (!empty($this->courseid)) {
 157                  $this->use_in($this->courseid);
 158              }
 159          }
 160          return $result;
 161      }
 162  
 163      /**
 164       * Mark outcome as used in a course
 165       *
 166       * @param int $courseid
 167       * @return False if invalid courseid requested
 168       */
 169      public function use_in($courseid) {
 170          global $DB;
 171          if (!empty($this->courseid) and $courseid != $this->courseid) {
 172              return false;
 173          }
 174  
 175          if (!$DB->record_exists('grade_outcomes_courses', array('courseid' => $courseid, 'outcomeid' => $this->id))) {
 176              $goc = new stdClass();
 177              $goc->courseid  = $courseid;
 178              $goc->outcomeid = $this->id;
 179              $DB->insert_record('grade_outcomes_courses', $goc);
 180          }
 181          return true;
 182      }
 183  
 184      /**
 185       * Finds and returns a grade_outcome instance based on params.
 186       *
 187       * @static
 188       * @param array $params associative arrays varname=>value
 189       * @return object grade_outcome instance or false if none found.
 190       */
 191      public static function fetch($params) {
 192          return grade_object::fetch_helper('grade_outcomes', 'grade_outcome', $params);
 193      }
 194  
 195      /**
 196       * Finds and returns all grade_outcome instances based on params.
 197       *
 198       * @static
 199       * @param array $params associative arrays varname=>value
 200       * @return array array of grade_outcome insatnces or false if none found.
 201       */
 202      public static function fetch_all($params) {
 203          return grade_object::fetch_all_helper('grade_outcomes', 'grade_outcome', $params);
 204      }
 205  
 206      /**
 207       * Instantiates a grade_scale object whose data is retrieved from the database
 208       *
 209       * @return grade_scale
 210       */
 211      public function load_scale() {
 212          if (empty($this->scale->id) or $this->scale->id != $this->scaleid) {
 213              $this->scale = grade_scale::fetch(array('id'=>$this->scaleid));
 214              $this->scale->load_items();
 215          }
 216          return $this->scale;
 217      }
 218  
 219      /**
 220       * Static function returning all global outcomes
 221       *
 222       * @static
 223       * @return array
 224       */
 225      public static function fetch_all_global() {
 226          if (!$outcomes = grade_outcome::fetch_all(array('courseid'=>null))) {
 227              $outcomes = array();
 228          }
 229          return $outcomes;
 230      }
 231  
 232      /**
 233       * Static function returning all local course outcomes
 234       *
 235       * @static
 236       * @param int $courseid
 237       * @return array
 238       */
 239      public static function fetch_all_local($courseid) {
 240          if (!$outcomes =grade_outcome::fetch_all(array('courseid'=>$courseid))) {
 241              $outcomes = array();
 242          }
 243          return $outcomes;
 244      }
 245  
 246      /**
 247       * Static method that returns all outcomes available in course
 248       *
 249       * @static
 250       * @param int $courseid
 251       * @return array
 252       */
 253      public static function fetch_all_available($courseid) {
 254          global $CFG, $DB;
 255  
 256          $result = array();
 257          $params = array($courseid);
 258          $sql = "SELECT go.*
 259                    FROM {grade_outcomes} go, {grade_outcomes_courses} goc
 260                   WHERE go.id = goc.outcomeid AND goc.courseid = ?
 261                ORDER BY go.id ASC";
 262  
 263          if ($datas = $DB->get_records_sql($sql, $params)) {
 264              foreach($datas as $data) {
 265                  $instance = new grade_outcome();
 266                  grade_object::set_properties($instance, $data);
 267                  $result[$instance->id] = $instance;
 268              }
 269          }
 270          return $result;
 271      }
 272  
 273  
 274      /**
 275       * Returns the most descriptive field for this object. This is a standard method used
 276       * when we do not know the exact type of an object.
 277       *
 278       * @return string name
 279       */
 280      public function get_name() {
 281          // Grade outcomes can be created at site or course context, so set the filter context appropriately.
 282          $context = empty($this->courseid) ? context_system::instance() : context_course::instance($this->courseid);
 283          return format_string($this->fullname, false, ["context" => $context]);
 284      }
 285  
 286      /**
 287       * Returns unique outcome short name.
 288       *
 289       * @return string name
 290       */
 291      public function get_shortname() {
 292          return $this->shortname;
 293      }
 294  
 295      /**
 296       * Returns the formatted grade description with URLs converted
 297       *
 298       * @return string
 299       */
 300      public function get_description() {
 301          global $CFG;
 302          require_once($CFG->libdir . '/filelib.php');
 303  
 304          $options = new stdClass;
 305          $options->noclean = true;
 306          $systemcontext = context_system::instance();
 307          $description = file_rewrite_pluginfile_urls($this->description, 'pluginfile.php', $systemcontext->id, 'grade', 'outcome', $this->id);
 308          return format_text($description, $this->descriptionformat, $options);
 309      }
 310  
 311      /**
 312       * Checks if outcome can be deleted.
 313       *
 314       * @return bool
 315       */
 316      public function can_delete() {
 317          if ($this->get_item_uses_count()) {
 318              return false;
 319          }
 320          if (empty($this->courseid)) {
 321              if ($this->get_course_uses_count()) {
 322                  return false;
 323              }
 324          }
 325          return true;
 326      }
 327  
 328      /**
 329       * Returns the number of places where outcome is used.
 330       *
 331       * @return int
 332       */
 333      public function get_course_uses_count() {
 334          global $DB;
 335  
 336          if (!empty($this->courseid)) {
 337              return 1;
 338          }
 339  
 340          return $DB->count_records('grade_outcomes_courses', array('outcomeid' => $this->id));
 341      }
 342  
 343      /**
 344       * Returns the number of grade items that use this grade outcome
 345       *
 346       * @return int
 347       */
 348      public function get_item_uses_count() {
 349          global $DB;
 350          return $DB->count_records('grade_items', array('outcomeid' => $this->id));
 351      }
 352  
 353      /**
 354       * Computes then returns extra information about this outcome and other objects that are linked to it.
 355       * The average of all grades that use this outcome, for all courses (or 1 course if courseid is given) can
 356       * be requested, and is returned as a float if requested alone. If the list of items that use this outcome
 357       * is also requested, then a single array is returned, which contains the grade_items AND the average grade
 358       * if such is still requested (array('items' => array(...), 'avg' => 2.30)). This combining of two
 359       * methods into one is to save on DB queries, since both queries are similar and can be performed together.
 360       *
 361       * @param int $courseid An optional courseid to narrow down the average to 1 course only
 362       * @param bool $average Whether or not to return the average grade for this outcome
 363       * @param bool $items Whether or not to return the list of items using this outcome
 364       * @return float
 365       */
 366      public function get_grade_info($courseid=null, $average=true, $items=false) {
 367          global $CFG, $DB;
 368  
 369          if (!isset($this->id)) {
 370              debugging("You must setup the outcome's id before calling its get_grade_info() method!");
 371              return false; // id must be defined for this to work
 372          }
 373  
 374          if ($average === false && $items === false) {
 375              debugging('Either the 1st or 2nd param of grade_outcome::get_grade_info() must be true, or both, but not both false!');
 376              return false;
 377          }
 378  
 379          $params = array($this->id);
 380  
 381          $wheresql = '';
 382          if (!is_null($courseid)) {
 383              $wheresql = " AND {grade_items}.courseid = ? ";
 384              $params[] = $courseid;
 385          }
 386  
 387          $selectadd = '';
 388          if ($items !== false) {
 389              $selectadd = ", {grade_items}.* ";
 390          }
 391  
 392          $sql = "SELECT finalgrade $selectadd
 393                    FROM {grade_grades}, {grade_items}, {grade_outcomes}
 394                   WHERE {grade_outcomes}.id = {grade_items}.outcomeid
 395                     AND {grade_items}.id = {grade_grades}.itemid
 396                     AND {grade_outcomes}.id = ?
 397                     $wheresql";
 398  
 399          $grades = $DB->get_records_sql($sql, $params);
 400          $retval = array();
 401  
 402          if ($average !== false && count($grades) > 0) {
 403              $count = 0;
 404              $total = 0;
 405  
 406              foreach ($grades as $k => $grade) {
 407                  // Skip null finalgrades
 408                  if (!is_null($grade->finalgrade)) {
 409                      $total += $grade->finalgrade;
 410                      $count++;
 411                  }
 412                  unset($grades[$k]->finalgrade);
 413              }
 414  
 415              $retval['avg'] = $total / $count;
 416          }
 417  
 418          if ($items !== false) {
 419              foreach ($grades as $grade) {
 420                  $retval['items'][$grade->id] = new grade_item($grade);
 421              }
 422          }
 423  
 424          return $retval;
 425      }
 426  }