| 1 | efrain | 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 a class to represent a grade category
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * @package   core_grades
 | 
        
           |  |  | 21 |  * @copyright 2006 Nicolas Connault
 | 
        
           |  |  | 22 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 |   | 
        
           |  |  | 25 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | require_once(__DIR__ . '/grade_object.php');
 | 
        
           |  |  | 28 |   | 
        
           |  |  | 29 | /**
 | 
        
           |  |  | 30 |  * grade_category is an object mapped to DB table {prefix}grade_categories
 | 
        
           |  |  | 31 |  *
 | 
        
           |  |  | 32 |  * @package   core_grades
 | 
        
           |  |  | 33 |  * @category  grade
 | 
        
           |  |  | 34 |  * @copyright 2007 Nicolas Connault
 | 
        
           |  |  | 35 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 36 |  */
 | 
        
           |  |  | 37 | class grade_category extends grade_object {
 | 
        
           |  |  | 38 |     /**
 | 
        
           |  |  | 39 |      * The DB table.
 | 
        
           |  |  | 40 |      * @var string $table
 | 
        
           |  |  | 41 |      */
 | 
        
           |  |  | 42 |     public $table = 'grade_categories';
 | 
        
           |  |  | 43 |   | 
        
           |  |  | 44 |     /**
 | 
        
           |  |  | 45 |      * Array of required table fields, must start with 'id'.
 | 
        
           |  |  | 46 |      * @var array $required_fields
 | 
        
           |  |  | 47 |      */
 | 
        
           |  |  | 48 |     public $required_fields = array('id', 'courseid', 'parent', 'depth', 'path', 'fullname', 'aggregation',
 | 
        
           |  |  | 49 |                                  'keephigh', 'droplow', 'aggregateonlygraded', 'aggregateoutcomes',
 | 
        
           |  |  | 50 |                                  'timecreated', 'timemodified', 'hidden');
 | 
        
           |  |  | 51 |   | 
        
           |  |  | 52 |     /**
 | 
        
           |  |  | 53 |      * The course this category belongs to.
 | 
        
           |  |  | 54 |      * @var int $courseid
 | 
        
           |  |  | 55 |      */
 | 
        
           |  |  | 56 |     public $courseid;
 | 
        
           |  |  | 57 |   | 
        
           |  |  | 58 |     /**
 | 
        
           |  |  | 59 |      * The category this category belongs to (optional).
 | 
        
           |  |  | 60 |      * @var int $parent
 | 
        
           |  |  | 61 |      */
 | 
        
           |  |  | 62 |     public $parent;
 | 
        
           |  |  | 63 |   | 
        
           |  |  | 64 |     /**
 | 
        
           |  |  | 65 |      * The grade_category object referenced by $this->parent (PK).
 | 
        
           |  |  | 66 |      * @var grade_category $parent_category
 | 
        
           |  |  | 67 |      */
 | 
        
           |  |  | 68 |     public $parent_category;
 | 
        
           |  |  | 69 |   | 
        
           |  |  | 70 |     /**
 | 
        
           |  |  | 71 |      * The number of parents this category has.
 | 
        
           |  |  | 72 |      * @var int $depth
 | 
        
           |  |  | 73 |      */
 | 
        
           |  |  | 74 |     public $depth = 0;
 | 
        
           |  |  | 75 |   | 
        
           |  |  | 76 |     /**
 | 
        
           |  |  | 77 |      * Shows the hierarchical path for this category as /1/2/3/ (like course_categories), the last number being
 | 
        
           |  |  | 78 |      * this category's autoincrement ID number.
 | 
        
           |  |  | 79 |      * @var string $path
 | 
        
           |  |  | 80 |      */
 | 
        
           |  |  | 81 |     public $path;
 | 
        
           |  |  | 82 |   | 
        
           |  |  | 83 |     /**
 | 
        
           |  |  | 84 |      * The name of this category.
 | 
        
           |  |  | 85 |      * @var string $fullname
 | 
        
           |  |  | 86 |      */
 | 
        
           |  |  | 87 |     public $fullname;
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |     /**
 | 
        
           |  |  | 90 |      * A constant pointing to one of the predefined aggregation strategies (none, mean, median, sum etc) .
 | 
        
           |  |  | 91 |      * @var int $aggregation
 | 
        
           |  |  | 92 |      */
 | 
        
           |  |  | 93 |     public $aggregation = GRADE_AGGREGATE_SUM;
 | 
        
           |  |  | 94 |   | 
        
           |  |  | 95 |     /**
 | 
        
           |  |  | 96 |      * Keep only the X highest items.
 | 
        
           |  |  | 97 |      * @var int $keephigh
 | 
        
           |  |  | 98 |      */
 | 
        
           |  |  | 99 |     public $keephigh = 0;
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |     /**
 | 
        
           |  |  | 102 |      * Drop the X lowest items.
 | 
        
           |  |  | 103 |      * @var int $droplow
 | 
        
           |  |  | 104 |      */
 | 
        
           |  |  | 105 |     public $droplow = 0;
 | 
        
           |  |  | 106 |   | 
        
           |  |  | 107 |     /**
 | 
        
           |  |  | 108 |      * Aggregate only graded items
 | 
        
           |  |  | 109 |      * @var int $aggregateonlygraded
 | 
        
           |  |  | 110 |      */
 | 
        
           |  |  | 111 |     public $aggregateonlygraded = 0;
 | 
        
           |  |  | 112 |   | 
        
           |  |  | 113 |     /**
 | 
        
           |  |  | 114 |      * Aggregate outcomes together with normal items
 | 
        
           |  |  | 115 |      * @var int $aggregateoutcomes
 | 
        
           |  |  | 116 |      */
 | 
        
           |  |  | 117 |     public $aggregateoutcomes = 0;
 | 
        
           |  |  | 118 |   | 
        
           |  |  | 119 |     /**
 | 
        
           |  |  | 120 |      * Array of grade_items or grade_categories nested exactly 1 level below this category
 | 
        
           |  |  | 121 |      * @var array $children
 | 
        
           |  |  | 122 |      */
 | 
        
           |  |  | 123 |     public $children;
 | 
        
           |  |  | 124 |   | 
        
           |  |  | 125 |     /**
 | 
        
           |  |  | 126 |      * A hierarchical array of all children below this category. This is stored separately from
 | 
        
           |  |  | 127 |      * $children because it is more memory-intensive and may not be used as often.
 | 
        
           |  |  | 128 |      * @var array $all_children
 | 
        
           |  |  | 129 |      */
 | 
        
           |  |  | 130 |     public $all_children;
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |     /**
 | 
        
           |  |  | 133 |      * An associated grade_item object, with itemtype=category, used to calculate and cache a set of grade values
 | 
        
           |  |  | 134 |      * for this category.
 | 
        
           |  |  | 135 |      * @var grade_item $grade_item
 | 
        
           |  |  | 136 |      */
 | 
        
           |  |  | 137 |     public $grade_item;
 | 
        
           |  |  | 138 |   | 
        
           |  |  | 139 |     /**
 | 
        
           |  |  | 140 |      * Temporary sortorder for speedup of children resorting
 | 
        
           |  |  | 141 |      * @var int $sortorder
 | 
        
           |  |  | 142 |      */
 | 
        
           |  |  | 143 |     public $sortorder;
 | 
        
           |  |  | 144 |   | 
        
           |  |  | 145 |     /**
 | 
        
           |  |  | 146 |      * List of options which can be "forced" from site settings.
 | 
        
           |  |  | 147 |      * @var array $forceable
 | 
        
           |  |  | 148 |      */
 | 
        
           |  |  | 149 |     public $forceable = array('aggregation', 'keephigh', 'droplow', 'aggregateonlygraded', 'aggregateoutcomes');
 | 
        
           |  |  | 150 |   | 
        
           |  |  | 151 |     /**
 | 
        
           |  |  | 152 |      * String representing the aggregation coefficient. Variable is used as cache.
 | 
        
           |  |  | 153 |      * @var string $coefstring
 | 
        
           |  |  | 154 |      */
 | 
        
           |  |  | 155 |     public $coefstring = null;
 | 
        
           |  |  | 156 |   | 
        
           |  |  | 157 |     /**
 | 
        
           |  |  | 158 |      * Static variable storing the result from {@link self::can_apply_limit_rules}.
 | 
        
           |  |  | 159 |      * @var bool
 | 
        
           |  |  | 160 |      */
 | 
        
           |  |  | 161 |     protected $canapplylimitrules;
 | 
        
           |  |  | 162 |   | 
        
           |  |  | 163 |     /**
 | 
        
           |  |  | 164 |      * e.g. 'category', 'course' and 'mod', 'blocks', 'import', etc...
 | 
        
           |  |  | 165 |      * @var string $itemtype
 | 
        
           |  |  | 166 |      */
 | 
        
           |  |  | 167 |     public $itemtype;
 | 
        
           |  |  | 168 |   | 
        
           |  |  | 169 |     /**
 | 
        
           |  |  | 170 |      * Builds this category's path string based on its parents (if any) and its own id number.
 | 
        
           |  |  | 171 |      * This is typically done just before inserting this object in the DB for the first time,
 | 
        
           |  |  | 172 |      * or when a new parent is added or changed. It is a recursive function: once the calling
 | 
        
           |  |  | 173 |      * object no longer has a parent, the path is complete.
 | 
        
           |  |  | 174 |      *
 | 
        
           |  |  | 175 |      * @param grade_category $grade_category A Grade_Category object
 | 
        
           |  |  | 176 |      * @return string The category's path string
 | 
        
           |  |  | 177 |      */
 | 
        
           |  |  | 178 |     public static function build_path($grade_category) {
 | 
        
           |  |  | 179 |         global $DB;
 | 
        
           |  |  | 180 |   | 
        
           |  |  | 181 |         if (empty($grade_category->parent)) {
 | 
        
           |  |  | 182 |             return '/'.$grade_category->id.'/';
 | 
        
           |  |  | 183 |   | 
        
           |  |  | 184 |         } else {
 | 
        
           |  |  | 185 |             $parent = $DB->get_record('grade_categories', array('id' => $grade_category->parent));
 | 
        
           |  |  | 186 |             return grade_category::build_path($parent).$grade_category->id.'/';
 | 
        
           |  |  | 187 |         }
 | 
        
           |  |  | 188 |     }
 | 
        
           |  |  | 189 |   | 
        
           |  |  | 190 |     /**
 | 
        
           |  |  | 191 |      * Finds and returns a grade_category instance based on params.
 | 
        
           |  |  | 192 |      *
 | 
        
           |  |  | 193 |      * @param array $params associative arrays varname=>value
 | 
        
           |  |  | 194 |      * @return grade_category The retrieved grade_category instance or false if none found.
 | 
        
           |  |  | 195 |      */
 | 
        
           |  |  | 196 |     public static function fetch($params) {
 | 
        
           |  |  | 197 |         if ($records = self::retrieve_record_set($params)) {
 | 
        
           |  |  | 198 |             return reset($records);
 | 
        
           |  |  | 199 |         }
 | 
        
           |  |  | 200 |   | 
        
           |  |  | 201 |         $record = grade_object::fetch_helper('grade_categories', 'grade_category', $params);
 | 
        
           |  |  | 202 |   | 
        
           |  |  | 203 |         // We store it as an array to keep a key => result set interface in the cache, grade_object::fetch_helper is
 | 
        
           |  |  | 204 |         // managing exceptions. We return only the first element though.
 | 
        
           |  |  | 205 |         $records = false;
 | 
        
           |  |  | 206 |         if ($record) {
 | 
        
           |  |  | 207 |             $records = array($record->id => $record);
 | 
        
           |  |  | 208 |         }
 | 
        
           |  |  | 209 |   | 
        
           |  |  | 210 |         self::set_record_set($params, $records);
 | 
        
           |  |  | 211 |   | 
        
           |  |  | 212 |         return $record;
 | 
        
           |  |  | 213 |     }
 | 
        
           |  |  | 214 |   | 
        
           |  |  | 215 |     /**
 | 
        
           |  |  | 216 |      * Finds and returns all grade_category instances based on params.
 | 
        
           |  |  | 217 |      *
 | 
        
           |  |  | 218 |      * @param array $params associative arrays varname=>value
 | 
        
           |  |  | 219 |      * @return array array of grade_category insatnces or false if none found.
 | 
        
           |  |  | 220 |      */
 | 
        
           |  |  | 221 |     public static function fetch_all($params) {
 | 
        
           |  |  | 222 |         if ($records = self::retrieve_record_set($params)) {
 | 
        
           |  |  | 223 |             return $records;
 | 
        
           |  |  | 224 |         }
 | 
        
           |  |  | 225 |   | 
        
           |  |  | 226 |         $records = grade_object::fetch_all_helper('grade_categories', 'grade_category', $params);
 | 
        
           |  |  | 227 |         self::set_record_set($params, $records);
 | 
        
           |  |  | 228 |   | 
        
           |  |  | 229 |         return $records;
 | 
        
           |  |  | 230 |     }
 | 
        
           |  |  | 231 |   | 
        
           |  |  | 232 |     /**
 | 
        
           |  |  | 233 |      * In addition to update() as defined in grade_object, call force_regrading of parent categories, if applicable.
 | 
        
           |  |  | 234 |      *
 | 
        
           |  |  | 235 |      * @param string $source from where was the object updated (mod/forum, manual, etc.)
 | 
        
           |  |  | 236 |      * @param bool $isbulkupdate If bulk grade update is happening.
 | 
        
           |  |  | 237 |      * @return bool success
 | 
        
           |  |  | 238 |      */
 | 
        
           |  |  | 239 |     public function update($source = null, $isbulkupdate = false) {
 | 
        
           |  |  | 240 |         // load the grade item or create a new one
 | 
        
           |  |  | 241 |         $this->load_grade_item();
 | 
        
           |  |  | 242 |   | 
        
           |  |  | 243 |         // force recalculation of path;
 | 
        
           |  |  | 244 |         if (empty($this->path)) {
 | 
        
           |  |  | 245 |             $this->path  = grade_category::build_path($this);
 | 
        
           |  |  | 246 |             $this->depth = substr_count($this->path, '/') - 1;
 | 
        
           |  |  | 247 |             $updatechildren = true;
 | 
        
           |  |  | 248 |   | 
        
           |  |  | 249 |         } else {
 | 
        
           |  |  | 250 |             $updatechildren = false;
 | 
        
           |  |  | 251 |         }
 | 
        
           |  |  | 252 |   | 
        
           |  |  | 253 |         $this->apply_forced_settings();
 | 
        
           |  |  | 254 |   | 
        
           |  |  | 255 |         // these are exclusive
 | 
        
           |  |  | 256 |         if ($this->droplow > 0) {
 | 
        
           |  |  | 257 |             $this->keephigh = 0;
 | 
        
           |  |  | 258 |   | 
        
           |  |  | 259 |         } else if ($this->keephigh > 0) {
 | 
        
           |  |  | 260 |             $this->droplow = 0;
 | 
        
           |  |  | 261 |         }
 | 
        
           |  |  | 262 |   | 
        
           |  |  | 263 |         // Recalculate grades if needed
 | 
        
           |  |  | 264 |         if ($this->qualifies_for_regrading()) {
 | 
        
           |  |  | 265 |             $this->force_regrading();
 | 
        
           |  |  | 266 |         }
 | 
        
           |  |  | 267 |   | 
        
           |  |  | 268 |         $this->timemodified = time();
 | 
        
           |  |  | 269 |   | 
        
           |  |  | 270 |         $result = parent::update($source);
 | 
        
           |  |  | 271 |   | 
        
           |  |  | 272 |         // now update paths in all child categories
 | 
        
           |  |  | 273 |         if ($result and $updatechildren) {
 | 
        
           |  |  | 274 |   | 
        
           |  |  | 275 |             if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
 | 
        
           |  |  | 276 |   | 
        
           |  |  | 277 |                 foreach ($children as $child) {
 | 
        
           |  |  | 278 |                     $child->path  = null;
 | 
        
           |  |  | 279 |                     $child->depth = 0;
 | 
        
           |  |  | 280 |                     $child->update($source);
 | 
        
           |  |  | 281 |                 }
 | 
        
           |  |  | 282 |             }
 | 
        
           |  |  | 283 |         }
 | 
        
           |  |  | 284 |   | 
        
           |  |  | 285 |         return $result;
 | 
        
           |  |  | 286 |     }
 | 
        
           |  |  | 287 |   | 
        
           |  |  | 288 |     /**
 | 
        
           |  |  | 289 |      * If parent::delete() is successful, send force_regrading message to parent category.
 | 
        
           |  |  | 290 |      *
 | 
        
           |  |  | 291 |      * @param string $source from where was the object deleted (mod/forum, manual, etc.)
 | 
        
           |  |  | 292 |      * @return bool success
 | 
        
           |  |  | 293 |      */
 | 
        
           |  |  | 294 |     public function delete($source=null) {
 | 
        
           |  |  | 295 |         global $DB;
 | 
        
           |  |  | 296 |   | 
        
           |  |  | 297 |         try {
 | 
        
           |  |  | 298 |             $transaction = $DB->start_delegated_transaction();
 | 
        
           |  |  | 299 |             $grade_item = $this->load_grade_item();
 | 
        
           |  |  | 300 |   | 
        
           |  |  | 301 |             if ($this->is_course_category()) {
 | 
        
           |  |  | 302 |   | 
        
           |  |  | 303 |                 if ($categories = self::fetch_all(['courseid' => $this->courseid])) {
 | 
        
           |  |  | 304 |   | 
        
           |  |  | 305 |                     foreach ($categories as $category) {
 | 
        
           |  |  | 306 |   | 
        
           |  |  | 307 |                         if ($category->id == $this->id) {
 | 
        
           |  |  | 308 |                             continue; // Do not delete course category yet.
 | 
        
           |  |  | 309 |                         }
 | 
        
           |  |  | 310 |                         $category->delete($source);
 | 
        
           |  |  | 311 |                     }
 | 
        
           |  |  | 312 |                 }
 | 
        
           |  |  | 313 |   | 
        
           |  |  | 314 |                 if ($items = grade_item::fetch_all(['courseid' => $this->courseid])) {
 | 
        
           |  |  | 315 |   | 
        
           |  |  | 316 |                     foreach ($items as $item) {
 | 
        
           |  |  | 317 |   | 
        
           |  |  | 318 |                         if ($item->id == $grade_item->id) {
 | 
        
           |  |  | 319 |                             continue; // Do not delete course item yet.
 | 
        
           |  |  | 320 |                         }
 | 
        
           |  |  | 321 |                         $item->delete($source);
 | 
        
           |  |  | 322 |                     }
 | 
        
           |  |  | 323 |                 }
 | 
        
           |  |  | 324 |   | 
        
           |  |  | 325 |             } else {
 | 
        
           |  |  | 326 |                 $this->force_regrading();
 | 
        
           |  |  | 327 |   | 
        
           |  |  | 328 |                 $parent = $this->load_parent_category();
 | 
        
           |  |  | 329 |   | 
        
           |  |  | 330 |                 // Update children's categoryid/parent field first.
 | 
        
           |  |  | 331 |                 if ($children = grade_item::fetch_all(['categoryid' => $this->id])) {
 | 
        
           |  |  | 332 |                     foreach ($children as $child) {
 | 
        
           |  |  | 333 |                         $child->set_parent($parent->id);
 | 
        
           |  |  | 334 |                     }
 | 
        
           |  |  | 335 |                 }
 | 
        
           |  |  | 336 |   | 
        
           |  |  | 337 |                 if ($children = self::fetch_all(['parent' => $this->id])) {
 | 
        
           |  |  | 338 |                     foreach ($children as $child) {
 | 
        
           |  |  | 339 |                         $child->set_parent($parent->id);
 | 
        
           |  |  | 340 |                     }
 | 
        
           |  |  | 341 |                 }
 | 
        
           |  |  | 342 |             }
 | 
        
           |  |  | 343 |   | 
        
           |  |  | 344 |             // First delete the attached grade item and grades.
 | 
        
           |  |  | 345 |             $grade_item->delete($source);
 | 
        
           |  |  | 346 |   | 
        
           |  |  | 347 |             // Delete category itself.
 | 
        
           |  |  | 348 |             $success = parent::delete($source);
 | 
        
           |  |  | 349 |   | 
        
           |  |  | 350 |             $transaction->allow_commit();
 | 
        
           |  |  | 351 |         } catch (Exception $e) {
 | 
        
           |  |  | 352 |             $transaction->rollback($e);
 | 
        
           |  |  | 353 |         }
 | 
        
           |  |  | 354 |         return $success;
 | 
        
           |  |  | 355 |     }
 | 
        
           |  |  | 356 |   | 
        
           |  |  | 357 |     /**
 | 
        
           |  |  | 358 |      * In addition to the normal insert() defined in grade_object, this method sets the depth
 | 
        
           |  |  | 359 |      * and path for this object, and update the record accordingly.
 | 
        
           |  |  | 360 |      *
 | 
        
           |  |  | 361 |      * We do this here instead of in the constructor as they both need to know the record's
 | 
        
           |  |  | 362 |      * ID number, which only gets created at insertion time.
 | 
        
           |  |  | 363 |      * This method also creates an associated grade_item if this wasn't done during construction.
 | 
        
           |  |  | 364 |      *
 | 
        
           |  |  | 365 |      * @param string $source from where was the object inserted (mod/forum, manual, etc.)
 | 
        
           |  |  | 366 |      * @param bool $isbulkupdate If bulk grade update is happening.
 | 
        
           |  |  | 367 |      * @return int PK ID if successful, false otherwise
 | 
        
           |  |  | 368 |      */
 | 
        
           |  |  | 369 |     public function insert($source = null, $isbulkupdate = false) {
 | 
        
           |  |  | 370 |   | 
        
           |  |  | 371 |         if (empty($this->courseid)) {
 | 
        
           |  |  | 372 |             throw new \moodle_exception('cannotinsertgrade');
 | 
        
           |  |  | 373 |         }
 | 
        
           |  |  | 374 |   | 
        
           |  |  | 375 |         if (empty($this->parent)) {
 | 
        
           |  |  | 376 |             $course_category = grade_category::fetch_course_category($this->courseid);
 | 
        
           |  |  | 377 |             $this->parent = $course_category->id;
 | 
        
           |  |  | 378 |         }
 | 
        
           |  |  | 379 |   | 
        
           |  |  | 380 |         $this->path = null;
 | 
        
           |  |  | 381 |   | 
        
           |  |  | 382 |         $this->timecreated = $this->timemodified = time();
 | 
        
           |  |  | 383 |   | 
        
           |  |  | 384 |         if (!parent::insert($source)) {
 | 
        
           |  |  | 385 |             debugging("Could not insert this category: " . print_r($this, true));
 | 
        
           |  |  | 386 |             return false;
 | 
        
           |  |  | 387 |         }
 | 
        
           |  |  | 388 |   | 
        
           |  |  | 389 |         $this->force_regrading();
 | 
        
           |  |  | 390 |   | 
        
           |  |  | 391 |         // build path and depth
 | 
        
           |  |  | 392 |         $this->update($source);
 | 
        
           |  |  | 393 |   | 
        
           |  |  | 394 |         return $this->id;
 | 
        
           |  |  | 395 |     }
 | 
        
           |  |  | 396 |   | 
        
           |  |  | 397 |     /**
 | 
        
           |  |  | 398 |      * Internal function - used only from fetch_course_category()
 | 
        
           |  |  | 399 |      * Normal insert() can not be used for course category
 | 
        
           |  |  | 400 |      *
 | 
        
           |  |  | 401 |      * @param int $courseid The course ID
 | 
        
           |  |  | 402 |      * @return int The ID of the new course category
 | 
        
           |  |  | 403 |      */
 | 
        
           |  |  | 404 |     public function insert_course_category($courseid) {
 | 
        
           |  |  | 405 |         $this->courseid    = $courseid;
 | 
        
           |  |  | 406 |         $this->fullname    = '?';
 | 
        
           |  |  | 407 |         $this->path        = null;
 | 
        
           |  |  | 408 |         $this->parent      = null;
 | 
        
           |  |  | 409 |         $this->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
 | 
        
           |  |  | 410 |   | 
        
           |  |  | 411 |         $this->apply_default_settings();
 | 
        
           |  |  | 412 |         $this->apply_forced_settings();
 | 
        
           |  |  | 413 |   | 
        
           |  |  | 414 |         $this->timecreated = $this->timemodified = time();
 | 
        
           |  |  | 415 |   | 
        
           |  |  | 416 |         if (!parent::insert('system')) {
 | 
        
           |  |  | 417 |             debugging("Could not insert this category: " . print_r($this, true));
 | 
        
           |  |  | 418 |             return false;
 | 
        
           |  |  | 419 |         }
 | 
        
           |  |  | 420 |   | 
        
           |  |  | 421 |         // build path and depth
 | 
        
           |  |  | 422 |         $this->update('system');
 | 
        
           |  |  | 423 |   | 
        
           |  |  | 424 |         return $this->id;
 | 
        
           |  |  | 425 |     }
 | 
        
           |  |  | 426 |   | 
        
           |  |  | 427 |     /**
 | 
        
           |  |  | 428 |      * Compares the values held by this object with those of the matching record in DB, and returns
 | 
        
           |  |  | 429 |      * whether or not these differences are sufficient to justify an update of all parent objects.
 | 
        
           |  |  | 430 |      * This assumes that this object has an ID number and a matching record in DB. If not, it will return false.
 | 
        
           |  |  | 431 |      *
 | 
        
           |  |  | 432 |      * @return bool
 | 
        
           |  |  | 433 |      */
 | 
        
           |  |  | 434 |     public function qualifies_for_regrading() {
 | 
        
           |  |  | 435 |         if (empty($this->id)) {
 | 
        
           |  |  | 436 |             debugging("Can not regrade non existing category");
 | 
        
           |  |  | 437 |             return false;
 | 
        
           |  |  | 438 |         }
 | 
        
           |  |  | 439 |   | 
        
           |  |  | 440 |         $db_item = grade_category::fetch(array('id'=>$this->id));
 | 
        
           |  |  | 441 |   | 
        
           |  |  | 442 |         $aggregationdiff = $db_item->aggregation         != $this->aggregation;
 | 
        
           |  |  | 443 |         $keephighdiff    = $db_item->keephigh            != $this->keephigh;
 | 
        
           |  |  | 444 |         $droplowdiff     = $db_item->droplow             != $this->droplow;
 | 
        
           |  |  | 445 |         $aggonlygrddiff  = $db_item->aggregateonlygraded != $this->aggregateonlygraded;
 | 
        
           |  |  | 446 |         $aggoutcomesdiff = $db_item->aggregateoutcomes   != $this->aggregateoutcomes;
 | 
        
           |  |  | 447 |   | 
        
           |  |  | 448 |         return ($aggregationdiff || $keephighdiff || $droplowdiff || $aggonlygrddiff || $aggoutcomesdiff);
 | 
        
           |  |  | 449 |     }
 | 
        
           |  |  | 450 |   | 
        
           |  |  | 451 |     /**
 | 
        
           |  |  | 452 |      * Marks this grade categories' associated grade item as needing regrading
 | 
        
           |  |  | 453 |      */
 | 
        
           |  |  | 454 |     public function force_regrading() {
 | 
        
           |  |  | 455 |         $grade_item = $this->load_grade_item();
 | 
        
           |  |  | 456 |         $grade_item->force_regrading();
 | 
        
           |  |  | 457 |     }
 | 
        
           |  |  | 458 |   | 
        
           |  |  | 459 |     /**
 | 
        
           |  |  | 460 |      * Something that should be called before we start regrading the whole course.
 | 
        
           |  |  | 461 |      *
 | 
        
           |  |  | 462 |      * @return void
 | 
        
           |  |  | 463 |      */
 | 
        
           |  |  | 464 |     public function pre_regrade_final_grades() {
 | 
        
           |  |  | 465 |         $this->auto_update_weights();
 | 
        
           |  |  | 466 |         $this->auto_update_max();
 | 
        
           |  |  | 467 |     }
 | 
        
           |  |  | 468 |   | 
        
           |  |  | 469 |     /**
 | 
        
           |  |  | 470 |      * Generates and saves final grades in associated category grade item.
 | 
        
           |  |  | 471 |      * These immediate children must already have their own final grades.
 | 
        
           |  |  | 472 |      * The category's aggregation method is used to generate final grades.
 | 
        
           |  |  | 473 |      *
 | 
        
           |  |  | 474 |      * Please note that category grade is either calculated or aggregated, not both at the same time.
 | 
        
           |  |  | 475 |      *
 | 
        
           |  |  | 476 |      * This method must be used ONLY from grade_item::regrade_final_grades(),
 | 
        
           |  |  | 477 |      * because the calculation must be done in correct order!
 | 
        
           |  |  | 478 |      *
 | 
        
           |  |  | 479 |      * Steps to follow:
 | 
        
           |  |  | 480 |      *  1. Get final grades from immediate children
 | 
        
           |  |  | 481 |      *  3. Aggregate these grades
 | 
        
           |  |  | 482 |      *  4. Save them in final grades of associated category grade item
 | 
        
           |  |  | 483 |      *
 | 
        
           |  |  | 484 |      * @param int $userid The user ID if final grade generation should be limited to a single user
 | 
        
           |  |  | 485 |      * @param \core\progress\base|null $progress Optional progress indicator
 | 
        
           |  |  | 486 |      * @return bool
 | 
        
           |  |  | 487 |      */
 | 
        
           |  |  | 488 |     public function generate_grades($userid=null, ?\core\progress\base $progress = null) {
 | 
        
           |  |  | 489 |         global $CFG, $DB;
 | 
        
           |  |  | 490 |   | 
        
           |  |  | 491 |         $this->load_grade_item();
 | 
        
           |  |  | 492 |   | 
        
           |  |  | 493 |         if ($this->grade_item->is_locked()) {
 | 
        
           |  |  | 494 |             return true; // no need to recalculate locked items
 | 
        
           |  |  | 495 |         }
 | 
        
           |  |  | 496 |   | 
        
           |  |  | 497 |         // find grade items of immediate children (category or grade items) and force site settings
 | 
        
           |  |  | 498 |         $depends_on = $this->grade_item->depends_on();
 | 
        
           |  |  | 499 |   | 
        
           |  |  | 500 |         if (empty($depends_on)) {
 | 
        
           |  |  | 501 |             $items = false;
 | 
        
           |  |  | 502 |   | 
        
           |  |  | 503 |         } else {
 | 
        
           |  |  | 504 |             list($usql, $params) = $DB->get_in_or_equal($depends_on);
 | 
        
           |  |  | 505 |             $sql = "SELECT *
 | 
        
           |  |  | 506 |                       FROM {grade_items}
 | 
        
           |  |  | 507 |                      WHERE id $usql";
 | 
        
           |  |  | 508 |             $items = $DB->get_records_sql($sql, $params);
 | 
        
           |  |  | 509 |             foreach ($items as $id => $item) {
 | 
        
           |  |  | 510 |                 $items[$id] = new grade_item($item, false);
 | 
        
           |  |  | 511 |             }
 | 
        
           |  |  | 512 |         }
 | 
        
           |  |  | 513 |   | 
        
           |  |  | 514 |         $grade_inst = new grade_grade();
 | 
        
           |  |  | 515 |         $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
 | 
        
           |  |  | 516 |   | 
        
           |  |  | 517 |         // where to look for final grades - include grade of this item too, we will store the results there
 | 
        
           |  |  | 518 |         $gis = array_merge($depends_on, array($this->grade_item->id));
 | 
        
           |  |  | 519 |         list($usql, $params) = $DB->get_in_or_equal($gis);
 | 
        
           |  |  | 520 |   | 
        
           |  |  | 521 |         if ($userid) {
 | 
        
           |  |  | 522 |             $usersql = "AND g.userid=?";
 | 
        
           |  |  | 523 |             $params[] = $userid;
 | 
        
           |  |  | 524 |   | 
        
           |  |  | 525 |         } else {
 | 
        
           |  |  | 526 |             $usersql = "";
 | 
        
           |  |  | 527 |         }
 | 
        
           |  |  | 528 |   | 
        
           |  |  | 529 |         $sql = "SELECT $fields
 | 
        
           |  |  | 530 |                   FROM {grade_grades} g, {grade_items} gi
 | 
        
           |  |  | 531 |                  WHERE gi.id = g.itemid AND gi.id $usql $usersql
 | 
        
           |  |  | 532 |               ORDER BY g.userid";
 | 
        
           |  |  | 533 |   | 
        
           |  |  | 534 |         // group the results by userid and aggregate the grades for this user
 | 
        
           |  |  | 535 |         $rs = $DB->get_recordset_sql($sql, $params);
 | 
        
           |  |  | 536 |         if ($rs->valid()) {
 | 
        
           |  |  | 537 |             $prevuser = 0;
 | 
        
           |  |  | 538 |             $grade_values = array();
 | 
        
           |  |  | 539 |             $excluded     = array();
 | 
        
           |  |  | 540 |             $oldgrade     = null;
 | 
        
           |  |  | 541 |             $grademaxoverrides = array();
 | 
        
           |  |  | 542 |             $grademinoverrides = array();
 | 
        
           |  |  | 543 |   | 
        
           |  |  | 544 |             foreach ($rs as $used) {
 | 
        
           |  |  | 545 |                 $grade = new grade_grade($used, false);
 | 
        
           |  |  | 546 |                 if (isset($items[$grade->itemid])) {
 | 
        
           |  |  | 547 |                     // Prevent grade item to be fetched from DB.
 | 
        
           |  |  | 548 |                     $grade->grade_item =& $items[$grade->itemid];
 | 
        
           |  |  | 549 |                 } else if ($grade->itemid == $this->grade_item->id) {
 | 
        
           |  |  | 550 |                     // This grade's grade item is not in $items.
 | 
        
           |  |  | 551 |                     $grade->grade_item =& $this->grade_item;
 | 
        
           |  |  | 552 |                 }
 | 
        
           |  |  | 553 |                 if ($grade->userid != $prevuser) {
 | 
        
           |  |  | 554 |                     $this->aggregate_grades($prevuser,
 | 
        
           |  |  | 555 |                                             $items,
 | 
        
           |  |  | 556 |                                             $grade_values,
 | 
        
           |  |  | 557 |                                             $oldgrade,
 | 
        
           |  |  | 558 |                                             $excluded,
 | 
        
           |  |  | 559 |                                             $grademinoverrides,
 | 
        
           |  |  | 560 |                                             $grademaxoverrides);
 | 
        
           |  |  | 561 |                     $prevuser = $grade->userid;
 | 
        
           |  |  | 562 |                     $grade_values = array();
 | 
        
           |  |  | 563 |                     $excluded     = array();
 | 
        
           |  |  | 564 |                     $oldgrade     = null;
 | 
        
           |  |  | 565 |                     $grademaxoverrides = array();
 | 
        
           |  |  | 566 |                     $grademinoverrides = array();
 | 
        
           |  |  | 567 |                 }
 | 
        
           |  |  | 568 |                 $grade_values[$grade->itemid] = $grade->finalgrade;
 | 
        
           |  |  | 569 |                 $grademaxoverrides[$grade->itemid] = $grade->get_grade_max();
 | 
        
           |  |  | 570 |                 $grademinoverrides[$grade->itemid] = $grade->get_grade_min();
 | 
        
           |  |  | 571 |   | 
        
           |  |  | 572 |                 if ($grade->excluded) {
 | 
        
           |  |  | 573 |                     $excluded[] = $grade->itemid;
 | 
        
           |  |  | 574 |                 }
 | 
        
           |  |  | 575 |   | 
        
           |  |  | 576 |                 if ($this->grade_item->id == $grade->itemid) {
 | 
        
           |  |  | 577 |                     $oldgrade = $grade;
 | 
        
           |  |  | 578 |                 }
 | 
        
           |  |  | 579 |   | 
        
           |  |  | 580 |                 if ($progress) {
 | 
        
           |  |  | 581 |                     // Incrementing the progress by nothing causes it to send an update (once per second)
 | 
        
           |  |  | 582 |                     // to the web browser so as to prevent the connection timing out.
 | 
        
           |  |  | 583 |                     $progress->increment_progress(0);
 | 
        
           |  |  | 584 |                 }
 | 
        
           |  |  | 585 |             }
 | 
        
           |  |  | 586 |             $this->aggregate_grades($prevuser,
 | 
        
           |  |  | 587 |                                     $items,
 | 
        
           |  |  | 588 |                                     $grade_values,
 | 
        
           |  |  | 589 |                                     $oldgrade,
 | 
        
           |  |  | 590 |                                     $excluded,
 | 
        
           |  |  | 591 |                                     $grademinoverrides,
 | 
        
           |  |  | 592 |                                     $grademaxoverrides);//the last one
 | 
        
           |  |  | 593 |         }
 | 
        
           |  |  | 594 |         $rs->close();
 | 
        
           |  |  | 595 |   | 
        
           |  |  | 596 |         return true;
 | 
        
           |  |  | 597 |     }
 | 
        
           |  |  | 598 |   | 
        
           |  |  | 599 |     /**
 | 
        
           |  |  | 600 |      * Internal function for grade category grade aggregation
 | 
        
           |  |  | 601 |      *
 | 
        
           |  |  | 602 |      * @param int    $userid The User ID
 | 
        
           |  |  | 603 |      * @param array  $items Grade items
 | 
        
           |  |  | 604 |      * @param array  $grade_values Array of grade values
 | 
        
           |  |  | 605 |      * @param object $oldgrade Old grade
 | 
        
           |  |  | 606 |      * @param array  $excluded Excluded
 | 
        
           |  |  | 607 |      * @param array  $grademinoverrides User specific grademin values if different to the grade_item grademin (key is itemid)
 | 
        
           |  |  | 608 |      * @param array  $grademaxoverrides User specific grademax values if different to the grade_item grademax (key is itemid)
 | 
        
           |  |  | 609 |      */
 | 
        
           |  |  | 610 |     private function aggregate_grades($userid,
 | 
        
           |  |  | 611 |                                       $items,
 | 
        
           |  |  | 612 |                                       $grade_values,
 | 
        
           |  |  | 613 |                                       $oldgrade,
 | 
        
           |  |  | 614 |                                       $excluded,
 | 
        
           |  |  | 615 |                                       $grademinoverrides,
 | 
        
           |  |  | 616 |                                       $grademaxoverrides) {
 | 
        
           |  |  | 617 |         global $CFG, $DB;
 | 
        
           |  |  | 618 |   | 
        
           |  |  | 619 |         // Remember these so we can set flags on them to describe how they were used in the aggregation.
 | 
        
           |  |  | 620 |         $novalue = array();
 | 
        
           |  |  | 621 |         $dropped = array();
 | 
        
           |  |  | 622 |         $extracredit = array();
 | 
        
           |  |  | 623 |         $usedweights = array();
 | 
        
           |  |  | 624 |   | 
        
           |  |  | 625 |         if (empty($userid)) {
 | 
        
           |  |  | 626 |             //ignore first call
 | 
        
           |  |  | 627 |             return;
 | 
        
           |  |  | 628 |         }
 | 
        
           |  |  | 629 |   | 
        
           |  |  | 630 |         if ($oldgrade) {
 | 
        
           |  |  | 631 |             $oldfinalgrade = $oldgrade->finalgrade;
 | 
        
           |  |  | 632 |             $grade = new grade_grade($oldgrade, false);
 | 
        
           |  |  | 633 |             $grade->grade_item =& $this->grade_item;
 | 
        
           |  |  | 634 |   | 
        
           |  |  | 635 |         } else {
 | 
        
           |  |  | 636 |             // insert final grade - it will be needed later anyway
 | 
        
           |  |  | 637 |             $grade = new grade_grade(array('itemid'=>$this->grade_item->id, 'userid'=>$userid), false);
 | 
        
           |  |  | 638 |             $grade->grade_item =& $this->grade_item;
 | 
        
           |  |  | 639 |             $grade->insert('system');
 | 
        
           |  |  | 640 |             $oldfinalgrade = null;
 | 
        
           |  |  | 641 |         }
 | 
        
           |  |  | 642 |   | 
        
           |  |  | 643 |         // no need to recalculate locked or overridden grades
 | 
        
           |  |  | 644 |         if ($grade->is_locked() or $grade->is_overridden()) {
 | 
        
           |  |  | 645 |             return;
 | 
        
           |  |  | 646 |         }
 | 
        
           |  |  | 647 |   | 
        
           |  |  | 648 |         // can not use own final category grade in calculation
 | 
        
           |  |  | 649 |         unset($grade_values[$this->grade_item->id]);
 | 
        
           |  |  | 650 |   | 
        
           |  |  | 651 |         // Make sure a grade_grade exists for every grade_item.
 | 
        
           |  |  | 652 |         // We need to do this so we can set the aggregationstatus
 | 
        
           |  |  | 653 |         // with a set_field call instead of checking if each one exists and creating/updating.
 | 
        
           |  |  | 654 |         if (!empty($items)) {
 | 
        
           |  |  | 655 |             list($ggsql, $params) = $DB->get_in_or_equal(array_keys($items), SQL_PARAMS_NAMED, 'g');
 | 
        
           |  |  | 656 |   | 
        
           |  |  | 657 |   | 
        
           |  |  | 658 |             $params['userid'] = $userid;
 | 
        
           |  |  | 659 |             $sql = "SELECT itemid
 | 
        
           |  |  | 660 |                       FROM {grade_grades}
 | 
        
           |  |  | 661 |                      WHERE itemid $ggsql AND userid = :userid";
 | 
        
           |  |  | 662 |             $existingitems = $DB->get_records_sql($sql, $params);
 | 
        
           |  |  | 663 |   | 
        
           |  |  | 664 |             $notexisting = array_diff(array_keys($items), array_keys($existingitems));
 | 
        
           |  |  | 665 |             foreach ($notexisting as $itemid) {
 | 
        
           |  |  | 666 |                 $gradeitem = $items[$itemid];
 | 
        
           |  |  | 667 |                 $gradegrade = new grade_grade(array('itemid' => $itemid,
 | 
        
           |  |  | 668 |                                                     'userid' => $userid,
 | 
        
           |  |  | 669 |                                                     'rawgrademin' => $gradeitem->grademin,
 | 
        
           |  |  | 670 |                                                     'rawgrademax' => $gradeitem->grademax), false);
 | 
        
           |  |  | 671 |                 $gradegrade->grade_item = $gradeitem;
 | 
        
           |  |  | 672 |                 $gradegrade->insert('system');
 | 
        
           |  |  | 673 |             }
 | 
        
           |  |  | 674 |         }
 | 
        
           |  |  | 675 |   | 
        
           |  |  | 676 |         // if no grades calculation possible or grading not allowed clear final grade
 | 
        
           |  |  | 677 |         if (empty($grade_values) or empty($items) or ($this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE)) {
 | 
        
           |  |  | 678 |             $grade->finalgrade = null;
 | 
        
           |  |  | 679 |   | 
        
           |  |  | 680 |             if (!is_null($oldfinalgrade)) {
 | 
        
           |  |  | 681 |                 $grade->timemodified = time();
 | 
        
           |  |  | 682 |                 $success = $grade->update('aggregation');
 | 
        
           |  |  | 683 |   | 
        
           |  |  | 684 |                 // If successful trigger a user_graded event.
 | 
        
           |  |  | 685 |                 if ($success) {
 | 
        
           |  |  | 686 |                     \core\event\user_graded::create_from_grade($grade, \core\event\base::USER_OTHER)->trigger();
 | 
        
           |  |  | 687 |                 }
 | 
        
           |  |  | 688 |             }
 | 
        
           |  |  | 689 |             $dropped = $grade_values;
 | 
        
           |  |  | 690 |             $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
 | 
        
           |  |  | 691 |             return;
 | 
        
           |  |  | 692 |         }
 | 
        
           |  |  | 693 |   | 
        
           |  |  | 694 |         // Normalize the grades first - all will have value 0...1
 | 
        
           |  |  | 695 |         // ungraded items are not used in aggregation.
 | 
        
           |  |  | 696 |         foreach ($grade_values as $itemid=>$v) {
 | 
        
           |  |  | 697 |             if (is_null($v)) {
 | 
        
           |  |  | 698 |                 // If null, it means no grade.
 | 
        
           |  |  | 699 |                 if ($this->aggregateonlygraded) {
 | 
        
           |  |  | 700 |                     unset($grade_values[$itemid]);
 | 
        
           |  |  | 701 |                     // Mark this item as "excluded empty" because it has no grade.
 | 
        
           |  |  | 702 |                     $novalue[$itemid] = 0;
 | 
        
           |  |  | 703 |                     continue;
 | 
        
           |  |  | 704 |                 }
 | 
        
           |  |  | 705 |             }
 | 
        
           |  |  | 706 |             if (in_array($itemid, $excluded)) {
 | 
        
           |  |  | 707 |                 unset($grade_values[$itemid]);
 | 
        
           |  |  | 708 |                 $dropped[$itemid] = 0;
 | 
        
           |  |  | 709 |                 continue;
 | 
        
           |  |  | 710 |             }
 | 
        
           |  |  | 711 |             // Check for user specific grade min/max overrides.
 | 
        
           |  |  | 712 |             $usergrademin = $items[$itemid]->grademin;
 | 
        
           |  |  | 713 |             $usergrademax = $items[$itemid]->grademax;
 | 
        
           |  |  | 714 |             if (isset($grademinoverrides[$itemid])) {
 | 
        
           |  |  | 715 |                 $usergrademin = $grademinoverrides[$itemid];
 | 
        
           |  |  | 716 |             }
 | 
        
           |  |  | 717 |             if (isset($grademaxoverrides[$itemid])) {
 | 
        
           |  |  | 718 |                 $usergrademax = $grademaxoverrides[$itemid];
 | 
        
           |  |  | 719 |             }
 | 
        
           |  |  | 720 |             if ($this->aggregation == GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 721 |                 // Assume that the grademin is 0 when standardising the score, to preserve negative grades.
 | 
        
           |  |  | 722 |                 $grade_values[$itemid] = grade_grade::standardise_score($v, 0, $usergrademax, 0, 1);
 | 
        
           |  |  | 723 |             } else {
 | 
        
           |  |  | 724 |                 $grade_values[$itemid] = grade_grade::standardise_score($v, $usergrademin, $usergrademax, 0, 1);
 | 
        
           |  |  | 725 |             }
 | 
        
           |  |  | 726 |   | 
        
           |  |  | 727 |         }
 | 
        
           |  |  | 728 |   | 
        
           |  |  | 729 |         // First, check if all grades are null, because the final grade will be null
 | 
        
           |  |  | 730 |         // even when aggreateonlygraded is true.
 | 
        
           |  |  | 731 |         $allnull = true;
 | 
        
           |  |  | 732 |         foreach ($grade_values as $v) {
 | 
        
           |  |  | 733 |             if (!is_null($v)) {
 | 
        
           |  |  | 734 |                 $allnull = false;
 | 
        
           |  |  | 735 |                 break;
 | 
        
           |  |  | 736 |             }
 | 
        
           |  |  | 737 |         }
 | 
        
           |  |  | 738 |   | 
        
           |  |  | 739 |         // For items with no value, and not excluded - either set their grade to 0 or exclude them.
 | 
        
           |  |  | 740 |         foreach ($items as $itemid=>$value) {
 | 
        
           |  |  | 741 |             if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
 | 
        
           |  |  | 742 |                 if (!$this->aggregateonlygraded) {
 | 
        
           |  |  | 743 |                     $grade_values[$itemid] = 0;
 | 
        
           |  |  | 744 |                 } else {
 | 
        
           |  |  | 745 |                     // We are specifically marking these items as "excluded empty".
 | 
        
           |  |  | 746 |                     $novalue[$itemid] = 0;
 | 
        
           |  |  | 747 |                 }
 | 
        
           |  |  | 748 |             }
 | 
        
           |  |  | 749 |         }
 | 
        
           |  |  | 750 |   | 
        
           |  |  | 751 |         // limit and sort
 | 
        
           |  |  | 752 |         $allvalues = $grade_values;
 | 
        
           |  |  | 753 |         if ($this->can_apply_limit_rules()) {
 | 
        
           |  |  | 754 |             $this->apply_limit_rules($grade_values, $items);
 | 
        
           |  |  | 755 |         }
 | 
        
           |  |  | 756 |   | 
        
           |  |  | 757 |         $moredropped = array_diff($allvalues, $grade_values);
 | 
        
           |  |  | 758 |         foreach ($moredropped as $drop => $unused) {
 | 
        
           |  |  | 759 |             $dropped[$drop] = 0;
 | 
        
           |  |  | 760 |         }
 | 
        
           |  |  | 761 |   | 
        
           |  |  | 762 |         foreach ($grade_values as $itemid => $val) {
 | 
        
           |  |  | 763 |             if (self::is_extracredit_used() && ($items[$itemid]->aggregationcoef > 0)) {
 | 
        
           |  |  | 764 |                 $extracredit[$itemid] = 0;
 | 
        
           |  |  | 765 |             }
 | 
        
           |  |  | 766 |         }
 | 
        
           |  |  | 767 |   | 
        
           |  |  | 768 |         asort($grade_values, SORT_NUMERIC);
 | 
        
           |  |  | 769 |   | 
        
           |  |  | 770 |         // let's see we have still enough grades to do any statistics
 | 
        
           |  |  | 771 |         if (count($grade_values) == 0) {
 | 
        
           |  |  | 772 |             // not enough attempts yet
 | 
        
           |  |  | 773 |             $grade->finalgrade = null;
 | 
        
           |  |  | 774 |   | 
        
           |  |  | 775 |             if (!is_null($oldfinalgrade)) {
 | 
        
           |  |  | 776 |                 $grade->timemodified = time();
 | 
        
           |  |  | 777 |                 $success = $grade->update('aggregation');
 | 
        
           |  |  | 778 |   | 
        
           |  |  | 779 |                 // If successful trigger a user_graded event.
 | 
        
           |  |  | 780 |                 if ($success) {
 | 
        
           |  |  | 781 |                     \core\event\user_graded::create_from_grade($grade, \core\event\base::USER_OTHER)->trigger();
 | 
        
           |  |  | 782 |                 }
 | 
        
           |  |  | 783 |             }
 | 
        
           |  |  | 784 |             $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
 | 
        
           |  |  | 785 |             return;
 | 
        
           |  |  | 786 |         }
 | 
        
           |  |  | 787 |   | 
        
           |  |  | 788 |         // do the maths
 | 
        
           |  |  | 789 |         $result = $this->aggregate_values_and_adjust_bounds($grade_values,
 | 
        
           |  |  | 790 |                                                             $items,
 | 
        
           |  |  | 791 |                                                             $usedweights,
 | 
        
           |  |  | 792 |                                                             $grademinoverrides,
 | 
        
           |  |  | 793 |                                                             $grademaxoverrides);
 | 
        
           |  |  | 794 |         $agg_grade = $result['grade'];
 | 
        
           |  |  | 795 |   | 
        
           |  |  | 796 |         // Set the actual grademin and max to bind the grade properly.
 | 
        
           |  |  | 797 |         $this->grade_item->grademin = $result['grademin'];
 | 
        
           |  |  | 798 |         $this->grade_item->grademax = $result['grademax'];
 | 
        
           |  |  | 799 |   | 
        
           |  |  | 800 |         if ($this->aggregation == GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 801 |             // The natural aggregation always displays the range as coming from 0 for categories.
 | 
        
           |  |  | 802 |             // However, when we bind the grade we allow for negative values.
 | 
        
           |  |  | 803 |             $result['grademin'] = 0;
 | 
        
           |  |  | 804 |         }
 | 
        
           |  |  | 805 |   | 
        
           |  |  | 806 |         if ($allnull) {
 | 
        
           |  |  | 807 |             $grade->finalgrade = null;
 | 
        
           |  |  | 808 |         } else {
 | 
        
           |  |  | 809 |             // Recalculate the grade back to requested range.
 | 
        
           |  |  | 810 |             $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $result['grademin'], $result['grademax']);
 | 
        
           |  |  | 811 |             $grade->finalgrade = $this->grade_item->bounded_grade($finalgrade);
 | 
        
           |  |  | 812 |         }
 | 
        
           |  |  | 813 |   | 
        
           |  |  | 814 |         $oldrawgrademin = $grade->rawgrademin;
 | 
        
           |  |  | 815 |         $oldrawgrademax = $grade->rawgrademax;
 | 
        
           |  |  | 816 |         $grade->rawgrademin = $result['grademin'];
 | 
        
           |  |  | 817 |         $grade->rawgrademax = $result['grademax'];
 | 
        
           |  |  | 818 |   | 
        
           |  |  | 819 |         // Update in db if changed.
 | 
        
           |  |  | 820 |         if (grade_floats_different($grade->finalgrade, $oldfinalgrade) ||
 | 
        
           |  |  | 821 |                 grade_floats_different($grade->rawgrademax, $oldrawgrademax) ||
 | 
        
           |  |  | 822 |                 grade_floats_different($grade->rawgrademin, $oldrawgrademin)) {
 | 
        
           |  |  | 823 |             $grade->timemodified = time();
 | 
        
           |  |  | 824 |             $success = $grade->update('aggregation');
 | 
        
           |  |  | 825 |   | 
        
           |  |  | 826 |             // If successful trigger a user_graded event.
 | 
        
           |  |  | 827 |             if ($success) {
 | 
        
           |  |  | 828 |                 \core\event\user_graded::create_from_grade($grade, \core\event\base::USER_OTHER)->trigger();
 | 
        
           |  |  | 829 |             }
 | 
        
           |  |  | 830 |         }
 | 
        
           |  |  | 831 |   | 
        
           |  |  | 832 |         $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
 | 
        
           |  |  | 833 |   | 
        
           |  |  | 834 |         return;
 | 
        
           |  |  | 835 |     }
 | 
        
           |  |  | 836 |   | 
        
           |  |  | 837 |     /**
 | 
        
           |  |  | 838 |      * Set the flags on the grade_grade items to indicate how individual grades are used
 | 
        
           |  |  | 839 |      * in the aggregation.
 | 
        
           |  |  | 840 |      *
 | 
        
           |  |  | 841 |      * WARNING: This function is called a lot during gradebook recalculation, be very performance considerate.
 | 
        
           |  |  | 842 |      *
 | 
        
           |  |  | 843 |      * @param int $userid The user we have aggregated the grades for.
 | 
        
           |  |  | 844 |      * @param array $usedweights An array with keys for each of the grade_item columns included in the aggregation. The value are the relative weight.
 | 
        
           |  |  | 845 |      * @param array $novalue An array with keys for each of the grade_item columns skipped because
 | 
        
           |  |  | 846 |      *                       they had no value in the aggregation.
 | 
        
           |  |  | 847 |      * @param array $dropped An array with keys for each of the grade_item columns dropped
 | 
        
           |  |  | 848 |      *                       because of any drop lowest/highest settings in the aggregation.
 | 
        
           |  |  | 849 |      * @param array $extracredit An array with keys for each of the grade_item columns
 | 
        
           |  |  | 850 |      *                       considered extra credit by the aggregation.
 | 
        
           |  |  | 851 |      */
 | 
        
           |  |  | 852 |     private function set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit) {
 | 
        
           |  |  | 853 |         global $DB;
 | 
        
           |  |  | 854 |   | 
        
           |  |  | 855 |         // We want to know all current user grades so we can decide whether they need to be updated or they already contain the
 | 
        
           |  |  | 856 |         // expected value.
 | 
        
           |  |  | 857 |         $sql = "SELECT gi.id, gg.aggregationstatus, gg.aggregationweight FROM {grade_grades} gg
 | 
        
           |  |  | 858 |                   JOIN {grade_items} gi ON (gg.itemid = gi.id)
 | 
        
           |  |  | 859 |                  WHERE gg.userid = :userid";
 | 
        
           |  |  | 860 |         $params = array('categoryid' => $this->id, 'userid' => $userid);
 | 
        
           |  |  | 861 |   | 
        
           |  |  | 862 |         // These are all grade_item ids which grade_grades will NOT end up being 'unknown' (because they are not unknown or
 | 
        
           |  |  | 863 |         // because we will update them to something different that 'unknown').
 | 
        
           |  |  | 864 |         $giids = array_keys($usedweights + $novalue + $dropped + $extracredit);
 | 
        
           |  |  | 865 |   | 
        
           |  |  | 866 |         if ($giids) {
 | 
        
           |  |  | 867 |             // We include grade items that might not be in categoryid.
 | 
        
           |  |  | 868 |             list($itemsql, $itemlist) = $DB->get_in_or_equal($giids, SQL_PARAMS_NAMED, 'gg');
 | 
        
           |  |  | 869 |             $sql .= ' AND (gi.categoryid = :categoryid OR gi.id ' . $itemsql . ')';
 | 
        
           |  |  | 870 |             $params = $params + $itemlist;
 | 
        
           |  |  | 871 |         } else {
 | 
        
           |  |  | 872 |             $sql .= ' AND gi.categoryid = :categoryid';
 | 
        
           |  |  | 873 |         }
 | 
        
           |  |  | 874 |         $currentgrades = $DB->get_recordset_sql($sql, $params);
 | 
        
           |  |  | 875 |   | 
        
           |  |  | 876 |         // We will store here the grade_item ids that need to be updated on db.
 | 
        
           |  |  | 877 |         $toupdate = array();
 | 
        
           |  |  | 878 |   | 
        
           |  |  | 879 |         if ($currentgrades->valid()) {
 | 
        
           |  |  | 880 |   | 
        
           |  |  | 881 |             // Iterate through the user grades to see if we really need to update any of them.
 | 
        
           |  |  | 882 |             foreach ($currentgrades as $currentgrade) {
 | 
        
           |  |  | 883 |   | 
        
           |  |  | 884 |                 // Unset $usedweights that we do not need to update.
 | 
        
           |  |  | 885 |                 if (!empty($usedweights) && isset($usedweights[$currentgrade->id]) && $currentgrade->aggregationstatus === 'used') {
 | 
        
           |  |  | 886 |                     // We discard the ones that already have the contribution specified in $usedweights and are marked as 'used'.
 | 
        
           |  |  | 887 |                     if (grade_floats_equal($currentgrade->aggregationweight, $usedweights[$currentgrade->id])) {
 | 
        
           |  |  | 888 |                         unset($usedweights[$currentgrade->id]);
 | 
        
           |  |  | 889 |                     }
 | 
        
           |  |  | 890 |                     // Used weights can be present in multiple set_usedinaggregation arguments.
 | 
        
           |  |  | 891 |                     if (!isset($novalue[$currentgrade->id]) && !isset($dropped[$currentgrade->id]) &&
 | 
        
           |  |  | 892 |                             !isset($extracredit[$currentgrade->id])) {
 | 
        
           |  |  | 893 |                         continue;
 | 
        
           |  |  | 894 |                     }
 | 
        
           |  |  | 895 |                 }
 | 
        
           |  |  | 896 |   | 
        
           |  |  | 897 |                 // No value grades.
 | 
        
           |  |  | 898 |                 if (!empty($novalue) && isset($novalue[$currentgrade->id])) {
 | 
        
           |  |  | 899 |                     if ($currentgrade->aggregationstatus !== 'novalue' ||
 | 
        
           |  |  | 900 |                             grade_floats_different($currentgrade->aggregationweight, 0)) {
 | 
        
           |  |  | 901 |                         $toupdate['novalue'][] = $currentgrade->id;
 | 
        
           |  |  | 902 |                     }
 | 
        
           |  |  | 903 |                     continue;
 | 
        
           |  |  | 904 |                 }
 | 
        
           |  |  | 905 |   | 
        
           |  |  | 906 |                 // Dropped grades.
 | 
        
           |  |  | 907 |                 if (!empty($dropped) && isset($dropped[$currentgrade->id])) {
 | 
        
           |  |  | 908 |                     if ($currentgrade->aggregationstatus !== 'dropped' ||
 | 
        
           |  |  | 909 |                             grade_floats_different($currentgrade->aggregationweight, 0)) {
 | 
        
           |  |  | 910 |                         $toupdate['dropped'][] = $currentgrade->id;
 | 
        
           |  |  | 911 |                     }
 | 
        
           |  |  | 912 |                     continue;
 | 
        
           |  |  | 913 |                 }
 | 
        
           |  |  | 914 |   | 
        
           |  |  | 915 |                 // Extra credit grades.
 | 
        
           |  |  | 916 |                 if (!empty($extracredit) && isset($extracredit[$currentgrade->id])) {
 | 
        
           |  |  | 917 |   | 
        
           |  |  | 918 |                     // If this grade item is already marked as 'extra' and it already has the provided $usedweights value would be
 | 
        
           |  |  | 919 |                     // silly to update to 'used' to later update to 'extra'.
 | 
        
           |  |  | 920 |                     if (!empty($usedweights) && isset($usedweights[$currentgrade->id]) &&
 | 
        
           |  |  | 921 |                             grade_floats_equal($currentgrade->aggregationweight, $usedweights[$currentgrade->id])) {
 | 
        
           |  |  | 922 |                         unset($usedweights[$currentgrade->id]);
 | 
        
           |  |  | 923 |                     }
 | 
        
           |  |  | 924 |   | 
        
           |  |  | 925 |                     // Update the item to extra if it is not already marked as extra in the database or if the item's
 | 
        
           |  |  | 926 |                     // aggregationweight will be updated when going through $usedweights items.
 | 
        
           |  |  | 927 |                     if ($currentgrade->aggregationstatus !== 'extra' ||
 | 
        
           |  |  | 928 |                             (!empty($usedweights) && isset($usedweights[$currentgrade->id]))) {
 | 
        
           |  |  | 929 |                         $toupdate['extracredit'][] = $currentgrade->id;
 | 
        
           |  |  | 930 |                     }
 | 
        
           |  |  | 931 |                     continue;
 | 
        
           |  |  | 932 |                 }
 | 
        
           |  |  | 933 |   | 
        
           |  |  | 934 |                 // If is not in any of the above groups it should be set to 'unknown', checking that the item is not already
 | 
        
           |  |  | 935 |                 // unknown, if it is we don't need to update it.
 | 
        
           |  |  | 936 |                 if ($currentgrade->aggregationstatus !== 'unknown' || grade_floats_different($currentgrade->aggregationweight, 0)) {
 | 
        
           |  |  | 937 |                     $toupdate['unknown'][] = $currentgrade->id;
 | 
        
           |  |  | 938 |                 }
 | 
        
           |  |  | 939 |             }
 | 
        
           |  |  | 940 |             $currentgrades->close();
 | 
        
           |  |  | 941 |         }
 | 
        
           |  |  | 942 |   | 
        
           |  |  | 943 |         // Update items to 'unknown' status.
 | 
        
           |  |  | 944 |         if (!empty($toupdate['unknown'])) {
 | 
        
           |  |  | 945 |             list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['unknown'], SQL_PARAMS_NAMED, 'g');
 | 
        
           |  |  | 946 |   | 
        
           |  |  | 947 |             $itemlist['userid'] = $userid;
 | 
        
           |  |  | 948 |   | 
        
           |  |  | 949 |             $sql = "UPDATE {grade_grades}
 | 
        
           |  |  | 950 |                        SET aggregationstatus = 'unknown',
 | 
        
           |  |  | 951 |                            aggregationweight = 0
 | 
        
           |  |  | 952 |                      WHERE itemid $itemsql AND userid = :userid";
 | 
        
           |  |  | 953 |             $DB->execute($sql, $itemlist);
 | 
        
           |  |  | 954 |         }
 | 
        
           |  |  | 955 |   | 
        
           |  |  | 956 |         // Update items to 'used' status and setting the proper weight.
 | 
        
           |  |  | 957 |         if (!empty($usedweights)) {
 | 
        
           |  |  | 958 |             // The usedweights items are updated individually to record the weights.
 | 
        
           |  |  | 959 |             foreach ($usedweights as $gradeitemid => $contribution) {
 | 
        
           |  |  | 960 |                 $sql = "UPDATE {grade_grades}
 | 
        
           |  |  | 961 |                            SET aggregationstatus = 'used',
 | 
        
           |  |  | 962 |                                aggregationweight = :contribution
 | 
        
           |  |  | 963 |                          WHERE itemid = :itemid AND userid = :userid";
 | 
        
           |  |  | 964 |   | 
        
           |  |  | 965 |                 $params = array('contribution' => $contribution, 'itemid' => $gradeitemid, 'userid' => $userid);
 | 
        
           |  |  | 966 |                 $DB->execute($sql, $params);
 | 
        
           |  |  | 967 |             }
 | 
        
           |  |  | 968 |         }
 | 
        
           |  |  | 969 |   | 
        
           |  |  | 970 |         // Update items to 'novalue' status.
 | 
        
           |  |  | 971 |         if (!empty($toupdate['novalue'])) {
 | 
        
           |  |  | 972 |             list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['novalue'], SQL_PARAMS_NAMED, 'g');
 | 
        
           |  |  | 973 |   | 
        
           |  |  | 974 |             $itemlist['userid'] = $userid;
 | 
        
           |  |  | 975 |   | 
        
           |  |  | 976 |             $sql = "UPDATE {grade_grades}
 | 
        
           |  |  | 977 |                        SET aggregationstatus = 'novalue',
 | 
        
           |  |  | 978 |                            aggregationweight = 0
 | 
        
           |  |  | 979 |                      WHERE itemid $itemsql AND userid = :userid";
 | 
        
           |  |  | 980 |   | 
        
           |  |  | 981 |             $DB->execute($sql, $itemlist);
 | 
        
           |  |  | 982 |         }
 | 
        
           |  |  | 983 |   | 
        
           |  |  | 984 |         // Update items to 'dropped' status.
 | 
        
           |  |  | 985 |         if (!empty($toupdate['dropped'])) {
 | 
        
           |  |  | 986 |             list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['dropped'], SQL_PARAMS_NAMED, 'g');
 | 
        
           |  |  | 987 |   | 
        
           |  |  | 988 |             $itemlist['userid'] = $userid;
 | 
        
           |  |  | 989 |   | 
        
           |  |  | 990 |             $sql = "UPDATE {grade_grades}
 | 
        
           |  |  | 991 |                        SET aggregationstatus = 'dropped',
 | 
        
           |  |  | 992 |                            aggregationweight = 0
 | 
        
           |  |  | 993 |                      WHERE itemid $itemsql AND userid = :userid";
 | 
        
           |  |  | 994 |   | 
        
           |  |  | 995 |             $DB->execute($sql, $itemlist);
 | 
        
           |  |  | 996 |         }
 | 
        
           |  |  | 997 |   | 
        
           |  |  | 998 |         // Update items to 'extracredit' status.
 | 
        
           |  |  | 999 |         if (!empty($toupdate['extracredit'])) {
 | 
        
           |  |  | 1000 |             list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['extracredit'], SQL_PARAMS_NAMED, 'g');
 | 
        
           |  |  | 1001 |   | 
        
           |  |  | 1002 |             $itemlist['userid'] = $userid;
 | 
        
           |  |  | 1003 |   | 
        
           |  |  | 1004 |             $DB->set_field_select('grade_grades',
 | 
        
           |  |  | 1005 |                                   'aggregationstatus',
 | 
        
           |  |  | 1006 |                                   'extra',
 | 
        
           |  |  | 1007 |                                   "itemid $itemsql AND userid = :userid",
 | 
        
           |  |  | 1008 |                                   $itemlist);
 | 
        
           |  |  | 1009 |         }
 | 
        
           |  |  | 1010 |     }
 | 
        
           |  |  | 1011 |   | 
        
           |  |  | 1012 |     /**
 | 
        
           |  |  | 1013 |      * Internal function that calculates the aggregated grade and new min/max for this grade category
 | 
        
           |  |  | 1014 |      *
 | 
        
           |  |  | 1015 |      * Must be public as it is used by grade_grade::get_hiding_affected()
 | 
        
           |  |  | 1016 |      *
 | 
        
           |  |  | 1017 |      * @param array $grade_values An array of values to be aggregated
 | 
        
           |  |  | 1018 |      * @param array $items The array of grade_items
 | 
        
           |  |  | 1019 |      * @since Moodle 2.6.5, 2.7.2
 | 
        
           |  |  | 1020 |      * @param array & $weights If provided, will be filled with the normalized weights
 | 
        
           |  |  | 1021 |      *                         for each grade_item as used in the aggregation.
 | 
        
           |  |  | 1022 |      *                         Some rules for the weights are:
 | 
        
           |  |  | 1023 |      *                         1. The weights must add up to 1 (unless there are extra credit)
 | 
        
           |  |  | 1024 |      *                         2. The contributed points column must add up to the course
 | 
        
           |  |  | 1025 |      *                         final grade and this column is calculated from these weights.
 | 
        
           |  |  | 1026 |      * @param array  $grademinoverrides User specific grademin values if different to the grade_item grademin (key is itemid)
 | 
        
           |  |  | 1027 |      * @param array  $grademaxoverrides User specific grademax values if different to the grade_item grademax (key is itemid)
 | 
        
           |  |  | 1028 |      * @return array containing values for:
 | 
        
           |  |  | 1029 |      *                'grade' => the new calculated grade
 | 
        
           |  |  | 1030 |      *                'grademin' => the new calculated min grade for the category
 | 
        
           |  |  | 1031 |      *                'grademax' => the new calculated max grade for the category
 | 
        
           |  |  | 1032 |      */
 | 
        
           |  |  | 1033 |     public function aggregate_values_and_adjust_bounds($grade_values,
 | 
        
           |  |  | 1034 |                                                        $items,
 | 
        
           |  |  | 1035 |                                                        & $weights = null,
 | 
        
           |  |  | 1036 |                                                        $grademinoverrides = array(),
 | 
        
           |  |  | 1037 |                                                        $grademaxoverrides = array()) {
 | 
        
           |  |  | 1038 |         global $CFG;
 | 
        
           |  |  | 1039 |   | 
        
           |  |  | 1040 |         $category_item = $this->load_grade_item();
 | 
        
           |  |  | 1041 |         $grademin = $category_item->grademin;
 | 
        
           |  |  | 1042 |         $grademax = $category_item->grademax;
 | 
        
           |  |  | 1043 |   | 
        
           |  |  | 1044 |         switch ($this->aggregation) {
 | 
        
           |  |  | 1045 |   | 
        
           |  |  | 1046 |             case GRADE_AGGREGATE_MEDIAN: // Middle point value in the set: ignores frequencies
 | 
        
           |  |  | 1047 |                 $num = count($grade_values);
 | 
        
           |  |  | 1048 |                 $grades = array_values($grade_values);
 | 
        
           |  |  | 1049 |   | 
        
           |  |  | 1050 |                 // The median gets 100% - others get 0.
 | 
        
           |  |  | 1051 |                 if ($weights !== null && $num > 0) {
 | 
        
           |  |  | 1052 |                     $count = 0;
 | 
        
           |  |  | 1053 |                     foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1054 |                         if (($num % 2 == 0) && ($count == intval($num/2)-1 || $count == intval($num/2))) {
 | 
        
           |  |  | 1055 |                             $weights[$itemid] = 0.5;
 | 
        
           |  |  | 1056 |                         } else if (($num % 2 != 0) && ($count == intval(($num/2)-0.5))) {
 | 
        
           |  |  | 1057 |                             $weights[$itemid] = 1.0;
 | 
        
           |  |  | 1058 |                         } else {
 | 
        
           |  |  | 1059 |                             $weights[$itemid] = 0;
 | 
        
           |  |  | 1060 |                         }
 | 
        
           |  |  | 1061 |                         $count++;
 | 
        
           |  |  | 1062 |                     }
 | 
        
           |  |  | 1063 |                 }
 | 
        
           |  |  | 1064 |                 if ($num % 2 == 0) {
 | 
        
           |  |  | 1065 |                     $agg_grade = ($grades[intval($num/2)-1] + $grades[intval($num/2)]) / 2;
 | 
        
           |  |  | 1066 |                 } else {
 | 
        
           |  |  | 1067 |                     $agg_grade = $grades[intval(($num/2)-0.5)];
 | 
        
           |  |  | 1068 |                 }
 | 
        
           |  |  | 1069 |   | 
        
           |  |  | 1070 |                 break;
 | 
        
           |  |  | 1071 |   | 
        
           |  |  | 1072 |             case GRADE_AGGREGATE_MIN:
 | 
        
           |  |  | 1073 |                 $agg_grade = reset($grade_values);
 | 
        
           |  |  | 1074 |                 // Record the weights as used.
 | 
        
           |  |  | 1075 |                 if ($weights !== null) {
 | 
        
           |  |  | 1076 |                     foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1077 |                         $weights[$itemid] = 0;
 | 
        
           |  |  | 1078 |                     }
 | 
        
           |  |  | 1079 |                 }
 | 
        
           |  |  | 1080 |                 // Set the first item to 1.
 | 
        
           |  |  | 1081 |                 $itemids = array_keys($grade_values);
 | 
        
           |  |  | 1082 |                 $weights[reset($itemids)] = 1;
 | 
        
           |  |  | 1083 |                 break;
 | 
        
           |  |  | 1084 |   | 
        
           |  |  | 1085 |             case GRADE_AGGREGATE_MAX:
 | 
        
           |  |  | 1086 |                 // Record the weights as used.
 | 
        
           |  |  | 1087 |                 if ($weights !== null) {
 | 
        
           |  |  | 1088 |                     foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1089 |                         $weights[$itemid] = 0;
 | 
        
           |  |  | 1090 |                     }
 | 
        
           |  |  | 1091 |                 }
 | 
        
           |  |  | 1092 |                 // Set the last item to 1.
 | 
        
           |  |  | 1093 |                 $itemids = array_keys($grade_values);
 | 
        
           |  |  | 1094 |                 $weights[end($itemids)] = 1;
 | 
        
           |  |  | 1095 |                 $agg_grade = end($grade_values);
 | 
        
           |  |  | 1096 |                 break;
 | 
        
           |  |  | 1097 |   | 
        
           |  |  | 1098 |             case GRADE_AGGREGATE_MODE:       // the most common value
 | 
        
           |  |  | 1099 |                 // array_count_values only counts INT and STRING, so if grades are floats we must convert them to string
 | 
        
           |  |  | 1100 |                 $converted_grade_values = array();
 | 
        
           |  |  | 1101 |   | 
        
           |  |  | 1102 |                 foreach ($grade_values as $k => $gv) {
 | 
        
           |  |  | 1103 |   | 
        
           |  |  | 1104 |                     if (!is_int($gv) && !is_string($gv)) {
 | 
        
           |  |  | 1105 |                         $converted_grade_values[$k] = (string) $gv;
 | 
        
           |  |  | 1106 |   | 
        
           |  |  | 1107 |                     } else {
 | 
        
           |  |  | 1108 |                         $converted_grade_values[$k] = $gv;
 | 
        
           |  |  | 1109 |                     }
 | 
        
           |  |  | 1110 |                     if ($weights !== null) {
 | 
        
           |  |  | 1111 |                         $weights[$k] = 0;
 | 
        
           |  |  | 1112 |                     }
 | 
        
           |  |  | 1113 |                 }
 | 
        
           |  |  | 1114 |   | 
        
           |  |  | 1115 |                 $freq = array_count_values($converted_grade_values);
 | 
        
           |  |  | 1116 |                 arsort($freq);                      // sort by frequency keeping keys
 | 
        
           |  |  | 1117 |                 $top = reset($freq);               // highest frequency count
 | 
        
           |  |  | 1118 |                 $modes = moodle_array_keys_filter($freq, $top);  // Search for all modes (have the same highest count).
 | 
        
           |  |  | 1119 |                 rsort($modes, SORT_NUMERIC);       // get highest mode
 | 
        
           |  |  | 1120 |                 $agg_grade = reset($modes);
 | 
        
           |  |  | 1121 |                 // Record the weights as used.
 | 
        
           |  |  | 1122 |                 if ($weights !== null && $top > 0) {
 | 
        
           |  |  | 1123 |                     foreach ($grade_values as $k => $gv) {
 | 
        
           |  |  | 1124 |                         if ($gv == $agg_grade) {
 | 
        
           |  |  | 1125 |                             $weights[$k] = 1.0 / $top;
 | 
        
           |  |  | 1126 |                         }
 | 
        
           |  |  | 1127 |                     }
 | 
        
           |  |  | 1128 |                 }
 | 
        
           |  |  | 1129 |                 break;
 | 
        
           |  |  | 1130 |   | 
        
           |  |  | 1131 |             case GRADE_AGGREGATE_WEIGHTED_MEAN: // Weighted average of all existing final grades, weight specified in coef
 | 
        
           |  |  | 1132 |                 $weightsum = 0;
 | 
        
           |  |  | 1133 |                 $sum       = 0;
 | 
        
           |  |  | 1134 |   | 
        
           |  |  | 1135 |                 foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1136 |                     if ($weights !== null) {
 | 
        
           |  |  | 1137 |                         $weights[$itemid] = $items[$itemid]->aggregationcoef;
 | 
        
           |  |  | 1138 |                     }
 | 
        
           |  |  | 1139 |                     if ($items[$itemid]->aggregationcoef <= 0) {
 | 
        
           |  |  | 1140 |                         continue;
 | 
        
           |  |  | 1141 |                     }
 | 
        
           |  |  | 1142 |                     $weightsum += $items[$itemid]->aggregationcoef;
 | 
        
           |  |  | 1143 |                     $sum       += $items[$itemid]->aggregationcoef * $grade_value;
 | 
        
           |  |  | 1144 |                 }
 | 
        
           |  |  | 1145 |                 if ($weightsum == 0) {
 | 
        
           |  |  | 1146 |                     $agg_grade = null;
 | 
        
           |  |  | 1147 |   | 
        
           |  |  | 1148 |                 } else {
 | 
        
           |  |  | 1149 |                     $agg_grade = $sum / $weightsum;
 | 
        
           |  |  | 1150 |                     if ($weights !== null) {
 | 
        
           |  |  | 1151 |                         // Normalise the weights.
 | 
        
           |  |  | 1152 |                         foreach ($weights as $itemid => $weight) {
 | 
        
           |  |  | 1153 |                             $weights[$itemid] = $weight / $weightsum;
 | 
        
           |  |  | 1154 |                         }
 | 
        
           |  |  | 1155 |                     }
 | 
        
           |  |  | 1156 |   | 
        
           |  |  | 1157 |                 }
 | 
        
           |  |  | 1158 |                 break;
 | 
        
           |  |  | 1159 |   | 
        
           |  |  | 1160 |             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
 | 
        
           |  |  | 1161 |                 // Weighted average of all existing final grades with optional extra credit flag,
 | 
        
           |  |  | 1162 |                 // weight is the range of grade (usually grademax)
 | 
        
           |  |  | 1163 |                 $this->load_grade_item();
 | 
        
           |  |  | 1164 |                 $weightsum = 0;
 | 
        
           |  |  | 1165 |                 $sum       = null;
 | 
        
           |  |  | 1166 |   | 
        
           |  |  | 1167 |                 foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1168 |                     if ($items[$itemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1169 |                         continue;
 | 
        
           |  |  | 1170 |                     }
 | 
        
           |  |  | 1171 |   | 
        
           |  |  | 1172 |                     $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
 | 
        
           |  |  | 1173 |                     if ($weight <= 0) {
 | 
        
           |  |  | 1174 |                         continue;
 | 
        
           |  |  | 1175 |                     }
 | 
        
           |  |  | 1176 |   | 
        
           |  |  | 1177 |                     $weightsum += $weight;
 | 
        
           |  |  | 1178 |                     $sum += $weight * $grade_value;
 | 
        
           |  |  | 1179 |                 }
 | 
        
           |  |  | 1180 |   | 
        
           |  |  | 1181 |                 // Handle the extra credit items separately to calculate their weight accurately.
 | 
        
           |  |  | 1182 |                 foreach ($grade_values as $itemid => $grade_value) {
 | 
        
           |  |  | 1183 |                     if ($items[$itemid]->aggregationcoef <= 0) {
 | 
        
           |  |  | 1184 |                         continue;
 | 
        
           |  |  | 1185 |                     }
 | 
        
           |  |  | 1186 |   | 
        
           |  |  | 1187 |                     $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
 | 
        
           |  |  | 1188 |                     if ($weight <= 0) {
 | 
        
           |  |  | 1189 |                         $weights[$itemid] = 0;
 | 
        
           |  |  | 1190 |                         continue;
 | 
        
           |  |  | 1191 |                     }
 | 
        
           |  |  | 1192 |   | 
        
           |  |  | 1193 |                     $oldsum = $sum;
 | 
        
           |  |  | 1194 |                     $weightedgrade = $weight * $grade_value;
 | 
        
           |  |  | 1195 |                     $sum += $weightedgrade;
 | 
        
           |  |  | 1196 |   | 
        
           |  |  | 1197 |                     if ($weights !== null) {
 | 
        
           |  |  | 1198 |                         if ($weightsum <= 0) {
 | 
        
           |  |  | 1199 |                             $weights[$itemid] = 0;
 | 
        
           |  |  | 1200 |                             continue;
 | 
        
           |  |  | 1201 |                         }
 | 
        
           |  |  | 1202 |   | 
        
           |  |  | 1203 |                         $oldgrade = $oldsum / $weightsum;
 | 
        
           |  |  | 1204 |                         $grade = $sum / $weightsum;
 | 
        
           |  |  | 1205 |                         $normoldgrade = grade_grade::standardise_score($oldgrade, 0, 1, $grademin, $grademax);
 | 
        
           |  |  | 1206 |                         $normgrade = grade_grade::standardise_score($grade, 0, 1, $grademin, $grademax);
 | 
        
           |  |  | 1207 |                         $boundedoldgrade = $this->grade_item->bounded_grade($normoldgrade);
 | 
        
           |  |  | 1208 |                         $boundedgrade = $this->grade_item->bounded_grade($normgrade);
 | 
        
           |  |  | 1209 |   | 
        
           |  |  | 1210 |                         if ($boundedgrade - $boundedoldgrade <= 0) {
 | 
        
           |  |  | 1211 |                             // Nothing new was added to the grade.
 | 
        
           |  |  | 1212 |                             $weights[$itemid] = 0;
 | 
        
           |  |  | 1213 |                         } else if ($boundedgrade < $normgrade) {
 | 
        
           |  |  | 1214 |                             // The grade has been bounded, the extra credit item needs to have a different weight.
 | 
        
           |  |  | 1215 |                             $gradediff = $boundedgrade - $normoldgrade;
 | 
        
           |  |  | 1216 |                             $gradediffnorm = grade_grade::standardise_score($gradediff, $grademin, $grademax, 0, 1);
 | 
        
           |  |  | 1217 |                             $weights[$itemid] = $gradediffnorm / $grade_value;
 | 
        
           |  |  | 1218 |                         } else {
 | 
        
           |  |  | 1219 |                             // Default weighting.
 | 
        
           |  |  | 1220 |                             $weights[$itemid] = $weight / $weightsum;
 | 
        
           |  |  | 1221 |                         }
 | 
        
           |  |  | 1222 |                     }
 | 
        
           |  |  | 1223 |                 }
 | 
        
           |  |  | 1224 |   | 
        
           |  |  | 1225 |                 if ($weightsum == 0) {
 | 
        
           |  |  | 1226 |                     $agg_grade = $sum; // only extra credits
 | 
        
           |  |  | 1227 |   | 
        
           |  |  | 1228 |                 } else {
 | 
        
           |  |  | 1229 |                     $agg_grade = $sum / $weightsum;
 | 
        
           |  |  | 1230 |                 }
 | 
        
           |  |  | 1231 |   | 
        
           |  |  | 1232 |                 // Record the weights as used.
 | 
        
           |  |  | 1233 |                 if ($weights !== null) {
 | 
        
           |  |  | 1234 |                     foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1235 |                         if ($items[$itemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1236 |                             // Ignore extra credit items, the weights have already been computed.
 | 
        
           |  |  | 1237 |                             continue;
 | 
        
           |  |  | 1238 |                         }
 | 
        
           |  |  | 1239 |                         if ($weightsum > 0) {
 | 
        
           |  |  | 1240 |                             $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
 | 
        
           |  |  | 1241 |                             $weights[$itemid] = $weight / $weightsum;
 | 
        
           |  |  | 1242 |                         } else {
 | 
        
           |  |  | 1243 |                             $weights[$itemid] = 0;
 | 
        
           |  |  | 1244 |                         }
 | 
        
           |  |  | 1245 |                     }
 | 
        
           |  |  | 1246 |                 }
 | 
        
           |  |  | 1247 |                 break;
 | 
        
           |  |  | 1248 |   | 
        
           |  |  | 1249 |             case GRADE_AGGREGATE_EXTRACREDIT_MEAN: // special average
 | 
        
           |  |  | 1250 |                 $this->load_grade_item();
 | 
        
           |  |  | 1251 |                 $num = 0;
 | 
        
           |  |  | 1252 |                 $sum = null;
 | 
        
           |  |  | 1253 |   | 
        
           |  |  | 1254 |                 foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1255 |                     if ($items[$itemid]->aggregationcoef == 0) {
 | 
        
           |  |  | 1256 |                         $num += 1;
 | 
        
           |  |  | 1257 |                         $sum += $grade_value;
 | 
        
           |  |  | 1258 |                         if ($weights !== null) {
 | 
        
           |  |  | 1259 |                             $weights[$itemid] = 1;
 | 
        
           |  |  | 1260 |                         }
 | 
        
           |  |  | 1261 |                     }
 | 
        
           |  |  | 1262 |                 }
 | 
        
           |  |  | 1263 |   | 
        
           |  |  | 1264 |                 // Treating the extra credit items separately to get a chance to calculate their effective weights.
 | 
        
           |  |  | 1265 |                 foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1266 |                     if ($items[$itemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1267 |                         $oldsum = $sum;
 | 
        
           |  |  | 1268 |                         $sum += $items[$itemid]->aggregationcoef * $grade_value;
 | 
        
           |  |  | 1269 |   | 
        
           |  |  | 1270 |                         if ($weights !== null) {
 | 
        
           |  |  | 1271 |                             if ($num <= 0) {
 | 
        
           |  |  | 1272 |                                 // The category only contains extra credit items, not setting the weight.
 | 
        
           |  |  | 1273 |                                 continue;
 | 
        
           |  |  | 1274 |                             }
 | 
        
           |  |  | 1275 |   | 
        
           |  |  | 1276 |                             $oldgrade = $oldsum / $num;
 | 
        
           |  |  | 1277 |                             $grade = $sum / $num;
 | 
        
           |  |  | 1278 |                             $normoldgrade = grade_grade::standardise_score($oldgrade, 0, 1, $grademin, $grademax);
 | 
        
           |  |  | 1279 |                             $normgrade = grade_grade::standardise_score($grade, 0, 1, $grademin, $grademax);
 | 
        
           |  |  | 1280 |                             $boundedoldgrade = $this->grade_item->bounded_grade($normoldgrade);
 | 
        
           |  |  | 1281 |                             $boundedgrade = $this->grade_item->bounded_grade($normgrade);
 | 
        
           |  |  | 1282 |   | 
        
           |  |  | 1283 |                             if ($boundedgrade - $boundedoldgrade <= 0) {
 | 
        
           |  |  | 1284 |                                 // Nothing new was added to the grade.
 | 
        
           |  |  | 1285 |                                 $weights[$itemid] = 0;
 | 
        
           |  |  | 1286 |                             } else if ($boundedgrade < $normgrade) {
 | 
        
           |  |  | 1287 |                                 // The grade has been bounded, the extra credit item needs to have a different weight.
 | 
        
           |  |  | 1288 |                                 $gradediff = $boundedgrade - $normoldgrade;
 | 
        
           |  |  | 1289 |                                 $gradediffnorm = grade_grade::standardise_score($gradediff, $grademin, $grademax, 0, 1);
 | 
        
           |  |  | 1290 |                                 $weights[$itemid] = $gradediffnorm / $grade_value;
 | 
        
           |  |  | 1291 |                             } else {
 | 
        
           |  |  | 1292 |                                 // Default weighting.
 | 
        
           |  |  | 1293 |                                 $weights[$itemid] = 1.0 / $num;
 | 
        
           |  |  | 1294 |                             }
 | 
        
           |  |  | 1295 |                         }
 | 
        
           |  |  | 1296 |                     }
 | 
        
           |  |  | 1297 |                 }
 | 
        
           |  |  | 1298 |   | 
        
           |  |  | 1299 |                 if ($weights !== null && $num > 0) {
 | 
        
           |  |  | 1300 |                     foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1301 |                         if ($items[$itemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1302 |                             // Extra credit weights were already calculated.
 | 
        
           |  |  | 1303 |                             continue;
 | 
        
           |  |  | 1304 |                         }
 | 
        
           |  |  | 1305 |                         if ($weights[$itemid]) {
 | 
        
           |  |  | 1306 |                             $weights[$itemid] = 1.0 / $num;
 | 
        
           |  |  | 1307 |                         }
 | 
        
           |  |  | 1308 |                     }
 | 
        
           |  |  | 1309 |                 }
 | 
        
           |  |  | 1310 |   | 
        
           |  |  | 1311 |                 if ($num == 0) {
 | 
        
           |  |  | 1312 |                     $agg_grade = $sum; // only extra credits or wrong coefs
 | 
        
           |  |  | 1313 |   | 
        
           |  |  | 1314 |                 } else {
 | 
        
           |  |  | 1315 |                     $agg_grade = $sum / $num;
 | 
        
           |  |  | 1316 |                 }
 | 
        
           |  |  | 1317 |   | 
        
           |  |  | 1318 |                 break;
 | 
        
           |  |  | 1319 |   | 
        
           |  |  | 1320 |             case GRADE_AGGREGATE_SUM:    // Add up all the items.
 | 
        
           |  |  | 1321 |                 $this->load_grade_item();
 | 
        
           |  |  | 1322 |                 $num = count($grade_values);
 | 
        
           |  |  | 1323 |                 $sum = 0;
 | 
        
           |  |  | 1324 |   | 
        
           |  |  | 1325 |                 // This setting indicates if we should use algorithm prior to MDL-49257 fix for calculating extra credit weights.
 | 
        
           |  |  | 1326 |                 // Even though old algorith has bugs in it, we need to preserve existing grades.
 | 
        
           |  |  | 1327 |                 $gradebookcalculationfreeze = 'gradebook_calculations_freeze_' . $this->courseid;
 | 
        
           |  |  | 1328 |                 $oldextracreditcalculation = isset($CFG->$gradebookcalculationfreeze)
 | 
        
           |  |  | 1329 |                         && ($CFG->$gradebookcalculationfreeze <= 20150619);
 | 
        
           |  |  | 1330 |   | 
        
           |  |  | 1331 |                 $sumweights = 0;
 | 
        
           |  |  | 1332 |                 $grademin = 0;
 | 
        
           |  |  | 1333 |                 $grademax = 0;
 | 
        
           |  |  | 1334 |                 $extracredititems = array();
 | 
        
           |  |  | 1335 |                 foreach ($grade_values as $itemid => $gradevalue) {
 | 
        
           |  |  | 1336 |                     // We need to check if the grademax/min was adjusted per user because of excluded items.
 | 
        
           |  |  | 1337 |                     $usergrademin = $items[$itemid]->grademin;
 | 
        
           |  |  | 1338 |                     $usergrademax = $items[$itemid]->grademax;
 | 
        
           |  |  | 1339 |                     if (isset($grademinoverrides[$itemid])) {
 | 
        
           |  |  | 1340 |                         $usergrademin = $grademinoverrides[$itemid];
 | 
        
           |  |  | 1341 |                     }
 | 
        
           |  |  | 1342 |                     if (isset($grademaxoverrides[$itemid])) {
 | 
        
           |  |  | 1343 |                         $usergrademax = $grademaxoverrides[$itemid];
 | 
        
           |  |  | 1344 |                     }
 | 
        
           |  |  | 1345 |   | 
        
           |  |  | 1346 |                     // Keep track of the extra credit items, we will need them later on.
 | 
        
           |  |  | 1347 |                     if ($items[$itemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1348 |                         $extracredititems[$itemid] = $items[$itemid];
 | 
        
           |  |  | 1349 |                     }
 | 
        
           |  |  | 1350 |   | 
        
           |  |  | 1351 |                     // Ignore extra credit and items with a weight of 0.
 | 
        
           |  |  | 1352 |                     if (!isset($extracredititems[$itemid]) && $items[$itemid]->aggregationcoef2 > 0) {
 | 
        
           |  |  | 1353 |                         $grademin += $usergrademin;
 | 
        
           |  |  | 1354 |                         $grademax += $usergrademax;
 | 
        
           |  |  | 1355 |                         $sumweights += $items[$itemid]->aggregationcoef2;
 | 
        
           |  |  | 1356 |                     }
 | 
        
           |  |  | 1357 |                 }
 | 
        
           |  |  | 1358 |                 $userweights = array();
 | 
        
           |  |  | 1359 |                 $totaloverriddenweight = 0;
 | 
        
           |  |  | 1360 |                 $totaloverriddengrademax = 0;
 | 
        
           |  |  | 1361 |                 // We first need to rescale all manually assigned weights down by the
 | 
        
           |  |  | 1362 |                 // percentage of weights missing from the category.
 | 
        
           |  |  | 1363 |                 foreach ($grade_values as $itemid => $gradevalue) {
 | 
        
           |  |  | 1364 |                     if ($items[$itemid]->weightoverride) {
 | 
        
           |  |  | 1365 |                         if ($items[$itemid]->aggregationcoef2 <= 0) {
 | 
        
           |  |  | 1366 |                             // Records the weight of 0 and continue.
 | 
        
           |  |  | 1367 |                             $userweights[$itemid] = 0;
 | 
        
           |  |  | 1368 |                             continue;
 | 
        
           |  |  | 1369 |                         }
 | 
        
           |  |  | 1370 |                         $userweights[$itemid] = $sumweights ? ($items[$itemid]->aggregationcoef2 / $sumweights) : 0;
 | 
        
           |  |  | 1371 |                         if (!$oldextracreditcalculation && isset($extracredititems[$itemid])) {
 | 
        
           |  |  | 1372 |                             // Extra credit items do not affect totals.
 | 
        
           |  |  | 1373 |                             continue;
 | 
        
           |  |  | 1374 |                         }
 | 
        
           |  |  | 1375 |                         $totaloverriddenweight += $userweights[$itemid];
 | 
        
           |  |  | 1376 |                         $usergrademax = $items[$itemid]->grademax;
 | 
        
           |  |  | 1377 |                         if (isset($grademaxoverrides[$itemid])) {
 | 
        
           |  |  | 1378 |                             $usergrademax = $grademaxoverrides[$itemid];
 | 
        
           |  |  | 1379 |                         }
 | 
        
           |  |  | 1380 |                         $totaloverriddengrademax += $usergrademax;
 | 
        
           |  |  | 1381 |                     }
 | 
        
           |  |  | 1382 |                 }
 | 
        
           |  |  | 1383 |                 $nonoverriddenpoints = $grademax - $totaloverriddengrademax;
 | 
        
           |  |  | 1384 |   | 
        
           |  |  | 1385 |                 // Then we need to recalculate the automatic weights except for extra credit items.
 | 
        
           |  |  | 1386 |                 foreach ($grade_values as $itemid => $gradevalue) {
 | 
        
           |  |  | 1387 |                     if (!$items[$itemid]->weightoverride && ($oldextracreditcalculation || !isset($extracredititems[$itemid]))) {
 | 
        
           |  |  | 1388 |                         $usergrademax = $items[$itemid]->grademax;
 | 
        
           |  |  | 1389 |                         if (isset($grademaxoverrides[$itemid])) {
 | 
        
           |  |  | 1390 |                             $usergrademax = $grademaxoverrides[$itemid];
 | 
        
           |  |  | 1391 |                         }
 | 
        
           |  |  | 1392 |                         if ($nonoverriddenpoints > 0) {
 | 
        
           |  |  | 1393 |                             $userweights[$itemid] = ($usergrademax/$nonoverriddenpoints) * (1 - $totaloverriddenweight);
 | 
        
           |  |  | 1394 |                         } else {
 | 
        
           |  |  | 1395 |                             $userweights[$itemid] = 0;
 | 
        
           |  |  | 1396 |                             if ($items[$itemid]->aggregationcoef2 > 0) {
 | 
        
           |  |  | 1397 |                                 // Items with a weight of 0 should not count for the grade max,
 | 
        
           |  |  | 1398 |                                 // though this only applies if the weight was changed to 0.
 | 
        
           |  |  | 1399 |                                 $grademax -= $usergrademax;
 | 
        
           |  |  | 1400 |                             }
 | 
        
           |  |  | 1401 |                         }
 | 
        
           |  |  | 1402 |                     }
 | 
        
           |  |  | 1403 |                 }
 | 
        
           |  |  | 1404 |   | 
        
           |  |  | 1405 |                 // Now when we finally know the grademax we can adjust the automatic weights of extra credit items.
 | 
        
           |  |  | 1406 |                 if (!$oldextracreditcalculation) {
 | 
        
           |  |  | 1407 |                     foreach ($grade_values as $itemid => $gradevalue) {
 | 
        
           |  |  | 1408 |                         if (!$items[$itemid]->weightoverride && isset($extracredititems[$itemid])) {
 | 
        
           |  |  | 1409 |                             $usergrademax = $items[$itemid]->grademax;
 | 
        
           |  |  | 1410 |                             if (isset($grademaxoverrides[$itemid])) {
 | 
        
           |  |  | 1411 |                                 $usergrademax = $grademaxoverrides[$itemid];
 | 
        
           |  |  | 1412 |                             }
 | 
        
           |  |  | 1413 |                             $userweights[$itemid] = $grademax ? ($usergrademax / $grademax) : 0;
 | 
        
           |  |  | 1414 |                         }
 | 
        
           |  |  | 1415 |                     }
 | 
        
           |  |  | 1416 |                 }
 | 
        
           |  |  | 1417 |   | 
        
           |  |  | 1418 |                 // We can use our freshly corrected weights below.
 | 
        
           |  |  | 1419 |                 foreach ($grade_values as $itemid => $gradevalue) {
 | 
        
           |  |  | 1420 |                     if (isset($extracredititems[$itemid])) {
 | 
        
           |  |  | 1421 |                         // We skip the extra credit items first.
 | 
        
           |  |  | 1422 |                         continue;
 | 
        
           |  |  | 1423 |                     }
 | 
        
           |  |  | 1424 |                     $sum += $gradevalue * $userweights[$itemid] * $grademax;
 | 
        
           |  |  | 1425 |                     if ($weights !== null) {
 | 
        
           |  |  | 1426 |                         $weights[$itemid] = $userweights[$itemid];
 | 
        
           |  |  | 1427 |                     }
 | 
        
           |  |  | 1428 |                 }
 | 
        
           |  |  | 1429 |   | 
        
           |  |  | 1430 |                 // No we proceed with the extra credit items. They might have a different final
 | 
        
           |  |  | 1431 |                 // weight in case the final grade was bounded. So we need to treat them different.
 | 
        
           |  |  | 1432 |                 // Also, as we need to use the bounded_grade() method, we have to inject the
 | 
        
           |  |  | 1433 |                 // right values there, and restore them afterwards.
 | 
        
           |  |  | 1434 |                 $oldgrademax = $this->grade_item->grademax;
 | 
        
           |  |  | 1435 |                 $oldgrademin = $this->grade_item->grademin;
 | 
        
           |  |  | 1436 |                 foreach ($grade_values as $itemid => $gradevalue) {
 | 
        
           |  |  | 1437 |                     if (!isset($extracredititems[$itemid])) {
 | 
        
           |  |  | 1438 |                         continue;
 | 
        
           |  |  | 1439 |                     }
 | 
        
           |  |  | 1440 |                     $oldsum = $sum;
 | 
        
           |  |  | 1441 |                     $weightedgrade = $gradevalue * $userweights[$itemid] * $grademax;
 | 
        
           |  |  | 1442 |                     $sum += $weightedgrade;
 | 
        
           |  |  | 1443 |   | 
        
           |  |  | 1444 |                     // Only go through this when we need to record the weights.
 | 
        
           |  |  | 1445 |                     if ($weights !== null) {
 | 
        
           |  |  | 1446 |                         if ($grademax <= 0) {
 | 
        
           |  |  | 1447 |                             // There are only extra credit items in this category,
 | 
        
           |  |  | 1448 |                             // all the weights should be accurate (and be 0).
 | 
        
           |  |  | 1449 |                             $weights[$itemid] = $userweights[$itemid];
 | 
        
           |  |  | 1450 |                             continue;
 | 
        
           |  |  | 1451 |                         }
 | 
        
           |  |  | 1452 |   | 
        
           |  |  | 1453 |                         $oldfinalgrade = $this->grade_item->bounded_grade($oldsum);
 | 
        
           |  |  | 1454 |                         $newfinalgrade = $this->grade_item->bounded_grade($sum);
 | 
        
           |  |  | 1455 |                         $finalgradediff = $newfinalgrade - $oldfinalgrade;
 | 
        
           |  |  | 1456 |                         if ($finalgradediff <= 0) {
 | 
        
           |  |  | 1457 |                             // This item did not contribute to the category total at all.
 | 
        
           |  |  | 1458 |                             $weights[$itemid] = 0;
 | 
        
           |  |  | 1459 |                         } else if ($finalgradediff < $weightedgrade) {
 | 
        
           |  |  | 1460 |                             // The weight needs to be adjusted because only a portion of the
 | 
        
           |  |  | 1461 |                             // extra credit item contributed to the category total.
 | 
        
           |  |  | 1462 |                             $weights[$itemid] = $finalgradediff / ($gradevalue * $grademax);
 | 
        
           |  |  | 1463 |                         } else {
 | 
        
           |  |  | 1464 |                             // The weight was accurate.
 | 
        
           |  |  | 1465 |                             $weights[$itemid] = $userweights[$itemid];
 | 
        
           |  |  | 1466 |                         }
 | 
        
           |  |  | 1467 |                     }
 | 
        
           |  |  | 1468 |                 }
 | 
        
           |  |  | 1469 |                 $this->grade_item->grademax = $oldgrademax;
 | 
        
           |  |  | 1470 |                 $this->grade_item->grademin = $oldgrademin;
 | 
        
           |  |  | 1471 |   | 
        
           |  |  | 1472 |                 if ($grademax > 0) {
 | 
        
           |  |  | 1473 |                     $agg_grade = $sum / $grademax; // Re-normalize score.
 | 
        
           |  |  | 1474 |                 } else {
 | 
        
           |  |  | 1475 |                     // Every item in the category is extra credit.
 | 
        
           |  |  | 1476 |                     $agg_grade = $sum;
 | 
        
           |  |  | 1477 |                     $grademax = $sum;
 | 
        
           |  |  | 1478 |                 }
 | 
        
           |  |  | 1479 |   | 
        
           |  |  | 1480 |                 break;
 | 
        
           |  |  | 1481 |   | 
        
           |  |  | 1482 |             case GRADE_AGGREGATE_MEAN:    // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
 | 
        
           |  |  | 1483 |             default:
 | 
        
           |  |  | 1484 |                 $num = count($grade_values);
 | 
        
           |  |  | 1485 |                 $sum = array_sum($grade_values);
 | 
        
           |  |  | 1486 |                 $agg_grade = $sum / $num;
 | 
        
           |  |  | 1487 |                 // Record the weights evenly.
 | 
        
           |  |  | 1488 |                 if ($weights !== null && $num > 0) {
 | 
        
           |  |  | 1489 |                     foreach ($grade_values as $itemid=>$grade_value) {
 | 
        
           |  |  | 1490 |                         $weights[$itemid] = 1.0 / $num;
 | 
        
           |  |  | 1491 |                     }
 | 
        
           |  |  | 1492 |                 }
 | 
        
           |  |  | 1493 |                 break;
 | 
        
           |  |  | 1494 |         }
 | 
        
           |  |  | 1495 |   | 
        
           |  |  | 1496 |         return array('grade' => $agg_grade, 'grademin' => $grademin, 'grademax' => $grademax);
 | 
        
           |  |  | 1497 |     }
 | 
        
           |  |  | 1498 |   | 
        
           |  |  | 1499 |     /**
 | 
        
           |  |  | 1500 |      * Internal function that calculates the aggregated grade for this grade category
 | 
        
           |  |  | 1501 |      *
 | 
        
           |  |  | 1502 |      * Must be public as it is used by grade_grade::get_hiding_affected()
 | 
        
           |  |  | 1503 |      *
 | 
        
           |  |  | 1504 |      * @deprecated since Moodle 2.8
 | 
        
           |  |  | 1505 |      * @param array $grade_values An array of values to be aggregated
 | 
        
           |  |  | 1506 |      * @param array $items The array of grade_items
 | 
        
           |  |  | 1507 |      * @return float The aggregate grade for this grade category
 | 
        
           |  |  | 1508 |      */
 | 
        
           |  |  | 1509 |     public function aggregate_values($grade_values, $items) {
 | 
        
           |  |  | 1510 |         debugging('grade_category::aggregate_values() is deprecated.
 | 
        
           |  |  | 1511 |                    Call grade_category::aggregate_values_and_adjust_bounds() instead.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 1512 |         $result = $this->aggregate_values_and_adjust_bounds($grade_values, $items);
 | 
        
           |  |  | 1513 |         return $result['grade'];
 | 
        
           |  |  | 1514 |     }
 | 
        
           |  |  | 1515 |   | 
        
           |  |  | 1516 |     /**
 | 
        
           |  |  | 1517 |      * Some aggregation types may need to update their max grade.
 | 
        
           |  |  | 1518 |      *
 | 
        
           |  |  | 1519 |      * This must be executed after updating the weights as it relies on them.
 | 
        
           |  |  | 1520 |      *
 | 
        
           |  |  | 1521 |      * @return void
 | 
        
           |  |  | 1522 |      */
 | 
        
           |  |  | 1523 |     private function auto_update_max() {
 | 
        
           |  |  | 1524 |         global $CFG, $DB;
 | 
        
           |  |  | 1525 |         if ($this->aggregation != GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 1526 |             // not needed at all
 | 
        
           |  |  | 1527 |             return;
 | 
        
           |  |  | 1528 |         }
 | 
        
           |  |  | 1529 |   | 
        
           |  |  | 1530 |         // Find grade items of immediate children (category or grade items) and force site settings.
 | 
        
           |  |  | 1531 |         $this->load_grade_item();
 | 
        
           |  |  | 1532 |         $depends_on = $this->grade_item->depends_on();
 | 
        
           |  |  | 1533 |   | 
        
           |  |  | 1534 |         // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they
 | 
        
           |  |  | 1535 |         // wish to update the grades.
 | 
        
           |  |  | 1536 |         $gradebookcalculationfreeze = 'gradebook_calculations_freeze_' . $this->courseid;
 | 
        
           |  |  | 1537 |         $oldextracreditcalculation = isset($CFG->$gradebookcalculationfreeze) && ($CFG->$gradebookcalculationfreeze <= 20150627);
 | 
        
           |  |  | 1538 |         // Only run if the gradebook isn't frozen.
 | 
        
           |  |  | 1539 |         if (!$oldextracreditcalculation) {
 | 
        
           |  |  | 1540 |             // Don't automatically update the max for calculated items.
 | 
        
           |  |  | 1541 |             if ($this->grade_item->is_calculated()) {
 | 
        
           |  |  | 1542 |                 return;
 | 
        
           |  |  | 1543 |             }
 | 
        
           |  |  | 1544 |         }
 | 
        
           |  |  | 1545 |   | 
        
           |  |  | 1546 |         $items = false;
 | 
        
           |  |  | 1547 |         if (!empty($depends_on)) {
 | 
        
           |  |  | 1548 |             list($usql, $params) = $DB->get_in_or_equal($depends_on);
 | 
        
           |  |  | 1549 |             $sql = "SELECT *
 | 
        
           |  |  | 1550 |                       FROM {grade_items}
 | 
        
           |  |  | 1551 |                      WHERE id $usql";
 | 
        
           |  |  | 1552 |             $items = $DB->get_records_sql($sql, $params);
 | 
        
           |  |  | 1553 |         }
 | 
        
           |  |  | 1554 |   | 
        
           |  |  | 1555 |         if (!$items) {
 | 
        
           |  |  | 1556 |   | 
        
           |  |  | 1557 |             if ($this->grade_item->grademax != 0 or $this->grade_item->gradetype != GRADE_TYPE_VALUE) {
 | 
        
           |  |  | 1558 |                 $this->grade_item->grademax  = 0;
 | 
        
           |  |  | 1559 |                 $this->grade_item->grademin  = 0;
 | 
        
           |  |  | 1560 |                 $this->grade_item->gradetype = GRADE_TYPE_VALUE;
 | 
        
           |  |  | 1561 |                 $this->grade_item->update('aggregation');
 | 
        
           |  |  | 1562 |             }
 | 
        
           |  |  | 1563 |             return;
 | 
        
           |  |  | 1564 |         }
 | 
        
           |  |  | 1565 |   | 
        
           |  |  | 1566 |         //find max grade possible
 | 
        
           |  |  | 1567 |         $maxes = array();
 | 
        
           |  |  | 1568 |   | 
        
           |  |  | 1569 |         foreach ($items as $item) {
 | 
        
           |  |  | 1570 |   | 
        
           |  |  | 1571 |             if ($item->aggregationcoef > 0) {
 | 
        
           |  |  | 1572 |                 // extra credit from this activity - does not affect total
 | 
        
           |  |  | 1573 |                 continue;
 | 
        
           |  |  | 1574 |             } else if ($item->aggregationcoef2 <= 0) {
 | 
        
           |  |  | 1575 |                 // Items with a weight of 0 do not affect the total.
 | 
        
           |  |  | 1576 |                 continue;
 | 
        
           |  |  | 1577 |             }
 | 
        
           |  |  | 1578 |   | 
        
           |  |  | 1579 |             if ($item->gradetype == GRADE_TYPE_VALUE) {
 | 
        
           |  |  | 1580 |                 $maxes[$item->id] = $item->grademax;
 | 
        
           |  |  | 1581 |   | 
        
           |  |  | 1582 |             } else if ($item->gradetype == GRADE_TYPE_SCALE) {
 | 
        
           |  |  | 1583 |                 $maxes[$item->id] = $item->grademax; // 0 = nograde, 1 = first scale item, 2 = second scale item
 | 
        
           |  |  | 1584 |             }
 | 
        
           |  |  | 1585 |         }
 | 
        
           |  |  | 1586 |   | 
        
           |  |  | 1587 |         if ($this->can_apply_limit_rules()) {
 | 
        
           |  |  | 1588 |             // Apply droplow and keephigh.
 | 
        
           |  |  | 1589 |             $this->apply_limit_rules($maxes, $items);
 | 
        
           |  |  | 1590 |         }
 | 
        
           |  |  | 1591 |         $max = array_sum($maxes);
 | 
        
           |  |  | 1592 |   | 
        
           |  |  | 1593 |         // update db if anything changed
 | 
        
           |  |  | 1594 |         if ($this->grade_item->grademax != $max or $this->grade_item->grademin != 0 or $this->grade_item->gradetype != GRADE_TYPE_VALUE) {
 | 
        
           |  |  | 1595 |             $this->grade_item->grademax  = $max;
 | 
        
           |  |  | 1596 |             $this->grade_item->grademin  = 0;
 | 
        
           |  |  | 1597 |             $this->grade_item->gradetype = GRADE_TYPE_VALUE;
 | 
        
           |  |  | 1598 |             $this->grade_item->update('aggregation');
 | 
        
           |  |  | 1599 |         }
 | 
        
           |  |  | 1600 |     }
 | 
        
           |  |  | 1601 |   | 
        
           |  |  | 1602 |     /**
 | 
        
           |  |  | 1603 |      * Recalculate the weights of the grade items in this category.
 | 
        
           |  |  | 1604 |      *
 | 
        
           |  |  | 1605 |      * The category total is not updated here, a further call to
 | 
        
           |  |  | 1606 |      * {@link self::auto_update_max()} is required.
 | 
        
           |  |  | 1607 |      *
 | 
        
           |  |  | 1608 |      * @return void
 | 
        
           |  |  | 1609 |      */
 | 
        
           |  |  | 1610 |     private function auto_update_weights() {
 | 
        
           |  |  | 1611 |         global $CFG;
 | 
        
           |  |  | 1612 |         if ($this->aggregation != GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 1613 |             // This is only required if we are using natural weights.
 | 
        
           |  |  | 1614 |             return;
 | 
        
           |  |  | 1615 |         }
 | 
        
           |  |  | 1616 |         $children = $this->get_children();
 | 
        
           |  |  | 1617 |   | 
        
           |  |  | 1618 |         $gradeitem = null;
 | 
        
           |  |  | 1619 |   | 
        
           |  |  | 1620 |         // Calculate the sum of the grademax's of all the items within this category.
 | 
        
           |  |  | 1621 |         $totalnonoverriddengrademax = 0;
 | 
        
           |  |  | 1622 |         $totalgrademax = 0;
 | 
        
           |  |  | 1623 |   | 
        
           |  |  | 1624 |         // Out of 1, how much weight has been manually overriden by a user?
 | 
        
           |  |  | 1625 |         $totaloverriddenweight  = 0;
 | 
        
           |  |  | 1626 |         $totaloverriddengrademax  = 0;
 | 
        
           |  |  | 1627 |   | 
        
           |  |  | 1628 |         // Has every assessment in this category been overridden?
 | 
        
           |  |  | 1629 |         $automaticgradeitemspresent = false;
 | 
        
           |  |  | 1630 |         // Does the grade item require normalising?
 | 
        
           |  |  | 1631 |         $requiresnormalising = false;
 | 
        
           |  |  | 1632 |   | 
        
           |  |  | 1633 |         // This array keeps track of the id and weight of every grade item that has been overridden.
 | 
        
           |  |  | 1634 |         $overridearray = array();
 | 
        
           |  |  | 1635 |         foreach ($children as $sortorder => $child) {
 | 
        
           |  |  | 1636 |             $gradeitem = null;
 | 
        
           |  |  | 1637 |   | 
        
           |  |  | 1638 |             if ($child['type'] == 'item') {
 | 
        
           |  |  | 1639 |                 $gradeitem = $child['object'];
 | 
        
           |  |  | 1640 |             } else if ($child['type'] == 'category') {
 | 
        
           |  |  | 1641 |                 $gradeitem = $child['object']->load_grade_item();
 | 
        
           |  |  | 1642 |             }
 | 
        
           |  |  | 1643 |   | 
        
           |  |  | 1644 |             if ($gradeitem->gradetype == GRADE_TYPE_NONE || $gradeitem->gradetype == GRADE_TYPE_TEXT) {
 | 
        
           |  |  | 1645 |                 // Text items and none items do not have a weight.
 | 
        
           |  |  | 1646 |                 continue;
 | 
        
           |  |  | 1647 |             } else if (!$this->aggregateoutcomes && $gradeitem->is_outcome_item()) {
 | 
        
           |  |  | 1648 |                 // We will not aggregate outcome items, so we can ignore them.
 | 
        
           |  |  | 1649 |                 continue;
 | 
        
           |  |  | 1650 |             } else if (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE) {
 | 
        
           |  |  | 1651 |                 // The scales are not included in the aggregation, ignore them.
 | 
        
           |  |  | 1652 |                 continue;
 | 
        
           |  |  | 1653 |             }
 | 
        
           |  |  | 1654 |   | 
        
           |  |  | 1655 |             // Record the ID and the weight for this grade item.
 | 
        
           |  |  | 1656 |             $overridearray[$gradeitem->id] = array();
 | 
        
           |  |  | 1657 |             $overridearray[$gradeitem->id]['extracredit'] = intval($gradeitem->aggregationcoef);
 | 
        
           |  |  | 1658 |             $overridearray[$gradeitem->id]['weight'] = $gradeitem->aggregationcoef2;
 | 
        
           |  |  | 1659 |             $overridearray[$gradeitem->id]['weightoverride'] = intval($gradeitem->weightoverride);
 | 
        
           |  |  | 1660 |             // If this item has had its weight overridden then set the flag to true, but
 | 
        
           |  |  | 1661 |             // only if all previous items were also overridden. Note that extra credit items
 | 
        
           |  |  | 1662 |             // are counted as overridden grade items.
 | 
        
           |  |  | 1663 |             if (!$gradeitem->weightoverride && $gradeitem->aggregationcoef == 0) {
 | 
        
           |  |  | 1664 |                 $automaticgradeitemspresent = true;
 | 
        
           |  |  | 1665 |             }
 | 
        
           |  |  | 1666 |   | 
        
           |  |  | 1667 |             if ($gradeitem->aggregationcoef > 0) {
 | 
        
           |  |  | 1668 |                 // An extra credit grade item doesn't contribute to $totaloverriddengrademax.
 | 
        
           |  |  | 1669 |                 continue;
 | 
        
           |  |  | 1670 |             } else if ($gradeitem->weightoverride > 0 && $gradeitem->aggregationcoef2 <= 0) {
 | 
        
           |  |  | 1671 |                 // An overridden item that defines a weight of 0 does not contribute to $totaloverriddengrademax.
 | 
        
           |  |  | 1672 |                 continue;
 | 
        
           |  |  | 1673 |             }
 | 
        
           |  |  | 1674 |   | 
        
           |  |  | 1675 |             $totalgrademax += $gradeitem->grademax;
 | 
        
           |  |  | 1676 |             if ($gradeitem->weightoverride > 0) {
 | 
        
           |  |  | 1677 |                 $totaloverriddenweight += $gradeitem->aggregationcoef2;
 | 
        
           |  |  | 1678 |                 $totaloverriddengrademax += $gradeitem->grademax;
 | 
        
           |  |  | 1679 |             }
 | 
        
           |  |  | 1680 |         }
 | 
        
           |  |  | 1681 |   | 
        
           |  |  | 1682 |         // Initialise this variable (used to keep track of the weight override total).
 | 
        
           |  |  | 1683 |         $normalisetotal = 0;
 | 
        
           |  |  | 1684 |         // Keep a record of how much the override total is to see if it is above 100. It it is then we need to set the
 | 
        
           |  |  | 1685 |         // other weights to zero and normalise the others.
 | 
        
           |  |  | 1686 |         $overriddentotal = 0;
 | 
        
           |  |  | 1687 |         // Total up all of the weights.
 | 
        
           |  |  | 1688 |         foreach ($overridearray as $gradeitemdetail) {
 | 
        
           |  |  | 1689 |             // If the grade item has extra credit, then don't add it to the normalisetotal.
 | 
        
           |  |  | 1690 |             if (!$gradeitemdetail['extracredit']) {
 | 
        
           |  |  | 1691 |                 $normalisetotal += $gradeitemdetail['weight'];
 | 
        
           |  |  | 1692 |             }
 | 
        
           |  |  | 1693 |             // The overridden total comprises of items that are set as overridden, that aren't extra credit and have a value
 | 
        
           |  |  | 1694 |             // greater than zero.
 | 
        
           |  |  | 1695 |             if ($gradeitemdetail['weightoverride'] && !$gradeitemdetail['extracredit'] && $gradeitemdetail['weight'] > 0) {
 | 
        
           |  |  | 1696 |                 // Add overriden weights up to see if they are greater than 1.
 | 
        
           |  |  | 1697 |                 $overriddentotal += $gradeitemdetail['weight'];
 | 
        
           |  |  | 1698 |             }
 | 
        
           |  |  | 1699 |         }
 | 
        
           |  |  | 1700 |         if ($overriddentotal > 1) {
 | 
        
           |  |  | 1701 |             // Make sure that this catergory of weights gets normalised.
 | 
        
           |  |  | 1702 |             $requiresnormalising = true;
 | 
        
           |  |  | 1703 |             // The normalised weights are only the overridden weights, so we just use the total of those.
 | 
        
           |  |  | 1704 |             $normalisetotal = $overriddentotal;
 | 
        
           |  |  | 1705 |         }
 | 
        
           |  |  | 1706 |   | 
        
           |  |  | 1707 |         $totalnonoverriddengrademax = $totalgrademax - $totaloverriddengrademax;
 | 
        
           |  |  | 1708 |   | 
        
           |  |  | 1709 |         // This setting indicates if we should use algorithm prior to MDL-49257 fix for calculating extra credit weights.
 | 
        
           |  |  | 1710 |         // Even though old algorith has bugs in it, we need to preserve existing grades.
 | 
        
           |  |  | 1711 |         $gradebookcalculationfreeze = (int)get_config('core', 'gradebook_calculations_freeze_' . $this->courseid);
 | 
        
           |  |  | 1712 |         $oldextracreditcalculation = $gradebookcalculationfreeze && ($gradebookcalculationfreeze <= 20150619);
 | 
        
           |  |  | 1713 |   | 
        
           |  |  | 1714 |         reset($children);
 | 
        
           |  |  | 1715 |         foreach ($children as $sortorder => $child) {
 | 
        
           |  |  | 1716 |             $gradeitem = null;
 | 
        
           |  |  | 1717 |   | 
        
           |  |  | 1718 |             if ($child['type'] == 'item') {
 | 
        
           |  |  | 1719 |                 $gradeitem = $child['object'];
 | 
        
           |  |  | 1720 |             } else if ($child['type'] == 'category') {
 | 
        
           |  |  | 1721 |                 $gradeitem = $child['object']->load_grade_item();
 | 
        
           |  |  | 1722 |             }
 | 
        
           |  |  | 1723 |   | 
        
           |  |  | 1724 |             if ($gradeitem->gradetype == GRADE_TYPE_NONE || $gradeitem->gradetype == GRADE_TYPE_TEXT) {
 | 
        
           |  |  | 1725 |                 // Text items and none items do not have a weight, no need to set their weight to
 | 
        
           |  |  | 1726 |                 // zero as they must never be used during aggregation.
 | 
        
           |  |  | 1727 |                 continue;
 | 
        
           |  |  | 1728 |             } else if (!$this->aggregateoutcomes && $gradeitem->is_outcome_item()) {
 | 
        
           |  |  | 1729 |                 // We will not aggregate outcome items, so we can ignore updating their weights.
 | 
        
           |  |  | 1730 |                 continue;
 | 
        
           |  |  | 1731 |             } else if (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE) {
 | 
        
           |  |  | 1732 |                 // We will not aggregate the scales, so we can ignore upating their weights.
 | 
        
           |  |  | 1733 |                 continue;
 | 
        
           |  |  | 1734 |             } else if (!$oldextracreditcalculation && $gradeitem->aggregationcoef > 0 && $gradeitem->weightoverride) {
 | 
        
           |  |  | 1735 |                 // For an item with extra credit ignore other weigths and overrides but do not change anything at all
 | 
        
           |  |  | 1736 |                 // if it's weight was already overridden.
 | 
        
           |  |  | 1737 |                 continue;
 | 
        
           |  |  | 1738 |             }
 | 
        
           |  |  | 1739 |   | 
        
           |  |  | 1740 |             // Store the previous value here, no need to update if it is the same value.
 | 
        
           |  |  | 1741 |             $prevaggregationcoef2 = $gradeitem->aggregationcoef2;
 | 
        
           |  |  | 1742 |   | 
        
           |  |  | 1743 |             if (!$oldextracreditcalculation && $gradeitem->aggregationcoef > 0 && !$gradeitem->weightoverride) {
 | 
        
           |  |  | 1744 |                 // For an item with extra credit ignore other weigths and overrides.
 | 
        
           |  |  | 1745 |                 $gradeitem->aggregationcoef2 = $totalgrademax ? ($gradeitem->grademax / $totalgrademax) : 0;
 | 
        
           |  |  | 1746 |   | 
        
           |  |  | 1747 |             } else if (!$gradeitem->weightoverride) {
 | 
        
           |  |  | 1748 |                 // Calculations with a grade maximum of zero will cause problems. Just set the weight to zero.
 | 
        
           |  |  | 1749 |                 if ($totaloverriddenweight >= 1 || $totalnonoverriddengrademax == 0 || $gradeitem->grademax == 0) {
 | 
        
           |  |  | 1750 |                     // There is no more weight to distribute.
 | 
        
           |  |  | 1751 |                     $gradeitem->aggregationcoef2 = 0;
 | 
        
           |  |  | 1752 |                 } else {
 | 
        
           |  |  | 1753 |                     // Calculate this item's weight as a percentage of the non-overridden total grade maxes
 | 
        
           |  |  | 1754 |                     // then convert it to a proportion of the available non-overriden weight.
 | 
        
           |  |  | 1755 |                     $gradeitem->aggregationcoef2 = ($gradeitem->grademax/$totalnonoverriddengrademax) *
 | 
        
           |  |  | 1756 |                             (1 - $totaloverriddenweight);
 | 
        
           |  |  | 1757 |                 }
 | 
        
           |  |  | 1758 |   | 
        
           |  |  | 1759 |             } else if ((!$automaticgradeitemspresent && $normalisetotal != 1) || ($requiresnormalising)
 | 
        
           |  |  | 1760 |                     || $overridearray[$gradeitem->id]['weight'] < 0) {
 | 
        
           |  |  | 1761 |                 // Just divide the overriden weight for this item against the total weight override of all
 | 
        
           |  |  | 1762 |                 // items in this category.
 | 
        
           |  |  | 1763 |                 if ($normalisetotal == 0 || $overridearray[$gradeitem->id]['weight'] < 0) {
 | 
        
           |  |  | 1764 |                     // If the normalised total equals zero, or the weight value is less than zero,
 | 
        
           |  |  | 1765 |                     // set the weight for the grade item to zero.
 | 
        
           |  |  | 1766 |                     $gradeitem->aggregationcoef2 = 0;
 | 
        
           |  |  | 1767 |                 } else {
 | 
        
           |  |  | 1768 |                     $gradeitem->aggregationcoef2 = $overridearray[$gradeitem->id]['weight'] / $normalisetotal;
 | 
        
           |  |  | 1769 |                 }
 | 
        
           |  |  | 1770 |             }
 | 
        
           |  |  | 1771 |   | 
        
           |  |  | 1772 |             if (grade_floatval($prevaggregationcoef2) !== grade_floatval($gradeitem->aggregationcoef2)) {
 | 
        
           |  |  | 1773 |                 // Update the grade item to reflect these changes.
 | 
        
           |  |  | 1774 |                 $gradeitem->update();
 | 
        
           |  |  | 1775 |             }
 | 
        
           |  |  | 1776 |         }
 | 
        
           |  |  | 1777 |     }
 | 
        
           |  |  | 1778 |   | 
        
           |  |  | 1779 |     /**
 | 
        
           |  |  | 1780 |      * Given an array of grade values (numerical indices) applies droplow or keephigh rules to limit the final array.
 | 
        
           |  |  | 1781 |      *
 | 
        
           |  |  | 1782 |      * @param array $grade_values itemid=>$grade_value float
 | 
        
           |  |  | 1783 |      * @param array $items grade item objects
 | 
        
           |  |  | 1784 |      * @return array Limited grades.
 | 
        
           |  |  | 1785 |      */
 | 
        
           |  |  | 1786 |     public function apply_limit_rules(&$grade_values, $items) {
 | 
        
           |  |  | 1787 |         $extraused = $this->is_extracredit_used();
 | 
        
           |  |  | 1788 |   | 
        
           |  |  | 1789 |         if (!empty($this->droplow)) {
 | 
        
           |  |  | 1790 |             asort($grade_values, SORT_NUMERIC);
 | 
        
           |  |  | 1791 |             $dropped = 0;
 | 
        
           |  |  | 1792 |   | 
        
           |  |  | 1793 |             // If we have fewer grade items available to drop than $this->droplow, use this flag to escape the loop
 | 
        
           |  |  | 1794 |             // May occur because of "extra credit" or if droplow is higher than the number of grade items
 | 
        
           |  |  | 1795 |             $droppedsomething = true;
 | 
        
           |  |  | 1796 |   | 
        
           |  |  | 1797 |             while ($dropped < $this->droplow && $droppedsomething) {
 | 
        
           |  |  | 1798 |                 $droppedsomething = false;
 | 
        
           |  |  | 1799 |   | 
        
           |  |  | 1800 |                 $grade_keys = array_keys($grade_values);
 | 
        
           |  |  | 1801 |                 $gradekeycount = count($grade_keys);
 | 
        
           |  |  | 1802 |   | 
        
           |  |  | 1803 |                 if ($gradekeycount === 0) {
 | 
        
           |  |  | 1804 |                     //We've dropped all grade items
 | 
        
           |  |  | 1805 |                     break;
 | 
        
           |  |  | 1806 |                 }
 | 
        
           |  |  | 1807 |   | 
        
           |  |  | 1808 |                 $originalindex = $founditemid = $foundmax = null;
 | 
        
           |  |  | 1809 |   | 
        
           |  |  | 1810 |                 // Find the first remaining grade item that is available to be dropped
 | 
        
           |  |  | 1811 |                 foreach ($grade_keys as $gradekeyindex=>$gradekey) {
 | 
        
           |  |  | 1812 |                     if (!$extraused || $items[$gradekey]->aggregationcoef <= 0) {
 | 
        
           |  |  | 1813 |                         // Found a non-extra credit grade item that is eligible to be dropped
 | 
        
           |  |  | 1814 |                         $originalindex = $gradekeyindex;
 | 
        
           |  |  | 1815 |                         $founditemid = $grade_keys[$originalindex];
 | 
        
           |  |  | 1816 |                         $foundmax = $items[$founditemid]->grademax;
 | 
        
           |  |  | 1817 |                         break;
 | 
        
           |  |  | 1818 |                     }
 | 
        
           |  |  | 1819 |                 }
 | 
        
           |  |  | 1820 |   | 
        
           |  |  | 1821 |                 if (empty($founditemid)) {
 | 
        
           |  |  | 1822 |                     // No grade items available to drop
 | 
        
           |  |  | 1823 |                     break;
 | 
        
           |  |  | 1824 |                 }
 | 
        
           |  |  | 1825 |   | 
        
           |  |  | 1826 |                 // Now iterate over the remaining grade items
 | 
        
           |  |  | 1827 |                 // We're looking for other grade items with the same grade value but a higher grademax
 | 
        
           |  |  | 1828 |                 $i = 1;
 | 
        
           |  |  | 1829 |                 while ($originalindex + $i < $gradekeycount) {
 | 
        
           |  |  | 1830 |   | 
        
           |  |  | 1831 |                     $possibleitemid = $grade_keys[$originalindex+$i];
 | 
        
           |  |  | 1832 |                     $i++;
 | 
        
           |  |  | 1833 |   | 
        
           |  |  | 1834 |                     if ($grade_values[$founditemid] != $grade_values[$possibleitemid]) {
 | 
        
           |  |  | 1835 |                         // The next grade item has a different grade value. Stop looking.
 | 
        
           |  |  | 1836 |                         break;
 | 
        
           |  |  | 1837 |                     }
 | 
        
           |  |  | 1838 |   | 
        
           |  |  | 1839 |                     if ($extraused && $items[$possibleitemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1840 |                         // Don't drop extra credit grade items. Continue the search.
 | 
        
           |  |  | 1841 |                         continue;
 | 
        
           |  |  | 1842 |                     }
 | 
        
           |  |  | 1843 |   | 
        
           |  |  | 1844 |                     if ($foundmax < $items[$possibleitemid]->grademax) {
 | 
        
           |  |  | 1845 |                         // Found a grade item with the same grade value and a higher grademax
 | 
        
           |  |  | 1846 |                         $foundmax = $items[$possibleitemid]->grademax;
 | 
        
           |  |  | 1847 |                         $founditemid = $possibleitemid;
 | 
        
           |  |  | 1848 |                         // Continue searching to see if there is an even higher grademax
 | 
        
           |  |  | 1849 |                     }
 | 
        
           |  |  | 1850 |                 }
 | 
        
           |  |  | 1851 |   | 
        
           |  |  | 1852 |                 // Now drop whatever grade item we have found
 | 
        
           |  |  | 1853 |                 unset($grade_values[$founditemid]);
 | 
        
           |  |  | 1854 |                 $dropped++;
 | 
        
           |  |  | 1855 |                 $droppedsomething = true;
 | 
        
           |  |  | 1856 |             }
 | 
        
           |  |  | 1857 |   | 
        
           |  |  | 1858 |         } else if (!empty($this->keephigh)) {
 | 
        
           |  |  | 1859 |             arsort($grade_values, SORT_NUMERIC);
 | 
        
           |  |  | 1860 |             $kept = 0;
 | 
        
           |  |  | 1861 |   | 
        
           |  |  | 1862 |             foreach ($grade_values as $itemid=>$value) {
 | 
        
           |  |  | 1863 |   | 
        
           |  |  | 1864 |                 if ($extraused and $items[$itemid]->aggregationcoef > 0) {
 | 
        
           |  |  | 1865 |                     // we keep all extra credits
 | 
        
           |  |  | 1866 |   | 
        
           |  |  | 1867 |                 } else if ($kept < $this->keephigh) {
 | 
        
           |  |  | 1868 |                     $kept++;
 | 
        
           |  |  | 1869 |   | 
        
           |  |  | 1870 |                 } else {
 | 
        
           |  |  | 1871 |                     unset($grade_values[$itemid]);
 | 
        
           |  |  | 1872 |                 }
 | 
        
           |  |  | 1873 |             }
 | 
        
           |  |  | 1874 |         }
 | 
        
           |  |  | 1875 |     }
 | 
        
           |  |  | 1876 |   | 
        
           |  |  | 1877 |     /**
 | 
        
           |  |  | 1878 |      * Returns whether or not we can apply the limit rules.
 | 
        
           |  |  | 1879 |      *
 | 
        
           |  |  | 1880 |      * There are cases where drop lowest or keep highest should not be used
 | 
        
           |  |  | 1881 |      * at all. This method will determine whether or not this logic can be
 | 
        
           |  |  | 1882 |      * applied considering the current setup of the category.
 | 
        
           |  |  | 1883 |      *
 | 
        
           |  |  | 1884 |      * @return bool
 | 
        
           |  |  | 1885 |      */
 | 
        
           |  |  | 1886 |     public function can_apply_limit_rules() {
 | 
        
           |  |  | 1887 |         if ($this->canapplylimitrules !== null) {
 | 
        
           |  |  | 1888 |             return $this->canapplylimitrules;
 | 
        
           |  |  | 1889 |         }
 | 
        
           |  |  | 1890 |   | 
        
           |  |  | 1891 |         // Set it to be supported by default.
 | 
        
           |  |  | 1892 |         $this->canapplylimitrules = true;
 | 
        
           |  |  | 1893 |   | 
        
           |  |  | 1894 |         // Natural aggregation.
 | 
        
           |  |  | 1895 |         if ($this->aggregation == GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 1896 |             $canapply = true;
 | 
        
           |  |  | 1897 |   | 
        
           |  |  | 1898 |             // Check until one child breaks the rules.
 | 
        
           |  |  | 1899 |             $gradeitems = $this->get_children();
 | 
        
           |  |  | 1900 |             $validitems = 0;
 | 
        
           |  |  | 1901 |             $lastweight = null;
 | 
        
           |  |  | 1902 |             $lastmaxgrade = null;
 | 
        
           |  |  | 1903 |             foreach ($gradeitems as $gradeitem) {
 | 
        
           |  |  | 1904 |                 $gi = $gradeitem['object'];
 | 
        
           |  |  | 1905 |   | 
        
           |  |  | 1906 |                 if ($gradeitem['type'] == 'category') {
 | 
        
           |  |  | 1907 |                     // Sub categories are not allowed because they can have dynamic weights/maxgrades.
 | 
        
           |  |  | 1908 |                     $canapply = false;
 | 
        
           |  |  | 1909 |                     break;
 | 
        
           |  |  | 1910 |                 }
 | 
        
           |  |  | 1911 |   | 
        
           |  |  | 1912 |                 if ($gi->aggregationcoef > 0) {
 | 
        
           |  |  | 1913 |                     // Extra credit items are not allowed.
 | 
        
           |  |  | 1914 |                     $canapply = false;
 | 
        
           |  |  | 1915 |                     break;
 | 
        
           |  |  | 1916 |                 }
 | 
        
           |  |  | 1917 |   | 
        
           |  |  | 1918 |                 if ($lastweight !== null && $lastweight != $gi->aggregationcoef2) {
 | 
        
           |  |  | 1919 |                     // One of the weight differs from another item.
 | 
        
           |  |  | 1920 |                     $canapply = false;
 | 
        
           |  |  | 1921 |                     break;
 | 
        
           |  |  | 1922 |                 }
 | 
        
           |  |  | 1923 |   | 
        
           |  |  | 1924 |                 if ($lastmaxgrade !== null && $lastmaxgrade != $gi->grademax) {
 | 
        
           |  |  | 1925 |                     // One of the max grade differ from another item. This is not allowed for now
 | 
        
           |  |  | 1926 |                     // because we could be end up with different max grade between users for this category.
 | 
        
           |  |  | 1927 |                     $canapply = false;
 | 
        
           |  |  | 1928 |                     break;
 | 
        
           |  |  | 1929 |                 }
 | 
        
           |  |  | 1930 |   | 
        
           |  |  | 1931 |                 $lastweight = $gi->aggregationcoef2;
 | 
        
           |  |  | 1932 |                 $lastmaxgrade = $gi->grademax;
 | 
        
           |  |  | 1933 |             }
 | 
        
           |  |  | 1934 |   | 
        
           |  |  | 1935 |             $this->canapplylimitrules = $canapply;
 | 
        
           |  |  | 1936 |         }
 | 
        
           |  |  | 1937 |   | 
        
           |  |  | 1938 |         return $this->canapplylimitrules;
 | 
        
           |  |  | 1939 |     }
 | 
        
           |  |  | 1940 |   | 
        
           |  |  | 1941 |     /**
 | 
        
           |  |  | 1942 |      * Returns true if category uses extra credit of any kind
 | 
        
           |  |  | 1943 |      *
 | 
        
           |  |  | 1944 |      * @return bool True if extra credit used
 | 
        
           |  |  | 1945 |      */
 | 
        
           |  |  | 1946 |     public function is_extracredit_used() {
 | 
        
           |  |  | 1947 |         return self::aggregation_uses_extracredit($this->aggregation);
 | 
        
           |  |  | 1948 |     }
 | 
        
           |  |  | 1949 |   | 
        
           |  |  | 1950 |     /**
 | 
        
           |  |  | 1951 |      * Returns true if aggregation passed is using extracredit.
 | 
        
           |  |  | 1952 |      *
 | 
        
           |  |  | 1953 |      * @param int $aggregation Aggregation const.
 | 
        
           |  |  | 1954 |      * @return bool True if extra credit used
 | 
        
           |  |  | 1955 |      */
 | 
        
           |  |  | 1956 |     public static function aggregation_uses_extracredit($aggregation) {
 | 
        
           |  |  | 1957 |         return ($aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2
 | 
        
           |  |  | 1958 |              or $aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
 | 
        
           |  |  | 1959 |              or $aggregation == GRADE_AGGREGATE_SUM);
 | 
        
           |  |  | 1960 |     }
 | 
        
           |  |  | 1961 |   | 
        
           |  |  | 1962 |     /**
 | 
        
           |  |  | 1963 |      * Returns true if category uses special aggregation coefficient
 | 
        
           |  |  | 1964 |      *
 | 
        
           |  |  | 1965 |      * @return bool True if an aggregation coefficient is being used
 | 
        
           |  |  | 1966 |      */
 | 
        
           |  |  | 1967 |     public function is_aggregationcoef_used() {
 | 
        
           |  |  | 1968 |         return self::aggregation_uses_aggregationcoef($this->aggregation);
 | 
        
           |  |  | 1969 |   | 
        
           |  |  | 1970 |     }
 | 
        
           |  |  | 1971 |   | 
        
           |  |  | 1972 |     /**
 | 
        
           |  |  | 1973 |      * Returns true if aggregation uses aggregationcoef
 | 
        
           |  |  | 1974 |      *
 | 
        
           |  |  | 1975 |      * @param int $aggregation Aggregation const.
 | 
        
           |  |  | 1976 |      * @return bool True if an aggregation coefficient is being used
 | 
        
           |  |  | 1977 |      */
 | 
        
           |  |  | 1978 |     public static function aggregation_uses_aggregationcoef($aggregation) {
 | 
        
           |  |  | 1979 |         return ($aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN
 | 
        
           |  |  | 1980 |              or $aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2
 | 
        
           |  |  | 1981 |              or $aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
 | 
        
           |  |  | 1982 |              or $aggregation == GRADE_AGGREGATE_SUM);
 | 
        
           |  |  | 1983 |   | 
        
           |  |  | 1984 |     }
 | 
        
           |  |  | 1985 |   | 
        
           |  |  | 1986 |     /**
 | 
        
           |  |  | 1987 |      * Recursive function to find which weight/extra credit field to use in the grade item form.
 | 
        
           |  |  | 1988 |      *
 | 
        
           |  |  | 1989 |      * @param string $first Whether or not this is the first item in the recursion
 | 
        
           |  |  | 1990 |      * @return string
 | 
        
           |  |  | 1991 |      */
 | 
        
           |  |  | 1992 |     public function get_coefstring($first=true) {
 | 
        
           |  |  | 1993 |         if (!is_null($this->coefstring)) {
 | 
        
           |  |  | 1994 |             return $this->coefstring;
 | 
        
           |  |  | 1995 |         }
 | 
        
           |  |  | 1996 |   | 
        
           |  |  | 1997 |         $overriding_coefstring = null;
 | 
        
           |  |  | 1998 |   | 
        
           |  |  | 1999 |         // Stop recursing upwards if this category has no parent
 | 
        
           |  |  | 2000 |         if (!$first) {
 | 
        
           |  |  | 2001 |   | 
        
           |  |  | 2002 |             if ($parent_category = $this->load_parent_category()) {
 | 
        
           |  |  | 2003 |                 return $parent_category->get_coefstring(false);
 | 
        
           |  |  | 2004 |   | 
        
           |  |  | 2005 |             } else {
 | 
        
           |  |  | 2006 |                 return null;
 | 
        
           |  |  | 2007 |             }
 | 
        
           |  |  | 2008 |   | 
        
           |  |  | 2009 |         } else if ($first) {
 | 
        
           |  |  | 2010 |   | 
        
           |  |  | 2011 |             if ($parent_category = $this->load_parent_category()) {
 | 
        
           |  |  | 2012 |                 $overriding_coefstring = $parent_category->get_coefstring(false);
 | 
        
           |  |  | 2013 |             }
 | 
        
           |  |  | 2014 |         }
 | 
        
           |  |  | 2015 |   | 
        
           |  |  | 2016 |         // If an overriding coefstring has trickled down from one of the parent categories, return it. Otherwise, return self.
 | 
        
           |  |  | 2017 |         if (!is_null($overriding_coefstring)) {
 | 
        
           |  |  | 2018 |             return $overriding_coefstring;
 | 
        
           |  |  | 2019 |         }
 | 
        
           |  |  | 2020 |   | 
        
           |  |  | 2021 |         // No parent category is overriding this category's aggregation, return its string
 | 
        
           |  |  | 2022 |         if ($this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
 | 
        
           |  |  | 2023 |             $this->coefstring = 'aggregationcoefweight';
 | 
        
           |  |  | 2024 |   | 
        
           |  |  | 2025 |         } else if ($this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) {
 | 
        
           |  |  | 2026 |             $this->coefstring = 'aggregationcoefextrasum';
 | 
        
           |  |  | 2027 |   | 
        
           |  |  | 2028 |         } else if ($this->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) {
 | 
        
           |  |  | 2029 |             $this->coefstring = 'aggregationcoefextraweight';
 | 
        
           |  |  | 2030 |   | 
        
           |  |  | 2031 |         } else if ($this->aggregation == GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 2032 |             $this->coefstring = 'aggregationcoefextraweightsum';
 | 
        
           |  |  | 2033 |   | 
        
           |  |  | 2034 |         } else {
 | 
        
           |  |  | 2035 |             $this->coefstring = 'aggregationcoef';
 | 
        
           |  |  | 2036 |         }
 | 
        
           |  |  | 2037 |         return $this->coefstring;
 | 
        
           |  |  | 2038 |     }
 | 
        
           |  |  | 2039 |   | 
        
           |  |  | 2040 |     /**
 | 
        
           |  |  | 2041 |      * Returns tree with all grade_items and categories as elements
 | 
        
           |  |  | 2042 |      *
 | 
        
           |  |  | 2043 |      * @param int $courseid The course ID
 | 
        
           |  |  | 2044 |      * @param bool $include_category_items as category children
 | 
        
           |  |  | 2045 |      * @return array
 | 
        
           |  |  | 2046 |      */
 | 
        
           |  |  | 2047 |     public static function fetch_course_tree($courseid, $include_category_items=false) {
 | 
        
           |  |  | 2048 |         $course_category = grade_category::fetch_course_category($courseid);
 | 
        
           |  |  | 2049 |         $category_array = array('object'=>$course_category, 'type'=>'category', 'depth'=>1,
 | 
        
           |  |  | 2050 |                                 'children'=>$course_category->get_children($include_category_items));
 | 
        
           |  |  | 2051 |   | 
        
           |  |  | 2052 |         $course_category->sortorder = $course_category->get_sortorder();
 | 
        
           |  |  | 2053 |         $sortorder = $course_category->get_sortorder();
 | 
        
           |  |  | 2054 |         return grade_category::_fetch_course_tree_recursion($category_array, $sortorder);
 | 
        
           |  |  | 2055 |     }
 | 
        
           |  |  | 2056 |   | 
        
           |  |  | 2057 |     /**
 | 
        
           |  |  | 2058 |      * An internal function that recursively sorts grade categories within a course
 | 
        
           |  |  | 2059 |      *
 | 
        
           |  |  | 2060 |      * @param array $category_array The seed of the recursion
 | 
        
           |  |  | 2061 |      * @param int   $sortorder The current sortorder
 | 
        
           |  |  | 2062 |      * @return array An array containing 'object', 'type', 'depth' and optionally 'children'
 | 
        
           |  |  | 2063 |      */
 | 
        
           |  |  | 2064 |     private static function _fetch_course_tree_recursion($category_array, &$sortorder) {
 | 
        
           |  |  | 2065 |         if (isset($category_array['object']->gradetype) && $category_array['object']->gradetype==GRADE_TYPE_NONE) {
 | 
        
           |  |  | 2066 |             return null;
 | 
        
           |  |  | 2067 |         }
 | 
        
           |  |  | 2068 |   | 
        
           |  |  | 2069 |         // store the grade_item or grade_category instance with extra info
 | 
        
           |  |  | 2070 |         $result = array('object'=>$category_array['object'], 'type'=>$category_array['type'], 'depth'=>$category_array['depth']);
 | 
        
           |  |  | 2071 |   | 
        
           |  |  | 2072 |         // reuse final grades if there
 | 
        
           |  |  | 2073 |         if (array_key_exists('finalgrades', $category_array)) {
 | 
        
           |  |  | 2074 |             $result['finalgrades'] = $category_array['finalgrades'];
 | 
        
           |  |  | 2075 |         }
 | 
        
           |  |  | 2076 |   | 
        
           |  |  | 2077 |         // recursively resort children
 | 
        
           |  |  | 2078 |         if (!empty($category_array['children'])) {
 | 
        
           |  |  | 2079 |             $result['children'] = array();
 | 
        
           |  |  | 2080 |             //process the category item first
 | 
        
           |  |  | 2081 |             $child = null;
 | 
        
           |  |  | 2082 |   | 
        
           |  |  | 2083 |             foreach ($category_array['children'] as $oldorder=>$child_array) {
 | 
        
           |  |  | 2084 |   | 
        
           |  |  | 2085 |                 if ($child_array['type'] == 'courseitem' or $child_array['type'] == 'categoryitem') {
 | 
        
           |  |  | 2086 |                     $child = grade_category::_fetch_course_tree_recursion($child_array, $sortorder);
 | 
        
           |  |  | 2087 |                     if (!empty($child)) {
 | 
        
           |  |  | 2088 |                         $result['children'][$sortorder] = $child;
 | 
        
           |  |  | 2089 |                     }
 | 
        
           |  |  | 2090 |                 }
 | 
        
           |  |  | 2091 |             }
 | 
        
           |  |  | 2092 |   | 
        
           |  |  | 2093 |             foreach ($category_array['children'] as $oldorder=>$child_array) {
 | 
        
           |  |  | 2094 |   | 
        
           |  |  | 2095 |                 if ($child_array['type'] != 'courseitem' and $child_array['type'] != 'categoryitem') {
 | 
        
           |  |  | 2096 |                     $child = grade_category::_fetch_course_tree_recursion($child_array, $sortorder);
 | 
        
           |  |  | 2097 |                     if (!empty($child)) {
 | 
        
           |  |  | 2098 |                         $result['children'][++$sortorder] = $child;
 | 
        
           |  |  | 2099 |                     }
 | 
        
           |  |  | 2100 |                 }
 | 
        
           |  |  | 2101 |             }
 | 
        
           |  |  | 2102 |         }
 | 
        
           |  |  | 2103 |   | 
        
           |  |  | 2104 |         return $result;
 | 
        
           |  |  | 2105 |     }
 | 
        
           |  |  | 2106 |   | 
        
           |  |  | 2107 |     /**
 | 
        
           |  |  | 2108 |      * Fetches and returns all the children categories and/or grade_items belonging to this category.
 | 
        
           |  |  | 2109 |      * By default only returns the immediate children (depth=1), but deeper levels can be requested,
 | 
        
           |  |  | 2110 |      * as well as all levels (0). The elements are indexed by sort order.
 | 
        
           |  |  | 2111 |      *
 | 
        
           |  |  | 2112 |      * @param bool $include_category_items Whether or not to include category grade_items in the children array
 | 
        
           |  |  | 2113 |      * @return array Array of child objects (grade_category and grade_item).
 | 
        
           |  |  | 2114 |      */
 | 
        
           |  |  | 2115 |     public function get_children($include_category_items=false) {
 | 
        
           |  |  | 2116 |         global $DB;
 | 
        
           |  |  | 2117 |   | 
        
           |  |  | 2118 |         // This function must be as fast as possible ;-)
 | 
        
           |  |  | 2119 |         // fetch all course grade items and categories into memory - we do not expect hundreds of these in course
 | 
        
           |  |  | 2120 |         // we have to limit the number of queries though, because it will be used often in grade reports
 | 
        
           |  |  | 2121 |   | 
        
           |  |  | 2122 |         $cats  = $DB->get_records('grade_categories', array('courseid' => $this->courseid));
 | 
        
           |  |  | 2123 |         $items = $DB->get_records('grade_items', array('courseid' => $this->courseid));
 | 
        
           |  |  | 2124 |   | 
        
           |  |  | 2125 |         // init children array first
 | 
        
           |  |  | 2126 |         foreach ($cats as $catid=>$cat) {
 | 
        
           |  |  | 2127 |             $cats[$catid]->children = array();
 | 
        
           |  |  | 2128 |         }
 | 
        
           |  |  | 2129 |   | 
        
           |  |  | 2130 |         //first attach items to cats and add category sortorder
 | 
        
           |  |  | 2131 |         foreach ($items as $item) {
 | 
        
           |  |  | 2132 |   | 
        
           |  |  | 2133 |             if ($item->itemtype == 'course' or $item->itemtype == 'category') {
 | 
        
           |  |  | 2134 |                 $cats[$item->iteminstance]->sortorder = $item->sortorder;
 | 
        
           |  |  | 2135 |   | 
        
           |  |  | 2136 |                 if (!$include_category_items) {
 | 
        
           |  |  | 2137 |                     continue;
 | 
        
           |  |  | 2138 |                 }
 | 
        
           |  |  | 2139 |                 $categoryid = $item->iteminstance;
 | 
        
           |  |  | 2140 |   | 
        
           |  |  | 2141 |             } else {
 | 
        
           |  |  | 2142 |                 $categoryid = $item->categoryid;
 | 
        
           |  |  | 2143 |                 if (empty($categoryid)) {
 | 
        
           |  |  | 2144 |                     debugging('Found a grade item that isnt in a category');
 | 
        
           |  |  | 2145 |                 }
 | 
        
           |  |  | 2146 |             }
 | 
        
           |  |  | 2147 |   | 
        
           |  |  | 2148 |             // prevent problems with duplicate sortorders in db
 | 
        
           |  |  | 2149 |             $sortorder = $item->sortorder;
 | 
        
           |  |  | 2150 |   | 
        
           |  |  | 2151 |             while (array_key_exists($categoryid, $cats)
 | 
        
           |  |  | 2152 |                 && array_key_exists($sortorder, $cats[$categoryid]->children)) {
 | 
        
           |  |  | 2153 |   | 
        
           |  |  | 2154 |                 $sortorder++;
 | 
        
           |  |  | 2155 |             }
 | 
        
           |  |  | 2156 |   | 
        
           |  |  | 2157 |             $cats[$categoryid]->children[$sortorder] = $item;
 | 
        
           |  |  | 2158 |   | 
        
           |  |  | 2159 |         }
 | 
        
           |  |  | 2160 |   | 
        
           |  |  | 2161 |         // now find the requested category and connect categories as children
 | 
        
           |  |  | 2162 |         $category = false;
 | 
        
           |  |  | 2163 |   | 
        
           |  |  | 2164 |         foreach ($cats as $catid=>$cat) {
 | 
        
           |  |  | 2165 |   | 
        
           |  |  | 2166 |             if (empty($cat->parent)) {
 | 
        
           |  |  | 2167 |   | 
        
           |  |  | 2168 |                 if ($cat->path !== '/'.$cat->id.'/') {
 | 
        
           |  |  | 2169 |                     $grade_category = new grade_category($cat, false);
 | 
        
           |  |  | 2170 |                     $grade_category->path  = '/'.$cat->id.'/';
 | 
        
           |  |  | 2171 |                     $grade_category->depth = 1;
 | 
        
           |  |  | 2172 |                     $grade_category->update('system');
 | 
        
           |  |  | 2173 |                     return $this->get_children($include_category_items);
 | 
        
           |  |  | 2174 |                 }
 | 
        
           |  |  | 2175 |   | 
        
           |  |  | 2176 |             } else {
 | 
        
           |  |  | 2177 |   | 
        
           |  |  | 2178 |                 if (empty($cat->path) or !preg_match('|/'.$cat->parent.'/'.$cat->id.'/$|', $cat->path)) {
 | 
        
           |  |  | 2179 |                     //fix paths and depts
 | 
        
           |  |  | 2180 |                     static $recursioncounter = 0; // prevents infinite recursion
 | 
        
           |  |  | 2181 |                     $recursioncounter++;
 | 
        
           |  |  | 2182 |   | 
        
           |  |  | 2183 |                     if ($recursioncounter < 5) {
 | 
        
           |  |  | 2184 |                         // fix paths and depths!
 | 
        
           |  |  | 2185 |                         $grade_category = new grade_category($cat, false);
 | 
        
           |  |  | 2186 |                         $grade_category->depth = 0;
 | 
        
           |  |  | 2187 |                         $grade_category->path  = null;
 | 
        
           |  |  | 2188 |                         $grade_category->update('system');
 | 
        
           |  |  | 2189 |                         return $this->get_children($include_category_items);
 | 
        
           |  |  | 2190 |                     }
 | 
        
           |  |  | 2191 |                 }
 | 
        
           |  |  | 2192 |                 // prevent problems with duplicate sortorders in db
 | 
        
           |  |  | 2193 |                 $sortorder = $cat->sortorder;
 | 
        
           |  |  | 2194 |   | 
        
           |  |  | 2195 |                 while (array_key_exists($sortorder, $cats[$cat->parent]->children)) {
 | 
        
           |  |  | 2196 |                     //debugging("$sortorder exists in cat loop");
 | 
        
           |  |  | 2197 |                     $sortorder++;
 | 
        
           |  |  | 2198 |                 }
 | 
        
           |  |  | 2199 |   | 
        
           |  |  | 2200 |                 $cats[$cat->parent]->children[$sortorder] = &$cats[$catid];
 | 
        
           |  |  | 2201 |             }
 | 
        
           |  |  | 2202 |   | 
        
           |  |  | 2203 |             if ($catid == $this->id) {
 | 
        
           |  |  | 2204 |                 $category = &$cats[$catid];
 | 
        
           |  |  | 2205 |             }
 | 
        
           |  |  | 2206 |         }
 | 
        
           |  |  | 2207 |   | 
        
           |  |  | 2208 |         unset($items); // not needed
 | 
        
           |  |  | 2209 |         unset($cats); // not needed
 | 
        
           |  |  | 2210 |   | 
        
           |  |  | 2211 |         $children_array = array();
 | 
        
           |  |  | 2212 |         if (is_object($category)) {
 | 
        
           |  |  | 2213 |             $children_array = grade_category::_get_children_recursion($category);
 | 
        
           |  |  | 2214 |             ksort($children_array);
 | 
        
           |  |  | 2215 |         }
 | 
        
           |  |  | 2216 |   | 
        
           |  |  | 2217 |         return $children_array;
 | 
        
           |  |  | 2218 |   | 
        
           |  |  | 2219 |     }
 | 
        
           |  |  | 2220 |   | 
        
           |  |  | 2221 |     /**
 | 
        
           |  |  | 2222 |      * Private method used to retrieve all children of this category recursively
 | 
        
           |  |  | 2223 |      *
 | 
        
           |  |  | 2224 |      * @param grade_category $category Source of current recursion
 | 
        
           |  |  | 2225 |      * @return array An array of child grade categories
 | 
        
           |  |  | 2226 |      */
 | 
        
           |  |  | 2227 |     private static function _get_children_recursion($category) {
 | 
        
           |  |  | 2228 |   | 
        
           |  |  | 2229 |         $children_array = array();
 | 
        
           |  |  | 2230 |         foreach ($category->children as $sortorder=>$child) {
 | 
        
           |  |  | 2231 |   | 
        
           |  |  | 2232 |             if (property_exists($child, 'itemtype')) {
 | 
        
           |  |  | 2233 |                 $grade_item = new grade_item($child, false);
 | 
        
           |  |  | 2234 |   | 
        
           |  |  | 2235 |                 if (in_array($grade_item->itemtype, array('course', 'category'))) {
 | 
        
           |  |  | 2236 |                     $type  = $grade_item->itemtype.'item';
 | 
        
           |  |  | 2237 |                     $depth = $category->depth;
 | 
        
           |  |  | 2238 |   | 
        
           |  |  | 2239 |                 } else {
 | 
        
           |  |  | 2240 |                     $type  = 'item';
 | 
        
           |  |  | 2241 |                     $depth = $category->depth; // we use this to set the same colour
 | 
        
           |  |  | 2242 |                 }
 | 
        
           |  |  | 2243 |                 $children_array[$sortorder] = array('object'=>$grade_item, 'type'=>$type, 'depth'=>$depth);
 | 
        
           |  |  | 2244 |   | 
        
           |  |  | 2245 |             } else {
 | 
        
           |  |  | 2246 |                 $children = grade_category::_get_children_recursion($child);
 | 
        
           |  |  | 2247 |                 $grade_category = new grade_category($child, false);
 | 
        
           |  |  | 2248 |   | 
        
           |  |  | 2249 |                 if (empty($children)) {
 | 
        
           |  |  | 2250 |                     $children = array();
 | 
        
           |  |  | 2251 |                 }
 | 
        
           |  |  | 2252 |                 $children_array[$sortorder] = array('object'=>$grade_category, 'type'=>'category', 'depth'=>$grade_category->depth, 'children'=>$children);
 | 
        
           |  |  | 2253 |             }
 | 
        
           |  |  | 2254 |         }
 | 
        
           |  |  | 2255 |   | 
        
           |  |  | 2256 |         // sort the array
 | 
        
           |  |  | 2257 |         ksort($children_array);
 | 
        
           |  |  | 2258 |   | 
        
           |  |  | 2259 |         return $children_array;
 | 
        
           |  |  | 2260 |     }
 | 
        
           |  |  | 2261 |   | 
        
           |  |  | 2262 |     /**
 | 
        
           |  |  | 2263 |      * Uses {@link get_grade_item()} to load or create a grade_item, then saves it as $this->grade_item.
 | 
        
           |  |  | 2264 |      *
 | 
        
           |  |  | 2265 |      * @return grade_item
 | 
        
           |  |  | 2266 |      */
 | 
        
           |  |  | 2267 |     public function load_grade_item() {
 | 
        
           |  |  | 2268 |         if (empty($this->grade_item)) {
 | 
        
           |  |  | 2269 |             $this->grade_item = $this->get_grade_item();
 | 
        
           |  |  | 2270 |         }
 | 
        
           |  |  | 2271 |         return $this->grade_item;
 | 
        
           |  |  | 2272 |     }
 | 
        
           |  |  | 2273 |   | 
        
           |  |  | 2274 |     /**
 | 
        
           |  |  | 2275 |      * Retrieves this grade categories' associated grade_item from the database
 | 
        
           |  |  | 2276 |      *
 | 
        
           |  |  | 2277 |      * If no grade_item exists yet, creates one.
 | 
        
           |  |  | 2278 |      *
 | 
        
           |  |  | 2279 |      * @return grade_item
 | 
        
           |  |  | 2280 |      */
 | 
        
           |  |  | 2281 |     public function get_grade_item() {
 | 
        
           |  |  | 2282 |         if (empty($this->id)) {
 | 
        
           |  |  | 2283 |             debugging("Attempt to obtain a grade_category's associated grade_item without the category's ID being set.");
 | 
        
           |  |  | 2284 |             return false;
 | 
        
           |  |  | 2285 |         }
 | 
        
           |  |  | 2286 |   | 
        
           |  |  | 2287 |         if (empty($this->parent)) {
 | 
        
           |  |  | 2288 |             $params = array('courseid'=>$this->courseid, 'itemtype'=>'course', 'iteminstance'=>$this->id);
 | 
        
           |  |  | 2289 |   | 
        
           |  |  | 2290 |         } else {
 | 
        
           |  |  | 2291 |             $params = array('courseid'=>$this->courseid, 'itemtype'=>'category', 'iteminstance'=>$this->id);
 | 
        
           |  |  | 2292 |         }
 | 
        
           |  |  | 2293 |   | 
        
           |  |  | 2294 |         if (!$grade_items = grade_item::fetch_all($params)) {
 | 
        
           |  |  | 2295 |             // create a new one
 | 
        
           |  |  | 2296 |             $grade_item = new grade_item($params, false);
 | 
        
           |  |  | 2297 |             $grade_item->gradetype = GRADE_TYPE_VALUE;
 | 
        
           |  |  | 2298 |             $grade_item->insert('system');
 | 
        
           |  |  | 2299 |   | 
        
           |  |  | 2300 |         } else if (count($grade_items) == 1) {
 | 
        
           |  |  | 2301 |             // found existing one
 | 
        
           |  |  | 2302 |             $grade_item = reset($grade_items);
 | 
        
           |  |  | 2303 |   | 
        
           |  |  | 2304 |         } else {
 | 
        
           |  |  | 2305 |             debugging("Found more than one grade_item attached to category id:".$this->id);
 | 
        
           |  |  | 2306 |             // return first one
 | 
        
           |  |  | 2307 |             $grade_item = reset($grade_items);
 | 
        
           |  |  | 2308 |         }
 | 
        
           |  |  | 2309 |   | 
        
           |  |  | 2310 |         return $grade_item;
 | 
        
           |  |  | 2311 |     }
 | 
        
           |  |  | 2312 |   | 
        
           |  |  | 2313 |     /**
 | 
        
           |  |  | 2314 |      * Uses $this->parent to instantiate $this->parent_category based on the referenced record in the DB
 | 
        
           |  |  | 2315 |      *
 | 
        
           |  |  | 2316 |      * @return grade_category The parent category
 | 
        
           |  |  | 2317 |      */
 | 
        
           |  |  | 2318 |     public function load_parent_category() {
 | 
        
           |  |  | 2319 |         if (empty($this->parent_category) && !empty($this->parent)) {
 | 
        
           |  |  | 2320 |             $this->parent_category = $this->get_parent_category();
 | 
        
           |  |  | 2321 |         }
 | 
        
           |  |  | 2322 |         return $this->parent_category;
 | 
        
           |  |  | 2323 |     }
 | 
        
           |  |  | 2324 |   | 
        
           |  |  | 2325 |     /**
 | 
        
           |  |  | 2326 |      * Uses $this->parent to instantiate and return a grade_category object
 | 
        
           |  |  | 2327 |      *
 | 
        
           |  |  | 2328 |      * @return grade_category Returns the parent category or null if this category has no parent
 | 
        
           |  |  | 2329 |      */
 | 
        
           |  |  | 2330 |     public function get_parent_category() {
 | 
        
           |  |  | 2331 |         if (!empty($this->parent)) {
 | 
        
           |  |  | 2332 |             $parent_category = new grade_category(array('id' => $this->parent));
 | 
        
           |  |  | 2333 |             return $parent_category;
 | 
        
           |  |  | 2334 |         } else {
 | 
        
           |  |  | 2335 |             return null;
 | 
        
           |  |  | 2336 |         }
 | 
        
           |  |  | 2337 |     }
 | 
        
           |  |  | 2338 |   | 
        
           |  |  | 2339 |     /**
 | 
        
           |  |  | 2340 |      * Returns the most descriptive field for this grade category
 | 
        
           |  |  | 2341 |      *
 | 
        
           |  |  | 2342 |      * @return string name
 | 
        
           |  |  | 2343 |      * @param bool $escape Whether the returned category name is to be HTML escaped or not.
 | 
        
           |  |  | 2344 |      */
 | 
        
           |  |  | 2345 |     public function get_name($escape = true) {
 | 
        
           |  |  | 2346 |         global $DB;
 | 
        
           |  |  | 2347 |         // For a course category, we return the course name if the fullname is set to '?' in the DB (empty in the category edit form)
 | 
        
           |  |  | 2348 |         if (empty($this->parent) && $this->fullname == '?') {
 | 
        
           |  |  | 2349 |             $course = $DB->get_record('course', array('id'=> $this->courseid));
 | 
        
           |  |  | 2350 |             return format_string($course->fullname, false, ['context' => context_course::instance($this->courseid),
 | 
        
           |  |  | 2351 |                 'escape' => $escape]);
 | 
        
           |  |  | 2352 |   | 
        
           |  |  | 2353 |         } else {
 | 
        
           |  |  | 2354 |             // Grade categories can't be set up at system context (unlike scales and outcomes)
 | 
        
           |  |  | 2355 |             // We therefore must have a courseid, and don't need to handle system contexts when filtering.
 | 
        
           |  |  | 2356 |             return format_string($this->fullname, false, ['context' => context_course::instance($this->courseid),
 | 
        
           |  |  | 2357 |                 'escape' => $escape]);
 | 
        
           |  |  | 2358 |         }
 | 
        
           |  |  | 2359 |     }
 | 
        
           |  |  | 2360 |   | 
        
           |  |  | 2361 |     /**
 | 
        
           |  |  | 2362 |      * Describe the aggregation settings for this category so the reports make more sense.
 | 
        
           |  |  | 2363 |      *
 | 
        
           |  |  | 2364 |      * @return string description
 | 
        
           |  |  | 2365 |      */
 | 
        
           |  |  | 2366 |     public function get_description() {
 | 
        
           |  |  | 2367 |         $allhelp = array();
 | 
        
           |  |  | 2368 |         if ($this->aggregation != GRADE_AGGREGATE_SUM) {
 | 
        
           |  |  | 2369 |             $aggrstrings = grade_helper::get_aggregation_strings();
 | 
        
           |  |  | 2370 |             $allhelp[] = $aggrstrings[$this->aggregation];
 | 
        
           |  |  | 2371 |         }
 | 
        
           |  |  | 2372 |   | 
        
           |  |  | 2373 |         if ($this->droplow && $this->can_apply_limit_rules()) {
 | 
        
           |  |  | 2374 |             $allhelp[] = get_string('droplowestvalues', 'grades', $this->droplow);
 | 
        
           |  |  | 2375 |         }
 | 
        
           |  |  | 2376 |         if ($this->keephigh && $this->can_apply_limit_rules()) {
 | 
        
           |  |  | 2377 |             $allhelp[] = get_string('keephighestvalues', 'grades', $this->keephigh);
 | 
        
           |  |  | 2378 |         }
 | 
        
           |  |  | 2379 |         if (!$this->aggregateonlygraded) {
 | 
        
           |  |  | 2380 |             $allhelp[] = get_string('aggregatenotonlygraded', 'grades');
 | 
        
           |  |  | 2381 |         }
 | 
        
           |  |  | 2382 |         if ($allhelp) {
 | 
        
           |  |  | 2383 |             return implode('. ', $allhelp) . '.';
 | 
        
           |  |  | 2384 |         }
 | 
        
           |  |  | 2385 |         return '';
 | 
        
           |  |  | 2386 |     }
 | 
        
           |  |  | 2387 |   | 
        
           |  |  | 2388 |     /**
 | 
        
           |  |  | 2389 |      * Sets this category's parent id
 | 
        
           |  |  | 2390 |      *
 | 
        
           |  |  | 2391 |      * @param int $parentid The ID of the category that is the new parent to $this
 | 
        
           |  |  | 2392 |      * @param string $source From where was the object updated (mod/forum, manual, etc.)
 | 
        
           |  |  | 2393 |      * @return bool success
 | 
        
           |  |  | 2394 |      */
 | 
        
           |  |  | 2395 |     public function set_parent($parentid, $source=null) {
 | 
        
           |  |  | 2396 |         if ($this->parent == $parentid) {
 | 
        
           |  |  | 2397 |             return true;
 | 
        
           |  |  | 2398 |         }
 | 
        
           |  |  | 2399 |   | 
        
           |  |  | 2400 |         if ($parentid == $this->id) {
 | 
        
           |  |  | 2401 |             throw new \moodle_exception('cannotassignselfasparent');
 | 
        
           |  |  | 2402 |         }
 | 
        
           |  |  | 2403 |   | 
        
           |  |  | 2404 |         if (empty($this->parent) and $this->is_course_category()) {
 | 
        
           |  |  | 2405 |             throw new \moodle_exception('cannothaveparentcate');
 | 
        
           |  |  | 2406 |         }
 | 
        
           |  |  | 2407 |   | 
        
           |  |  | 2408 |         // find parent and check course id
 | 
        
           |  |  | 2409 |         if (!$parent_category = grade_category::fetch(array('id'=>$parentid, 'courseid'=>$this->courseid))) {
 | 
        
           |  |  | 2410 |             return false;
 | 
        
           |  |  | 2411 |         }
 | 
        
           |  |  | 2412 |   | 
        
           |  |  | 2413 |         $this->force_regrading();
 | 
        
           |  |  | 2414 |   | 
        
           |  |  | 2415 |         // set new parent category
 | 
        
           |  |  | 2416 |         $this->parent          = $parent_category->id;
 | 
        
           |  |  | 2417 |         $this->parent_category =& $parent_category;
 | 
        
           |  |  | 2418 |         $this->path            = null;       // remove old path and depth - will be recalculated in update()
 | 
        
           |  |  | 2419 |         $this->depth           = 0;          // remove old path and depth - will be recalculated in update()
 | 
        
           |  |  | 2420 |         $this->update($source);
 | 
        
           |  |  | 2421 |   | 
        
           |  |  | 2422 |         return $this->update($source);
 | 
        
           |  |  | 2423 |     }
 | 
        
           |  |  | 2424 |   | 
        
           |  |  | 2425 |     /**
 | 
        
           |  |  | 2426 |      * Returns the final grade values for this grade category.
 | 
        
           |  |  | 2427 |      *
 | 
        
           |  |  | 2428 |      * @param int $userid Optional user ID to retrieve a single user's final grade
 | 
        
           |  |  | 2429 |      * @return mixed An array of all final_grades (stdClass objects) for this grade_item, or a single final_grade.
 | 
        
           |  |  | 2430 |      */
 | 
        
           |  |  | 2431 |     public function get_final($userid=null) {
 | 
        
           |  |  | 2432 |         $this->load_grade_item();
 | 
        
           |  |  | 2433 |         return $this->grade_item->get_final($userid);
 | 
        
           |  |  | 2434 |     }
 | 
        
           |  |  | 2435 |   | 
        
           |  |  | 2436 |     /**
 | 
        
           |  |  | 2437 |      * Returns the sortorder of the grade categories' associated grade_item
 | 
        
           |  |  | 2438 |      *
 | 
        
           |  |  | 2439 |      * This method is also available in grade_item for cases where the object type is not known.
 | 
        
           |  |  | 2440 |      *
 | 
        
           |  |  | 2441 |      * @return int Sort order
 | 
        
           |  |  | 2442 |      */
 | 
        
           |  |  | 2443 |     public function get_sortorder() {
 | 
        
           |  |  | 2444 |         $this->load_grade_item();
 | 
        
           |  |  | 2445 |         return $this->grade_item->get_sortorder();
 | 
        
           |  |  | 2446 |     }
 | 
        
           |  |  | 2447 |   | 
        
           |  |  | 2448 |     /**
 | 
        
           |  |  | 2449 |      * Returns the idnumber of the grade categories' associated grade_item.
 | 
        
           |  |  | 2450 |      *
 | 
        
           |  |  | 2451 |      * This method is also available in grade_item for cases where the object type is not known.
 | 
        
           |  |  | 2452 |      *
 | 
        
           |  |  | 2453 |      * @return string idnumber
 | 
        
           |  |  | 2454 |      */
 | 
        
           |  |  | 2455 |     public function get_idnumber() {
 | 
        
           |  |  | 2456 |         $this->load_grade_item();
 | 
        
           |  |  | 2457 |         return $this->grade_item->get_idnumber();
 | 
        
           |  |  | 2458 |     }
 | 
        
           |  |  | 2459 |   | 
        
           |  |  | 2460 |     /**
 | 
        
           |  |  | 2461 |      * Sets the sortorder variable for this category.
 | 
        
           |  |  | 2462 |      *
 | 
        
           |  |  | 2463 |      * This method is also available in grade_item, for cases where the object type is not know.
 | 
        
           |  |  | 2464 |      *
 | 
        
           |  |  | 2465 |      * @param int $sortorder The sortorder to assign to this category
 | 
        
           |  |  | 2466 |      */
 | 
        
           |  |  | 2467 |     public function set_sortorder($sortorder) {
 | 
        
           |  |  | 2468 |         $this->load_grade_item();
 | 
        
           |  |  | 2469 |         $this->grade_item->set_sortorder($sortorder);
 | 
        
           |  |  | 2470 |     }
 | 
        
           |  |  | 2471 |   | 
        
           |  |  | 2472 |     /**
 | 
        
           |  |  | 2473 |      * Move this category after the given sortorder
 | 
        
           |  |  | 2474 |      *
 | 
        
           |  |  | 2475 |      * Does not change the parent
 | 
        
           |  |  | 2476 |      *
 | 
        
           |  |  | 2477 |      * @param int $sortorder to place after.
 | 
        
           |  |  | 2478 |      * @return void
 | 
        
           |  |  | 2479 |      */
 | 
        
           |  |  | 2480 |     public function move_after_sortorder($sortorder) {
 | 
        
           |  |  | 2481 |         $this->load_grade_item();
 | 
        
           |  |  | 2482 |         $this->grade_item->move_after_sortorder($sortorder);
 | 
        
           |  |  | 2483 |     }
 | 
        
           |  |  | 2484 |   | 
        
           |  |  | 2485 |     /**
 | 
        
           |  |  | 2486 |      * Return true if this is the top most category that represents the total course grade.
 | 
        
           |  |  | 2487 |      *
 | 
        
           |  |  | 2488 |      * @return bool
 | 
        
           |  |  | 2489 |      */
 | 
        
           |  |  | 2490 |     public function is_course_category() {
 | 
        
           |  |  | 2491 |         $this->load_grade_item();
 | 
        
           |  |  | 2492 |         return $this->grade_item->is_course_item();
 | 
        
           |  |  | 2493 |     }
 | 
        
           |  |  | 2494 |   | 
        
           |  |  | 2495 |     /**
 | 
        
           |  |  | 2496 |      * Return the course level grade_category object
 | 
        
           |  |  | 2497 |      *
 | 
        
           |  |  | 2498 |      * @param int $courseid The Course ID
 | 
        
           |  |  | 2499 |      * @return grade_category Returns the course level grade_category instance
 | 
        
           |  |  | 2500 |      */
 | 
        
           |  |  | 2501 |     public static function fetch_course_category($courseid) {
 | 
        
           |  |  | 2502 |         if (empty($courseid)) {
 | 
        
           |  |  | 2503 |             debugging('Missing course id!');
 | 
        
           |  |  | 2504 |             return false;
 | 
        
           |  |  | 2505 |         }
 | 
        
           |  |  | 2506 |   | 
        
           |  |  | 2507 |         // course category has no parent
 | 
        
           |  |  | 2508 |         if ($course_category = grade_category::fetch(array('courseid'=>$courseid, 'parent'=>null))) {
 | 
        
           |  |  | 2509 |             return $course_category;
 | 
        
           |  |  | 2510 |         }
 | 
        
           |  |  | 2511 |   | 
        
           |  |  | 2512 |         // create a new one
 | 
        
           |  |  | 2513 |         $course_category = new grade_category();
 | 
        
           |  |  | 2514 |         $course_category->insert_course_category($courseid);
 | 
        
           |  |  | 2515 |   | 
        
           |  |  | 2516 |         return $course_category;
 | 
        
           |  |  | 2517 |     }
 | 
        
           |  |  | 2518 |   | 
        
           |  |  | 2519 |     /**
 | 
        
           |  |  | 2520 |      * Is grading object editable?
 | 
        
           |  |  | 2521 |      *
 | 
        
           |  |  | 2522 |      * @return bool
 | 
        
           |  |  | 2523 |      */
 | 
        
           |  |  | 2524 |     public function is_editable() {
 | 
        
           |  |  | 2525 |         return true;
 | 
        
           |  |  | 2526 |     }
 | 
        
           |  |  | 2527 |   | 
        
           |  |  | 2528 |     /**
 | 
        
           |  |  | 2529 |      * Returns the locked state/date of the grade categories' associated grade_item.
 | 
        
           |  |  | 2530 |      *
 | 
        
           |  |  | 2531 |      * This method is also available in grade_item, for cases where the object type is not known.
 | 
        
           |  |  | 2532 |      *
 | 
        
           |  |  | 2533 |      * @return bool
 | 
        
           |  |  | 2534 |      */
 | 
        
           |  |  | 2535 |     public function is_locked() {
 | 
        
           |  |  | 2536 |         $this->load_grade_item();
 | 
        
           |  |  | 2537 |         return $this->grade_item->is_locked();
 | 
        
           |  |  | 2538 |     }
 | 
        
           |  |  | 2539 |   | 
        
           |  |  | 2540 |     /**
 | 
        
           |  |  | 2541 |      * Sets the grade_item's locked variable and updates the grade_item.
 | 
        
           |  |  | 2542 |      *
 | 
        
           |  |  | 2543 |      * Calls set_locked() on the categories' grade_item
 | 
        
           |  |  | 2544 |      *
 | 
        
           |  |  | 2545 |      * @param int  $lockedstate 0, 1 or a timestamp int(10) after which date the item will be locked.
 | 
        
           |  |  | 2546 |      * @param bool $cascade lock/unlock child objects too
 | 
        
           |  |  | 2547 |      * @param bool $refresh refresh grades when unlocking
 | 
        
           |  |  | 2548 |      * @return bool success if category locked (not all children mayb be locked though)
 | 
        
           |  |  | 2549 |      */
 | 
        
           |  |  | 2550 |     public function set_locked($lockedstate, $cascade=false, $refresh=true) {
 | 
        
           |  |  | 2551 |         $this->load_grade_item();
 | 
        
           |  |  | 2552 |   | 
        
           |  |  | 2553 |         $result = $this->grade_item->set_locked($lockedstate, $cascade, true);
 | 
        
           |  |  | 2554 |   | 
        
           |  |  | 2555 |         // Process all children - items and categories.
 | 
        
           |  |  | 2556 |         if ($children = grade_item::fetch_all(['categoryid' => $this->id])) {
 | 
        
           |  |  | 2557 |             foreach ($children as $child) {
 | 
        
           |  |  | 2558 |                 $child->set_locked($lockedstate, $cascade, false);
 | 
        
           |  |  | 2559 |   | 
        
           |  |  | 2560 |                 if (empty($lockedstate) && $refresh) {
 | 
        
           |  |  | 2561 |                     // Refresh when unlocking.
 | 
        
           |  |  | 2562 |                     $child->refresh_grades();
 | 
        
           |  |  | 2563 |                 }
 | 
        
           |  |  | 2564 |             }
 | 
        
           |  |  | 2565 |         }
 | 
        
           |  |  | 2566 |   | 
        
           |  |  | 2567 |         if ($children = static::fetch_all(['parent' => $this->id])) {
 | 
        
           |  |  | 2568 |             foreach ($children as $child) {
 | 
        
           |  |  | 2569 |                 $child->set_locked($lockedstate, $cascade, true);
 | 
        
           |  |  | 2570 |             }
 | 
        
           |  |  | 2571 |         }
 | 
        
           |  |  | 2572 |   | 
        
           |  |  | 2573 |         return $result;
 | 
        
           |  |  | 2574 |     }
 | 
        
           |  |  | 2575 |   | 
        
           |  |  | 2576 |     /**
 | 
        
           |  |  | 2577 |      * Overrides grade_object::set_properties() to add special handling for changes to category aggregation types
 | 
        
           |  |  | 2578 |      *
 | 
        
           |  |  | 2579 |      * @param grade_category $instance the object to set the properties on
 | 
        
           |  |  | 2580 |      * @param array|stdClass $params Either an associative array or an object containing property name, property value pairs
 | 
        
           |  |  | 2581 |      */
 | 
        
           |  |  | 2582 |     public static function set_properties(&$instance, $params) {
 | 
        
           |  |  | 2583 |         global $DB;
 | 
        
           |  |  | 2584 |   | 
        
           |  |  | 2585 |         $fromaggregation = $instance->aggregation;
 | 
        
           |  |  | 2586 |   | 
        
           |  |  | 2587 |         parent::set_properties($instance, $params);
 | 
        
           |  |  | 2588 |   | 
        
           |  |  | 2589 |         // The aggregation method is changing and this category has already been saved.
 | 
        
           |  |  | 2590 |         if (isset($params->aggregation) && !empty($instance->id)) {
 | 
        
           |  |  | 2591 |             $achildwasdupdated = false;
 | 
        
           |  |  | 2592 |   | 
        
           |  |  | 2593 |             // Get all its children.
 | 
        
           |  |  | 2594 |             $children = $instance->get_children();
 | 
        
           |  |  | 2595 |             foreach ($children as $child) {
 | 
        
           |  |  | 2596 |                 $item = $child['object'];
 | 
        
           |  |  | 2597 |                 if ($child['type'] == 'category') {
 | 
        
           |  |  | 2598 |                     $item = $item->load_grade_item();
 | 
        
           |  |  | 2599 |                 }
 | 
        
           |  |  | 2600 |   | 
        
           |  |  | 2601 |                 // Set the new aggregation fields.
 | 
        
           |  |  | 2602 |                 if ($item->set_aggregation_fields_for_aggregation($fromaggregation, $params->aggregation)) {
 | 
        
           |  |  | 2603 |                     $item->update();
 | 
        
           |  |  | 2604 |                     $achildwasdupdated = true;
 | 
        
           |  |  | 2605 |                 }
 | 
        
           |  |  | 2606 |             }
 | 
        
           |  |  | 2607 |   | 
        
           |  |  | 2608 |             // If this is the course category, it is possible that its grade item was set as needsupdate
 | 
        
           |  |  | 2609 |             // by one of its children. If we keep a reference to that stale object we might cause the
 | 
        
           |  |  | 2610 |             // needsupdate flag to be lost. It's safer to just reload the grade_item from the database.
 | 
        
           |  |  | 2611 |             if ($achildwasdupdated && !empty($instance->grade_item) && $instance->is_course_category()) {
 | 
        
           |  |  | 2612 |                 $instance->grade_item = null;
 | 
        
           |  |  | 2613 |                 $instance->load_grade_item();
 | 
        
           |  |  | 2614 |             }
 | 
        
           |  |  | 2615 |         }
 | 
        
           |  |  | 2616 |     }
 | 
        
           |  |  | 2617 |   | 
        
           |  |  | 2618 |     /**
 | 
        
           |  |  | 2619 |      * Sets the grade_item's hidden variable and updates the grade_item.
 | 
        
           |  |  | 2620 |      *
 | 
        
           |  |  | 2621 |      * Overrides grade_item::set_hidden() to add cascading of the hidden value to grade items in this grade category
 | 
        
           |  |  | 2622 |      *
 | 
        
           |  |  | 2623 |      * @param int $hidden 0 mean always visible, 1 means always hidden and a number > 1 is a timestamp to hide until
 | 
        
           |  |  | 2624 |      * @param bool $cascade apply to child objects too
 | 
        
           |  |  | 2625 |      */
 | 
        
           |  |  | 2626 |     public function set_hidden($hidden, $cascade=false) {
 | 
        
           |  |  | 2627 |         $this->load_grade_item();
 | 
        
           |  |  | 2628 |         //this hides the category itself and everything it contains
 | 
        
           |  |  | 2629 |         parent::set_hidden($hidden, $cascade);
 | 
        
           |  |  | 2630 |   | 
        
           |  |  | 2631 |         if ($cascade) {
 | 
        
           |  |  | 2632 |   | 
        
           |  |  | 2633 |             // This hides the associated grade item (the course/category total).
 | 
        
           |  |  | 2634 |             $this->grade_item->set_hidden($hidden, $cascade);
 | 
        
           |  |  | 2635 |   | 
        
           |  |  | 2636 |             if ($children = grade_item::fetch_all(array('categoryid'=>$this->id))) {
 | 
        
           |  |  | 2637 |   | 
        
           |  |  | 2638 |                 foreach ($children as $child) {
 | 
        
           |  |  | 2639 |                     if ($child->can_control_visibility()) {
 | 
        
           |  |  | 2640 |                         $child->set_hidden($hidden, $cascade);
 | 
        
           |  |  | 2641 |                     }
 | 
        
           |  |  | 2642 |                 }
 | 
        
           |  |  | 2643 |             }
 | 
        
           |  |  | 2644 |   | 
        
           |  |  | 2645 |             if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
 | 
        
           |  |  | 2646 |   | 
        
           |  |  | 2647 |                 foreach ($children as $child) {
 | 
        
           |  |  | 2648 |                     $child->set_hidden($hidden, $cascade);
 | 
        
           |  |  | 2649 |                 }
 | 
        
           |  |  | 2650 |             }
 | 
        
           |  |  | 2651 |         }
 | 
        
           |  |  | 2652 |   | 
        
           |  |  | 2653 |         //if marking category visible make sure parent category is visible MDL-21367
 | 
        
           |  |  | 2654 |         if( !$hidden ) {
 | 
        
           |  |  | 2655 |             $category_array = grade_category::fetch_all(array('id'=>$this->parent));
 | 
        
           |  |  | 2656 |             if ($category_array && array_key_exists($this->parent, $category_array)) {
 | 
        
           |  |  | 2657 |                 $category = $category_array[$this->parent];
 | 
        
           |  |  | 2658 |                 //call set_hidden on the category regardless of whether it is hidden as its parent might be hidden
 | 
        
           |  |  | 2659 |                 $category->set_hidden($hidden, false);
 | 
        
           |  |  | 2660 |             }
 | 
        
           |  |  | 2661 |         }
 | 
        
           |  |  | 2662 |     }
 | 
        
           |  |  | 2663 |   | 
        
           |  |  | 2664 |     /**
 | 
        
           |  |  | 2665 |      * Applies default settings on this category
 | 
        
           |  |  | 2666 |      *
 | 
        
           |  |  | 2667 |      * @return bool True if anything changed
 | 
        
           |  |  | 2668 |      */
 | 
        
           |  |  | 2669 |     public function apply_default_settings() {
 | 
        
           |  |  | 2670 |         global $CFG;
 | 
        
           |  |  | 2671 |   | 
        
           |  |  | 2672 |         foreach ($this->forceable as $property) {
 | 
        
           |  |  | 2673 |   | 
        
           |  |  | 2674 |             if (isset($CFG->{"grade_$property"})) {
 | 
        
           |  |  | 2675 |   | 
        
           |  |  | 2676 |                 if ($CFG->{"grade_$property"} == -1) {
 | 
        
           |  |  | 2677 |                     continue; //temporary bc before version bump
 | 
        
           |  |  | 2678 |                 }
 | 
        
           |  |  | 2679 |                 $this->$property = $CFG->{"grade_$property"};
 | 
        
           |  |  | 2680 |             }
 | 
        
           |  |  | 2681 |         }
 | 
        
           |  |  | 2682 |     }
 | 
        
           |  |  | 2683 |   | 
        
           |  |  | 2684 |     /**
 | 
        
           |  |  | 2685 |      * Applies forced settings on this category
 | 
        
           |  |  | 2686 |      *
 | 
        
           |  |  | 2687 |      * @return bool True if anything changed
 | 
        
           |  |  | 2688 |      */
 | 
        
           |  |  | 2689 |     public function apply_forced_settings() {
 | 
        
           |  |  | 2690 |         global $CFG;
 | 
        
           |  |  | 2691 |   | 
        
           |  |  | 2692 |         $updated = false;
 | 
        
           |  |  | 2693 |   | 
        
           |  |  | 2694 |         foreach ($this->forceable as $property) {
 | 
        
           |  |  | 2695 |   | 
        
           |  |  | 2696 |             if (isset($CFG->{"grade_$property"}) and isset($CFG->{"grade_{$property}_flag"}) and
 | 
        
           |  |  | 2697 |                                                     ((int) $CFG->{"grade_{$property}_flag"} & 1)) {
 | 
        
           |  |  | 2698 |   | 
        
           |  |  | 2699 |                 if ($CFG->{"grade_$property"} == -1) {
 | 
        
           |  |  | 2700 |                     continue; //temporary bc before version bump
 | 
        
           |  |  | 2701 |                 }
 | 
        
           |  |  | 2702 |                 $this->$property = $CFG->{"grade_$property"};
 | 
        
           |  |  | 2703 |                 $updated = true;
 | 
        
           |  |  | 2704 |             }
 | 
        
           |  |  | 2705 |         }
 | 
        
           |  |  | 2706 |   | 
        
           |  |  | 2707 |         return $updated;
 | 
        
           |  |  | 2708 |     }
 | 
        
           |  |  | 2709 |   | 
        
           |  |  | 2710 |     /**
 | 
        
           |  |  | 2711 |      * Notification of change in forced category settings.
 | 
        
           |  |  | 2712 |      *
 | 
        
           |  |  | 2713 |      * Causes all course and category grade items to be marked as needing to be updated
 | 
        
           |  |  | 2714 |      */
 | 
        
           |  |  | 2715 |     public static function updated_forced_settings() {
 | 
        
           |  |  | 2716 |         global $CFG, $DB;
 | 
        
           |  |  | 2717 |         $params = array(1, 'course', 'category');
 | 
        
           |  |  | 2718 |         $sql = "UPDATE {grade_items} SET needsupdate=? WHERE itemtype=? or itemtype=?";
 | 
        
           |  |  | 2719 |         $DB->execute($sql, $params);
 | 
        
           |  |  | 2720 |     }
 | 
        
           |  |  | 2721 |   | 
        
           |  |  | 2722 |     /**
 | 
        
           |  |  | 2723 |      * Determine the default aggregation values for a given aggregation method.
 | 
        
           |  |  | 2724 |      *
 | 
        
           |  |  | 2725 |      * @param int $aggregationmethod The aggregation method constant value.
 | 
        
           |  |  | 2726 |      * @return array Containing the keys 'aggregationcoef', 'aggregationcoef2' and 'weightoverride'.
 | 
        
           |  |  | 2727 |      */
 | 
        
           |  |  | 2728 |     public static function get_default_aggregation_coefficient_values($aggregationmethod) {
 | 
        
           |  |  | 2729 |         $defaultcoefficients = array(
 | 
        
           |  |  | 2730 |             'aggregationcoef' => 0,
 | 
        
           |  |  | 2731 |             'aggregationcoef2' => 0,
 | 
        
           |  |  | 2732 |             'weightoverride' => 0
 | 
        
           |  |  | 2733 |         );
 | 
        
           |  |  | 2734 |   | 
        
           |  |  | 2735 |         switch ($aggregationmethod) {
 | 
        
           |  |  | 2736 |             case GRADE_AGGREGATE_WEIGHTED_MEAN:
 | 
        
           |  |  | 2737 |                 $defaultcoefficients['aggregationcoef'] = 1;
 | 
        
           |  |  | 2738 |                 break;
 | 
        
           |  |  | 2739 |             case GRADE_AGGREGATE_SUM:
 | 
        
           |  |  | 2740 |                 $defaultcoefficients['aggregationcoef2'] = 1;
 | 
        
           |  |  | 2741 |                 break;
 | 
        
           |  |  | 2742 |         }
 | 
        
           |  |  | 2743 |   | 
        
           |  |  | 2744 |         return $defaultcoefficients;
 | 
        
           |  |  | 2745 |     }
 | 
        
           |  |  | 2746 |   | 
        
           |  |  | 2747 |     /**
 | 
        
           |  |  | 2748 |      * Cleans the cache.
 | 
        
           |  |  | 2749 |      *
 | 
        
           |  |  | 2750 |      * We invalidate them all so it can be completely reloaded.
 | 
        
           |  |  | 2751 |      *
 | 
        
           |  |  | 2752 |      * Being conservative here, if there is a new grade_category we purge them, the important part
 | 
        
           |  |  | 2753 |      * is that this is not purged when there are no changes in grade_categories.
 | 
        
           |  |  | 2754 |      *
 | 
        
           |  |  | 2755 |      * @param bool $deleted
 | 
        
           |  |  | 2756 |      * @return void
 | 
        
           |  |  | 2757 |      */
 | 
        
           |  |  | 2758 |     protected function notify_changed($deleted) {
 | 
        
           |  |  | 2759 |         self::clean_record_set();
 | 
        
           |  |  | 2760 |     }
 | 
        
           |  |  | 2761 |   | 
        
           |  |  | 2762 |     /**
 | 
        
           |  |  | 2763 |      * Generates a unique key per query.
 | 
        
           |  |  | 2764 |      *
 | 
        
           |  |  | 2765 |      * Not unique between grade_object children. self::retrieve_record_set and self::set_record_set will be in charge of
 | 
        
           |  |  | 2766 |      * selecting the appropriate cache.
 | 
        
           |  |  | 2767 |      *
 | 
        
           |  |  | 2768 |      * @param array $params An array of conditions like $fieldname => $fieldvalue
 | 
        
           |  |  | 2769 |      * @return string
 | 
        
           |  |  | 2770 |      */
 | 
        
           |  |  | 2771 |     protected static function generate_record_set_key($params) {
 | 
        
           |  |  | 2772 |         return sha1(json_encode($params));
 | 
        
           |  |  | 2773 |     }
 | 
        
           |  |  | 2774 |   | 
        
           |  |  | 2775 |     /**
 | 
        
           |  |  | 2776 |      * Tries to retrieve a record set from the cache.
 | 
        
           |  |  | 2777 |      *
 | 
        
           |  |  | 2778 |      * @param array $params The query params
 | 
        
           |  |  | 2779 |      * @return grade_object[]|bool An array of grade_objects or false if not found.
 | 
        
           |  |  | 2780 |      */
 | 
        
           |  |  | 2781 |     protected static function retrieve_record_set($params) {
 | 
        
           |  |  | 2782 |         $cache = cache::make('core', 'grade_categories');
 | 
        
           |  |  | 2783 |         return $cache->get(self::generate_record_set_key($params));
 | 
        
           |  |  | 2784 |     }
 | 
        
           |  |  | 2785 |   | 
        
           |  |  | 2786 |     /**
 | 
        
           |  |  | 2787 |      * Sets a result to the records cache, even if there were no results.
 | 
        
           |  |  | 2788 |      *
 | 
        
           |  |  | 2789 |      * @param string $params The query params
 | 
        
           |  |  | 2790 |      * @param grade_object[]|bool $records An array of grade_objects or false if there are no records matching the $key filters
 | 
        
           |  |  | 2791 |      * @return void
 | 
        
           |  |  | 2792 |      */
 | 
        
           |  |  | 2793 |     protected static function set_record_set($params, $records) {
 | 
        
           |  |  | 2794 |         $cache = cache::make('core', 'grade_categories');
 | 
        
           |  |  | 2795 |         return $cache->set(self::generate_record_set_key($params), $records);
 | 
        
           |  |  | 2796 |     }
 | 
        
           |  |  | 2797 |   | 
        
           |  |  | 2798 |     /**
 | 
        
           |  |  | 2799 |      * Cleans the cache.
 | 
        
           |  |  | 2800 |      *
 | 
        
           |  |  | 2801 |      * Aggressive deletion to be conservative given the gradebook design.
 | 
        
           |  |  | 2802 |      * The key is based on the requested params, not easy nor worth to purge selectively.
 | 
        
           |  |  | 2803 |      *
 | 
        
           |  |  | 2804 |      * @return void
 | 
        
           |  |  | 2805 |      */
 | 
        
           |  |  | 2806 |     public static function clean_record_set() {
 | 
        
           |  |  | 2807 |         cache_helper::purge_by_event('changesingradecategories');
 | 
        
           |  |  | 2808 |     }
 | 
        
           |  |  | 2809 | }
 |