MDL-18778 more robust reset form defaults
[moodle.git] / lib / grade / grade_category.php
CommitLineData
8a31e65c 1<?php // $Id$
2
3///////////////////////////////////////////////////////////////////////////
4// //
5// NOTICE OF COPYRIGHT //
6// //
7// Moodle - Modular Object-Oriented Dynamic Learning Environment //
8// http://moodle.com //
9// //
1f0e4921 10// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
8a31e65c 11// //
12// This program is free software; you can redistribute it and/or modify //
13// it under the terms of the GNU General Public License as published by //
14// the Free Software Foundation; either version 2 of the License, or //
15// (at your option) any later version. //
16// //
17// This program is distributed in the hope that it will be useful, //
18// but WITHOUT ANY WARRANTY; without even the implied warranty of //
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
20// GNU General Public License for more details: //
21// //
22// http://www.gnu.org/copyleft/gpl.html //
23// //
24///////////////////////////////////////////////////////////////////////////
25
26require_once('grade_object.php');
27
3058964f 28class grade_category extends grade_object {
8a31e65c 29 /**
7c8a963f 30 * The DB table.
8a31e65c 31 * @var string $table
32 */
da3801e8 33 public $table = 'grade_categories';
4a490db0 34
8a31e65c 35 /**
3f2b0c8a 36 * Array of required table fields, must start with 'id'.
37 * @var array $required_fields
8a31e65c 38 */
da3801e8 39 public $required_fields = array('id', 'courseid', 'parent', 'depth', 'path', 'fullname', 'aggregation',
3f2b0c8a 40 'keephigh', 'droplow', 'aggregateonlygraded', 'aggregateoutcomes',
41 'aggregatesubcats', 'timecreated', 'timemodified');
4a490db0 42
8a31e65c 43 /**
44 * The course this category belongs to.
45 * @var int $courseid
46 */
da3801e8 47 public $courseid;
4a490db0 48
8a31e65c 49 /**
50 * The category this category belongs to (optional).
4a490db0 51 * @var int $parent
8a31e65c 52 */
da3801e8 53 public $parent;
4a490db0 54
8c846243 55 /**
56 * The grade_category object referenced by $this->parent (PK).
57 * @var object $parent_category
58 */
da3801e8 59 public $parent_category;
27f95e9b 60
e5c674f1 61 /**
62 * The number of parents this category has.
63 * @var int $depth
64 */
da3801e8 65 public $depth = 0;
e5c674f1 66
67 /**
c2efb501 68 * Shows the hierarchical path for this category as /1/2/3/ (like course_categories), the last number being
e5c674f1 69 * this category's autoincrement ID number.
70 * @var string $path
71 */
da3801e8 72 public $path;
e5c674f1 73
8a31e65c 74 /**
75 * The name of this category.
76 * @var string $fullname
77 */
da3801e8 78 public $fullname;
4a490db0 79
8a31e65c 80 /**
81 * A constant pointing to one of the predefined aggregation strategies (none, mean, median, sum etc) .
4a490db0 82 * @var int $aggregation
8a31e65c 83 */
da3801e8 84 public $aggregation = GRADE_AGGREGATE_MEAN;
4a490db0 85
8a31e65c 86 /**
87 * Keep only the X highest items.
88 * @var int $keephigh
89 */
da3801e8 90 public $keephigh = 0;
4a490db0 91
8a31e65c 92 /**
93 * Drop the X lowest items.
94 * @var int $droplow
95 */
da3801e8 96 public $droplow = 0;
4a490db0 97
c2efb501 98 /**
99 * Aggregate only graded items
100 * @var int $aggregateonlygraded
101 */
da3801e8 102 public $aggregateonlygraded = 0;
c2efb501 103
29d509f5 104 /**
105 * Aggregate outcomes together with normal items
c2efb501 106 * @var int $aggregateoutcomes
29d509f5 107 */
da3801e8 108 public $aggregateoutcomes = 0;
29d509f5 109
c2efb501 110 /**
111 * Ignore subcategories when aggregating
112 * @var int $aggregatesubcats
113 */
da3801e8 114 public $aggregatesubcats = 0;
c2efb501 115
8a31e65c 116 /**
117 * Array of grade_items or grade_categories nested exactly 1 level below this category
118 * @var array $children
119 */
da3801e8 120 public $children;
8a31e65c 121
7c8a963f 122 /**
4a490db0 123 * A hierarchical array of all children below this category. This is stored separately from
7c8a963f 124 * $children because it is more memory-intensive and may not be used as often.
125 * @var array $all_children
126 */
da3801e8 127 public $all_children;
7c8a963f 128
f151b073 129 /**
130 * An associated grade_item object, with itemtype=category, used to calculate and cache a set of grade values
131 * for this category.
132 * @var object $grade_item
133 */
da3801e8 134 public $grade_item;
f151b073 135
b3ac6c3e 136 /**
137 * Temporary sortorder for speedup of children resorting
138 */
da3801e8 139 public $sortorder;
b3ac6c3e 140
89a5f827 141 /**
142 * List of options which can be "forced" from site settings.
143 */
da3801e8 144 public $forceable = array('aggregation', 'keephigh', 'droplow', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats');
89a5f827 145
e5c674f1 146 /**
147 * Builds this category's path string based on its parents (if any) and its own id number.
148 * This is typically done just before inserting this object in the DB for the first time,
ce385eb4 149 * or when a new parent is added or changed. It is a recursive function: once the calling
150 * object no longer has a parent, the path is complete.
151 *
152 * @static
153 * @param object $grade_category
154 * @return int The depth of this category (2 means there is one parent)
e5c674f1 155 */
da3801e8 156 public function build_path($grade_category) {
157 global $DB;
158
ce385eb4 159 if (empty($grade_category->parent)) {
c2efb501 160 return '/'.$grade_category->id.'/';
ce385eb4 161 } else {
da3801e8 162 $parent = $DB->get_record('grade_categories', array('id' => $grade_category->parent));
9a68cffc 163 return grade_category::build_path($parent).$grade_category->id.'/';
ce385eb4 164 }
e5c674f1 165 }
166
8a31e65c 167 /**
f92dcad8 168 * Finds and returns a grade_category instance based on params.
61c33818 169 * @static
8a31e65c 170 *
f92dcad8 171 * @param array $params associative arrays varname=>value
172 * @return object grade_category instance or false if none found.
173 */
da3801e8 174 public static function fetch($params) {
f3ac8eb4 175 return grade_object::fetch_helper('grade_categories', 'grade_category', $params);
f92dcad8 176 }
177
178 /**
179 * Finds and returns all grade_category instances based on params.
180 * @static
181 *
182 * @param array $params associative arrays varname=>value
183 * @return array array of grade_category insatnces or false if none found.
184 */
da3801e8 185 public static function fetch_all($params) {
f3ac8eb4 186 return grade_object::fetch_all_helper('grade_categories', 'grade_category', $params);
ce385eb4 187 }
188
8f4a626d 189 /**
2cc4b0f9 190 * In addition to update() as defined in grade_object, call force_regrading of parent categories, if applicable.
aaff71da 191 * @param string $source from where was the object updated (mod/forum, manual, etc.)
192 * @return boolean success
8f4a626d 193 */
da3801e8 194 public function update($source=null) {
b3ac6c3e 195 // load the grade item or create a new one
196 $this->load_grade_item();
197
198 // force recalculation of path;
199 if (empty($this->path)) {
9a68cffc 200 $this->path = grade_category::build_path($this);
c2efb501 201 $this->depth = substr_count($this->path, '/') - 1;
1909a127 202 $updatechildren = true;
203 } else {
204 $updatechildren = false;
4a490db0 205 }
0fc7f624 206
89a5f827 207 $this->apply_forced_settings();
208
209 // these are exclusive
210 if ($this->droplow > 0) {
211 $this->keephigh = 0;
212 } else if ($this->keephigh > 0) {
213 $this->droplow = 0;
214 }
4a490db0 215
b3ac6c3e 216 // Recalculate grades if needed
217 if ($this->qualifies_for_regrading()) {
f8e6e4db 218 $this->force_regrading();
4a490db0 219 }
f8e6e4db 220
ced5ee59 221 $this->timemodified = time();
222
1909a127 223 $result = parent::update($source);
224
225 // now update paths in all child categories
226 if ($result and $updatechildren) {
227 if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
228 foreach ($children as $child) {
1909a127 229 $child->path = null;
230 $child->depth = 0;
231 $child->update($source);
da3801e8 232 }
1909a127 233 }
234 }
235
236 return $result;
8f4a626d 237 }
4a490db0 238
8f4a626d 239 /**
2cc4b0f9 240 * If parent::delete() is successful, send force_regrading message to parent category.
aaff71da 241 * @param string $source from where was the object deleted (mod/forum, manual, etc.)
242 * @return boolean success
8f4a626d 243 */
da3801e8 244 public function delete($source=null) {
f13002d5 245 $grade_item = $this->load_grade_item();
4a490db0 246
f615fbab 247 if ($this->is_course_category()) {
f3ac8eb4 248 if ($categories = grade_category::fetch_all(array('courseid'=>$this->courseid))) {
f615fbab 249 foreach ($categories as $category) {
250 if ($category->id == $this->id) {
251 continue; // do not delete course category yet
252 }
253 $category->delete($source);
254 }
aaff71da 255 }
2b0f65e2 256
f3ac8eb4 257 if ($items = grade_item::fetch_all(array('courseid'=>$this->courseid))) {
f615fbab 258 foreach ($items as $item) {
259 if ($item->id == $grade_item->id) {
260 continue; // do not delete course item yet
261 }
262 $item->delete($source);
263 }
264 }
265
266 } else {
267 $this->force_regrading();
2b0f65e2 268
f615fbab 269 $parent = $this->load_parent_category();
2b0f65e2 270
f615fbab 271 // Update children's categoryid/parent field first
f3ac8eb4 272 if ($children = grade_item::fetch_all(array('categoryid'=>$this->id))) {
f615fbab 273 foreach ($children as $child) {
274 $child->set_parent($parent->id);
275 }
276 }
f3ac8eb4 277 if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
f615fbab 278 foreach ($children as $child) {
279 $child->set_parent($parent->id);
280 }
aaff71da 281 }
282 }
f13002d5 283
aaff71da 284 // first delete the attached grade item and grades
285 $grade_item->delete($source);
f13002d5 286
287 // delete category itself
aaff71da 288 return parent::delete($source);
8f4a626d 289 }
4a490db0 290
ce385eb4 291 /**
292 * In addition to the normal insert() defined in grade_object, this method sets the depth
293 * and path for this object, and update the record accordingly. The reason why this must
294 * be done here instead of in the constructor, is that they both need to know the record's
4a490db0 295 * id number, which only gets created at insertion time.
f151b073 296 * This method also creates an associated grade_item if this wasn't done during construction.
aaff71da 297 * @param string $source from where was the object inserted (mod/forum, manual, etc.)
298 * @return int PK ID if successful, false otherwise
ce385eb4 299 */
da3801e8 300 public function insert($source=null) {
b3ac6c3e 301
302 if (empty($this->courseid)) {
2f137aa1 303 print_error('cannotinsertgrade');
b8ff92b6 304 }
4a490db0 305
b3ac6c3e 306 if (empty($this->parent)) {
f3ac8eb4 307 $course_category = grade_category::fetch_course_category($this->courseid);
b3ac6c3e 308 $this->parent = $course_category->id;
ce385eb4 309 }
4a490db0 310
b3ac6c3e 311 $this->path = null;
312
ced5ee59 313 $this->timecreated = $this->timemodified = time();
314
aaff71da 315 if (!parent::insert($source)) {
b3ac6c3e 316 debugging("Could not insert this category: " . print_r($this, true));
317 return false;
318 }
319
f8e6e4db 320 $this->force_regrading();
321
b3ac6c3e 322 // build path and depth
aaff71da 323 $this->update($source);
4a490db0 324
aaff71da 325 return $this->id;
b3ac6c3e 326 }
327
f2c88356 328 /**
f615fbab 329 * Internal function - used only from fetch_course_category()
330 * Normal insert() can not be used for course category
331 * @param int $courseid
332 * @return bool success
f2c88356 333 */
da3801e8 334 public function insert_course_category($courseid) {
1f0e4921 335 $this->courseid = $courseid;
8f6fdf43 336 $this->fullname = '?';
1f0e4921 337 $this->path = null;
338 $this->parent = null;
0c87b5aa 339 $this->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
4a490db0 340
190af29f 341 $this->apply_default_settings();
89a5f827 342 $this->apply_forced_settings();
343
ced5ee59 344 $this->timecreated = $this->timemodified = time();
345
aaff71da 346 if (!parent::insert('system')) {
b3ac6c3e 347 debugging("Could not insert this category: " . print_r($this, true));
348 return false;
f151b073 349 }
4a490db0 350
b3ac6c3e 351 // build path and depth
aaff71da 352 $this->update('system');
b3ac6c3e 353
aaff71da 354 return $this->id;
ce385eb4 355 }
4a490db0 356
8f4a626d 357 /**
358 * Compares the values held by this object with those of the matching record in DB, and returns
359 * whether or not these differences are sufficient to justify an update of all parent objects.
360 * This assumes that this object has an id number and a matching record in DB. If not, it will return false.
361 * @return boolean
362 */
da3801e8 363 public function qualifies_for_regrading() {
8f4a626d 364 if (empty($this->id)) {
6639ead3 365 debugging("Can not regrade non existing category");
8f4a626d 366 return false;
367 }
f3ac8eb4 368
369 $db_item = grade_category::fetch(array('id'=>$this->id));
4a490db0 370
c2efb501 371 $aggregationdiff = $db_item->aggregation != $this->aggregation;
372 $keephighdiff = $db_item->keephigh != $this->keephigh;
373 $droplowdiff = $db_item->droplow != $this->droplow;
374 $aggonlygrddiff = $db_item->aggregateonlygraded != $this->aggregateonlygraded;
375 $aggoutcomesdiff = $db_item->aggregateoutcomes != $this->aggregateoutcomes;
376 $aggsubcatsdiff = $db_item->aggregatesubcats != $this->aggregatesubcats;
8f4a626d 377
c2efb501 378 return ($aggregationdiff || $keephighdiff || $droplowdiff || $aggonlygrddiff || $aggoutcomesdiff || $aggsubcatsdiff);
8f4a626d 379 }
8c846243 380
381 /**
f8e6e4db 382 * Marks the category and course item as needing update - categories are always regraded.
383 * @return void
8c846243 384 */
da3801e8 385 public function force_regrading() {
f8e6e4db 386 $grade_item = $this->load_grade_item();
387 $grade_item->force_regrading();
8c846243 388 }
389
0aa32279 390 /**
0758a08e 391 * Generates and saves final grades in associated category grade item.
1994d890 392 * These immediate children must already have their own final grades.
0758a08e 393 * The category's aggregation method is used to generate final grades.
ac9b0805 394 *
395 * Please note that category grade is either calculated or aggregated - not both at the same time.
396 *
c86caae7 397 * This method must be used ONLY from grade_item::regrade_final_grades(),
ac9b0805 398 * because the calculation must be done in correct order!
b8ff92b6 399 *
4a490db0 400 * Steps to follow:
ac9b0805 401 * 1. Get final grades from immediate children
2df71235 402 * 3. Aggregate these grades
0758a08e 403 * 4. Save them in final grades of associated category grade item
0aa32279 404 */
da3801e8 405 public function generate_grades($userid=null) {
406 global $CFG, $DB;
4a490db0 407
ac9b0805 408 $this->load_grade_item();
2cc4b0f9 409
410 if ($this->grade_item->is_locked()) {
411 return true; // no need to recalculate locked items
412 }
413
89a5f827 414 // find grade items of immediate children (category or grade items) and force site settings
61c33818 415 $depends_on = $this->grade_item->depends_on();
b3ac6c3e 416
f8e6e4db 417 if (empty($depends_on)) {
418 $items = false;
419 } else {
5b0af8c5 420 list($usql, $params) = $DB->get_in_or_equal($depends_on);
f8e6e4db 421 $sql = "SELECT *
5b0af8c5 422 FROM {grade_items}
423 WHERE id $usql";
424 $items = $DB->get_records_sql($sql, $params);
f8e6e4db 425 }
4a490db0 426
5b0af8c5 427 $grade_inst = new grade_grade();
428 $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
429
430 // where to look for final grades - include grade of this item too, we will store the results there
431 $gis = array_merge($depends_on, array($this->grade_item->id));
432 list($usql, $params) = $DB->get_in_or_equal($gis);
433
f8e6e4db 434 if ($userid) {
5b0af8c5 435 $usersql = "AND g.userid=?";
436 $params[] = $userid;
f8e6e4db 437 } else {
438 $usersql = "";
b8ff92b6 439 }
4a490db0 440
3f2b0c8a 441 $sql = "SELECT $fields
5b0af8c5 442 FROM {grade_grades} g, {grade_items} gi
443 WHERE gi.id = g.itemid AND gi.id $usql $usersql
ac9b0805 444 ORDER BY g.userid";
b8ff92b6 445
9580a21f 446 // group the results by userid and aggregate the grades for this user
5b0af8c5 447 if ($rs = $DB->get_recordset_sql($sql, $params)) {
03cedd62 448 $prevuser = 0;
449 $grade_values = array();
450 $excluded = array();
451 $oldgrade = null;
da3801e8 452 foreach ($rs as $used) {
03cedd62 453 if ($used->userid != $prevuser) {
454 $this->aggregate_grades($prevuser, $items, $grade_values, $oldgrade, $excluded);
455 $prevuser = $used->userid;
456 $grade_values = array();
457 $excluded = array();
458 $oldgrade = null;
459 }
460 $grade_values[$used->itemid] = $used->finalgrade;
461 if ($used->excluded) {
462 $excluded[] = $used->itemid;
463 }
464 if ($this->grade_item->id == $used->itemid) {
465 $oldgrade = $used;
2df71235 466 }
b8ff92b6 467 }
da3801e8 468 $rs->close();
03cedd62 469 $this->aggregate_grades($prevuser, $items, $grade_values, $oldgrade, $excluded);//the last one
b8ff92b6 470 }
471
b8ff92b6 472 return true;
473 }
474
475 /**
ac9b0805 476 * internal function for category grades aggregation
ced5ee59 477 *
1994d890 478 * @param int $userid
479 * @param array $items
480 * @param array $grade_values
f1ad9e04 481 * @param object $oldgrade
1994d890 482 * @param bool $excluded
483 * @return boolean (just plain return;)
b8ff92b6 484 */
da3801e8 485 private function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded) {
e171963b 486 global $CFG;
b8ff92b6 487 if (empty($userid)) {
f8e6e4db 488 //ignore first call
b8ff92b6 489 return;
490 }
4a490db0 491
f8e6e4db 492 if ($oldgrade) {
66690b69 493 $oldfinalgrade = $oldgrade->finalgrade;
f3ac8eb4 494 $grade = new grade_grade($oldgrade, false);
f8e6e4db 495 $grade->grade_item =& $this->grade_item;
b8ff92b6 496
f8e6e4db 497 } else {
498 // insert final grade - it will be needed later anyway
f3ac8eb4 499 $grade = new grade_grade(array('itemid'=>$this->grade_item->id, 'userid'=>$userid), false);
f8e6e4db 500 $grade->grade_item =& $this->grade_item;
f1ad9e04 501 $grade->insert('system');
502 $oldfinalgrade = null;
f8e6e4db 503 }
f1ad9e04 504
c86caae7 505 // no need to recalculate locked or overridden grades
506 if ($grade->is_locked() or $grade->is_overridden()) {
2cc4b0f9 507 return;
ac9b0805 508 }
509
f8e6e4db 510 // can not use own final category grade in calculation
9580a21f 511 unset($grade_values[$this->grade_item->id]);
f8e6e4db 512
f1ad9e04 513
514 /// sum is a special aggregation types - it adjusts the min max, does not use relative values
515 if ($this->aggregation == GRADE_AGGREGATE_SUM) {
516 $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded);
517 return;
518 }
519
0758a08e 520 // if no grades calculation possible or grading not allowed clear final grade
9580a21f 521 if (empty($grade_values) or empty($items) or ($this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE)) {
f8e6e4db 522 $grade->finalgrade = null;
66690b69 523 if (!is_null($oldfinalgrade)) {
0758a08e 524 $grade->update('aggregation');
f8e6e4db 525 }
b8ff92b6 526 return;
527 }
0758a08e 528
f8e6e4db 529 /// normalize the grades first - all will have value 0...1
d5f0aa01 530 // ungraded items are not used in aggregation
23207a1a 531 foreach ($grade_values as $itemid=>$v) {
b8ff92b6 532 if (is_null($v)) {
533 // null means no grade
23207a1a 534 unset($grade_values[$itemid]);
535 continue;
536 } else if (in_array($itemid, $excluded)) {
537 unset($grade_values[$itemid]);
b8ff92b6 538 continue;
0aa32279 539 }
9a68cffc 540 $grade_values[$itemid] = grade_grade::standardise_score($v, $items[$itemid]->grademin, $items[$itemid]->grademax, 0, 1);
0aa32279 541 }
dda0c7e6 542
eacd3700 543 // use min grade if grade missing for these types
c2efb501 544 if (!$this->aggregateonlygraded) {
545 foreach($items as $itemid=>$value) {
546 if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
547 $grade_values[$itemid] = 0;
eacd3700 548 }
c2efb501 549 }
eacd3700 550 }
551
552 // limit and sort
9580a21f 553 $this->apply_limit_rules($grade_values);
554 asort($grade_values, SORT_NUMERIC);
4a490db0 555
d5f0aa01 556 // let's see we have still enough grades to do any statistics
9580a21f 557 if (count($grade_values) == 0) {
ac9b0805 558 // not enough attempts yet
f8e6e4db 559 $grade->finalgrade = null;
66690b69 560 if (!is_null($oldfinalgrade)) {
0758a08e 561 $grade->update('aggregation');
b8ff92b6 562 }
563 return;
564 }
2df71235 565
d297269d 566 // do the maths
567 $agg_grade = $this->aggregate_values($grade_values, $items);
568
0758a08e 569 // recalculate the grade back to requested range
9a68cffc 570 $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax);
d297269d 571
f1ad9e04 572 if (is_null($finalgrade)) {
573 $grade->finalgrade = null;
d297269d 574 } else {
f1ad9e04 575 $grade->finalgrade = (float)bounded_number($this->grade_item->grademin, $finalgrade, $this->grade_item->grademax);
d297269d 576 }
577
578 // update in db if changed
25bcd908 579 if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
0758a08e 580 $grade->update('aggregation');
d297269d 581 }
582
583 return;
584 }
585
89a5f827 586 /**
587 * Internal function - aggregation maths.
54c4a2cb 588 * Must be public: used by grade_grade::get_hiding_affected()
89a5f827 589 */
54c4a2cb 590 public function aggregate_values($grade_values, $items) {
b8ff92b6 591 switch ($this->aggregation) {
c2efb501 592 case GRADE_AGGREGATE_MEDIAN: // Middle point value in the set: ignores frequencies
9580a21f 593 $num = count($grade_values);
c186c7b2 594 $grades = array_values($grade_values);
595 if ($num % 2 == 0) {
9c8d38fa 596 $agg_grade = ($grades[intval($num/2)-1] + $grades[intval($num/2)]) / 2;
b8ff92b6 597 } else {
9c8d38fa 598 $agg_grade = $grades[intval(($num/2)-0.5)];
b8ff92b6 599 }
600 break;
ac9b0805 601
c2efb501 602 case GRADE_AGGREGATE_MIN:
9c8d38fa 603 $agg_grade = reset($grade_values);
b8ff92b6 604 break;
605
c2efb501 606 case GRADE_AGGREGATE_MAX:
9c8d38fa 607 $agg_grade = array_pop($grade_values);
b8ff92b6 608 break;
609
c2efb501 610 case GRADE_AGGREGATE_MODE: // the most common value, average used if multimode
0198929c 611 // array_count_values only counts INT and STRING, so if grades are floats we must convert them to string
612 $converted_grade_values = array();
613
614 foreach ($grade_values as $k => $gv) {
615 if (!is_int($gv) && !is_string($gv)) {
616 $converted_grade_values[$k] = (string) $gv;
617 } else {
618 $converted_grade_values[$k] = $gv;
619 }
620 }
621
622 $freq = array_count_values($converted_grade_values);
95affb8a 623 arsort($freq); // sort by frequency keeping keys
624 $top = reset($freq); // highest frequency count
625 $modes = array_keys($freq, $top); // search for all modes (have the same highest count)
626 rsort($modes, SORT_NUMERIC); // get highes mode
9c8d38fa 627 $agg_grade = reset($modes);
d5fab31f 628 break;
95affb8a 629
1426edac 630 case GRADE_AGGREGATE_WEIGHTED_MEAN: // Weighted average of all existing final grades, weight specified in coef
9580a21f 631 $weightsum = 0;
632 $sum = 0;
eacd3700 633 foreach($grade_values as $itemid=>$grade_value) {
634 if ($items[$itemid]->aggregationcoef <= 0) {
9580a21f 635 continue;
636 }
eacd3700 637 $weightsum += $items[$itemid]->aggregationcoef;
638 $sum += $items[$itemid]->aggregationcoef * $grade_value;
9580a21f 639 }
640 if ($weightsum == 0) {
9c8d38fa 641 $agg_grade = null;
9580a21f 642 } else {
9c8d38fa 643 $agg_grade = $sum / $weightsum;
9580a21f 644 }
645 break;
646
1426edac 647 case GRADE_AGGREGATE_WEIGHTED_MEAN2: // Weighted average of all existing final grades, weight is the range of grade (ususally grademax)
648 $weightsum = 0;
649 $sum = 0;
650 foreach($grade_values as $itemid=>$grade_value) {
651 $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
652 if ($weight <= 0) {
653 continue;
654 }
655 $weightsum += $weight;
656 $sum += $weight * $grade_value;
657 }
658 if ($weightsum == 0) {
659 $agg_grade = null;
660 } else {
661 $agg_grade = $sum / $weightsum;
662 }
663 break;
664
c2efb501 665 case GRADE_AGGREGATE_EXTRACREDIT_MEAN: // special average
9580a21f 666 $num = 0;
667 $sum = 0;
eacd3700 668 foreach($grade_values as $itemid=>$grade_value) {
669 if ($items[$itemid]->aggregationcoef == 0) {
9580a21f 670 $num += 1;
671 $sum += $grade_value;
eacd3700 672 } else if ($items[$itemid]->aggregationcoef > 0) {
673 $sum += $items[$itemid]->aggregationcoef * $grade_value;
9580a21f 674 }
675 }
676 if ($num == 0) {
9c8d38fa 677 $agg_grade = $sum; // only extra credits or wrong coefs
9580a21f 678 } else {
9c8d38fa 679 $agg_grade = $sum / $num;
9580a21f 680 }
681 break;
682
c2efb501 683 case GRADE_AGGREGATE_MEAN: // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
ac9b0805 684 default:
9580a21f 685 $num = count($grade_values);
686 $sum = array_sum($grade_values);
9c8d38fa 687 $agg_grade = $sum / $num;
b8ff92b6 688 break;
689 }
690
d297269d 691 return $agg_grade;
0aa32279 692 }
0758a08e 693
0758a08e 694 /**
695 * internal function for category grades summing
696 *
f1ad9e04 697 * @param object $grade
0758a08e 698 * @param int $userid
f1ad9e04 699 * @param float $oldfinalgrade
0758a08e 700 * @param array $items
701 * @param array $grade_values
0758a08e 702 * @param bool $excluded
703 * @return boolean (just plain return;)
704 */
da3801e8 705 private function sum_grades(&$grade, $oldfinalgrade, $items, $grade_values, $excluded) {
0758a08e 706 // ungraded and exluded items are not used in aggregation
707 foreach ($grade_values as $itemid=>$v) {
708 if (is_null($v)) {
709 unset($grade_values[$itemid]);
710 } else if (in_array($itemid, $excluded)) {
711 unset($grade_values[$itemid]);
712 }
713 }
714
d28f25a4 715 // use 0 if grade missing, droplow used and aggregating all items
716 if (!$this->aggregateonlygraded and !empty($this->droplow)) {
717 foreach($items as $itemid=>$value) {
718 if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
719 $grade_values[$itemid] = 0;
720 }
721 }
722 }
723
0758a08e 724 $max = 0;
725
726 //find max grade
727 foreach ($items as $item) {
2e0d37fe 728 if ($item->aggregationcoef > 0) {
729 // extra credit from this activity - does not affect total
730 continue;
0758a08e 731 }
91f9a62c 732 if ($item->gradetype == GRADE_TYPE_VALUE) {
733 $max += $item->grademax;
734 } else if ($item->gradetype == GRADE_TYPE_SCALE) {
735 $max += $item->grademax - 1; // scales min is 1
736 }
0758a08e 737 }
738
739 if ($this->grade_item->grademax != $max or $this->grade_item->grademin != 0 or $this->grade_item->gradetype != GRADE_TYPE_VALUE){
740 $this->grade_item->grademax = $max;
741 $this->grade_item->grademin = 0;
742 $this->grade_item->gradetype = GRADE_TYPE_VALUE;
743 $this->grade_item->update('aggregation');
744 }
745
746 $this->apply_limit_rules($grade_values);
747
748 $sum = array_sum($grade_values);
66690b69 749 $grade->finalgrade = bounded_number($this->grade_item->grademin, $sum, $this->grade_item->grademax);
0758a08e 750
751 // update in db if changed
25bcd908 752 if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
0758a08e 753 $grade->update('aggregation');
754 }
755
756 return;
757 }
758
adc2f286 759 /**
760 * Given an array of grade values (numerical indices), applies droplow or keephigh
761 * rules to limit the final array.
9580a21f 762 * @param array $grade_values
adc2f286 763 * @return array Limited grades.
764 */
da3801e8 765 public function apply_limit_rules(&$grade_values) {
9580a21f 766 arsort($grade_values, SORT_NUMERIC);
adc2f286 767 if (!empty($this->droplow)) {
768 for ($i = 0; $i < $this->droplow; $i++) {
9580a21f 769 array_pop($grade_values);
adc2f286 770 }
4a490db0 771 } elseif (!empty($this->keephigh)) {
9580a21f 772 while (count($grade_values) > $this->keephigh) {
773 array_pop($grade_values);
adc2f286 774 }
775 }
0aa32279 776 }
777
9580a21f 778
779 /**
780 * Returns true if category uses special aggregation coeficient
781 * @return boolean true if coeficient used
782 */
4e9ca991 783 public function is_aggregationcoef_used() {
c2efb501 784 return ($this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN
2e0d37fe 785 or $this->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN
786 or $this->aggregation == GRADE_AGGREGATE_SUM);
ba74762b 787
9580a21f 788 }
789
1c307f21 790 /**
b3ac6c3e 791 * Returns tree with all grade_items and categories as elements
792 * @static
793 * @param int $courseid
b3ac6c3e 794 * @param boolean $include_category_items as category children
795 * @return array
1c307f21 796 */
da3801e8 797 public static function fetch_course_tree($courseid, $include_category_items=false) {
f3ac8eb4 798 $course_category = grade_category::fetch_course_category($courseid);
514a3467 799 $category_array = array('object'=>$course_category, 'type'=>'category', 'depth'=>1,
800 'children'=>$course_category->get_children($include_category_items));
801 $sortorder = 1;
802 $course_category->set_sortorder($sortorder);
803 $course_category->sortorder = $sortorder;
f3ac8eb4 804 return grade_category::_fetch_course_tree_recursion($category_array, $sortorder);
1c307f21 805 }
806
da3801e8 807 private function _fetch_course_tree_recursion($category_array, &$sortorder) {
b3ac6c3e 808 // update the sortorder in db if needed
809 if ($category_array['object']->sortorder != $sortorder) {
810 $category_array['object']->set_sortorder($sortorder);
ce385eb4 811 }
ce385eb4 812
314c4336 813 // store the grade_item or grade_category instance with extra info
814 $result = array('object'=>$category_array['object'], 'type'=>$category_array['type'], 'depth'=>$category_array['depth']);
b3ac6c3e 815
816 // reuse final grades if there
817 if (array_key_exists('finalgrades', $category_array)) {
818 $result['finalgrades'] = $category_array['finalgrades'];
819 }
820
821 // recursively resort children
822 if (!empty($category_array['children'])) {
823 $result['children'] = array();
29d509f5 824 //process the category item first
825 $cat_item_id = null;
b3ac6c3e 826 foreach($category_array['children'] as $oldorder=>$child_array) {
314c4336 827 if ($child_array['type'] == 'courseitem' or $child_array['type'] == 'categoryitem') {
f3ac8eb4 828 $result['children'][$sortorder] = grade_category::_fetch_course_tree_recursion($child_array, $sortorder);
29d509f5 829 }
2b0f65e2 830 }
29d509f5 831 foreach($category_array['children'] as $oldorder=>$child_array) {
832 if ($child_array['type'] != 'courseitem' and $child_array['type'] != 'categoryitem') {
f3ac8eb4 833 $result['children'][++$sortorder] = grade_category::_fetch_course_tree_recursion($child_array, $sortorder);
b3ac6c3e 834 }
835 }
836 }
837
838 return $result;
ce385eb4 839 }
7c8a963f 840
841 /**
4a490db0 842 * Fetches and returns all the children categories and/or grade_items belonging to this category.
843 * By default only returns the immediate children (depth=1), but deeper levels can be requested,
a39cac25 844 * as well as all levels (0). The elements are indexed by sort order.
7c8a963f 845 * @return array Array of child objects (grade_category and grade_item).
846 */
da3801e8 847 public function get_children($include_category_items=false) {
848 global $DB;
b3ac6c3e 849
850 // This function must be as fast as possible ;-)
851 // fetch all course grade items and categories into memory - we do not expect hundreds of these in course
852 // we have to limit the number of queries though, because it will be used often in grade reports
853
da3801e8 854 $cats = $DB->get_records('grade_categories', array('courseid' => $this->courseid));
855 $items = $DB->get_records('grade_items', array('courseid' => $this->courseid));
4a490db0 856
b3ac6c3e 857 // init children array first
858 foreach ($cats as $catid=>$cat) {
859 $cats[$catid]->children = array();
27f95e9b 860 }
4a490db0 861
b3ac6c3e 862 //first attach items to cats and add category sortorder
863 foreach ($items as $item) {
864 if ($item->itemtype == 'course' or $item->itemtype == 'category') {
865 $cats[$item->iteminstance]->sortorder = $item->sortorder;
4a490db0 866
b3ac6c3e 867 if (!$include_category_items) {
868 continue;
869 }
870 $categoryid = $item->iteminstance;
871 } else {
872 $categoryid = $item->categoryid;
873 }
874
875 // prevent problems with duplicate sortorders in db
876 $sortorder = $item->sortorder;
877 while(array_key_exists($sortorder, $cats[$categoryid]->children)) {
f13002d5 878 //debugging("$sortorder exists in item loop");
b3ac6c3e 879 $sortorder++;
880 }
881
882 $cats[$categoryid]->children[$sortorder] = $item;
883
884 }
885
886 // now find the requested category and connect categories as children
887 $category = false;
888 foreach ($cats as $catid=>$cat) {
ec3717e1 889 if (empty($cat->parent)) {
890 if ($cat->path !== '/'.$cat->id.'/') {
891 $grade_category = new grade_category($cat, false);
892 $grade_category->path = '/'.$cat->id.'/';
893 $grade_category->depth = 1;
894 $grade_category->update('system');
895 return $this->get_children($include_category_items);
896 }
897 } else {
898 if (empty($cat->path) or !preg_match('|/'.$cat->parent.'/'.$cat->id.'/$|', $cat->path)) {
899 //fix paths and depts
900 static $recursioncounter = 0; // prevents infinite recursion
901 $recursioncounter++;
da3801e8 902 if ($recursioncounter < 5) {
ec3717e1 903 // fix paths and depths!
904 $grade_category = new grade_category($cat, false);
905 $grade_category->depth = 0;
906 $grade_category->path = null;
907 $grade_category->update('system');
908 return $this->get_children($include_category_items);
909 }
da3801e8 910 }
b3ac6c3e 911 // prevent problems with duplicate sortorders in db
912 $sortorder = $cat->sortorder;
913 while(array_key_exists($sortorder, $cats[$cat->parent]->children)) {
f13002d5 914 //debugging("$sortorder exists in cat loop");
b3ac6c3e 915 $sortorder++;
916 }
917
65370356 918 $cats[$cat->parent]->children[$sortorder] = &$cats[$catid];
b3ac6c3e 919 }
f3ac8eb4 920
b3ac6c3e 921 if ($catid == $this->id) {
922 $category = &$cats[$catid];
923 }
924 }
925
926 unset($items); // not needed
927 unset($cats); // not needed
928
f3ac8eb4 929 $children_array = grade_category::_get_children_recursion($category);
b3ac6c3e 930
931 ksort($children_array);
932
933 return $children_array;
934
935 }
936
da3801e8 937 private function _get_children_recursion($category) {
b3ac6c3e 938
939 $children_array = array();
940 foreach($category->children as $sortorder=>$child) {
941 if (array_key_exists('itemtype', $child)) {
f3ac8eb4 942 $grade_item = new grade_item($child, false);
4faf5f99 943 if (in_array($grade_item->itemtype, array('course', 'category'))) {
944 $type = $grade_item->itemtype.'item';
945 $depth = $category->depth;
314c4336 946 } else {
947 $type = 'item';
948 $depth = $category->depth; // we use this to set the same colour
b3ac6c3e 949 }
4faf5f99 950 $children_array[$sortorder] = array('object'=>$grade_item, 'type'=>$type, 'depth'=>$depth);
4a490db0 951
7c8a963f 952 } else {
f3ac8eb4 953 $children = grade_category::_get_children_recursion($child);
954 $grade_category = new grade_category($child, false);
b3ac6c3e 955 if (empty($children)) {
314c4336 956 $children = array();
7c8a963f 957 }
4faf5f99 958 $children_array[$sortorder] = array('object'=>$grade_category, 'type'=>'category', 'depth'=>$grade_category->depth, 'children'=>$children);
314c4336 959 }
27f95e9b 960 }
961
b3ac6c3e 962 // sort the array
963 ksort($children_array);
964
27f95e9b 965 return $children_array;
966 }
4a490db0 967
f151b073 968 /**
ab53054f 969 * Uses get_grade_item to load or create a grade_item, then saves it as $this->grade_item.
f151b073 970 * @return object Grade_item
971 */
da3801e8 972 public function load_grade_item() {
ac9b0805 973 if (empty($this->grade_item)) {
974 $this->grade_item = $this->get_grade_item();
975 }
ab53054f 976 return $this->grade_item;
977 }
4a490db0 978
ab53054f 979 /**
980 * Retrieves from DB and instantiates the associated grade_item object.
981 * If no grade_item exists yet, create one.
982 * @return object Grade_item
983 */
da3801e8 984 public function get_grade_item() {
c91ed4be 985 if (empty($this->id)) {
986 debugging("Attempt to obtain a grade_category's associated grade_item without the category's ID being set.");
987 return false;
988 }
989
b3ac6c3e 990 if (empty($this->parent)) {
991 $params = array('courseid'=>$this->courseid, 'itemtype'=>'course', 'iteminstance'=>$this->id);
992
993 } else {
994 $params = array('courseid'=>$this->courseid, 'itemtype'=>'category', 'iteminstance'=>$this->id);
995 }
4ac209d5 996
f3ac8eb4 997 if (!$grade_items = grade_item::fetch_all($params)) {
b8ff92b6 998 // create a new one
f3ac8eb4 999 $grade_item = new grade_item($params, false);
b8ff92b6 1000 $grade_item->gradetype = GRADE_TYPE_VALUE;
f8e6e4db 1001 $grade_item->insert('system');
4a490db0 1002
b8ff92b6 1003 } else if (count($grade_items) == 1){
1004 // found existing one
1005 $grade_item = reset($grade_items);
4a490db0 1006
b8ff92b6 1007 } else {
1008 debugging("Found more than one grade_item attached to category id:".$this->id);
ac9b0805 1009 // return first one
1010 $grade_item = reset($grade_items);
2c72af1f 1011 }
1012
ab53054f 1013 return $grade_item;
f151b073 1014 }
8c846243 1015
1016 /**
1017 * Uses $this->parent to instantiate $this->parent_category based on the
1018 * referenced record in the DB.
1019 * @return object Parent_category
1020 */
da3801e8 1021 public function load_parent_category() {
8c846243 1022 if (empty($this->parent_category) && !empty($this->parent)) {
ab53054f 1023 $this->parent_category = $this->get_parent_category();
8c846243 1024 }
1025 return $this->parent_category;
4a490db0 1026 }
1027
ab53054f 1028 /**
1029 * Uses $this->parent to instantiate and return a grade_category object.
1030 * @return object Parent_category
1031 */
da3801e8 1032 public function get_parent_category() {
ab53054f 1033 if (!empty($this->parent)) {
f3ac8eb4 1034 $parent_category = new grade_category(array('id' => $this->parent));
4a490db0 1035 return $parent_category;
ab53054f 1036 } else {
1037 return null;
1038 }
1039 }
1040
2186f72c 1041 /**
4a490db0 1042 * Returns the most descriptive field for this object. This is a standard method used
2186f72c 1043 * when we do not know the exact type of an object.
1044 * @return string name
1045 */
da3801e8 1046 public function get_name() {
1047 global $DB;
8f6fdf43 1048 // For a course category, we return the course name if the fullname is set to '?' in the DB (empty in the category edit form)
1049 if (empty($this->parent) && $this->fullname == '?') {
da3801e8 1050 $course = $DB->get_record('course', array('id'=> $this->courseid));
410753fb 1051 return format_string($course->fullname);
314c4336 1052 } else {
1053 return $this->fullname;
1054 }
2186f72c 1055 }
c91ed4be 1056
0fc7f624 1057 /**
1058 * Sets this category's parent id. A generic method shared by objects that have a parent id of some kind.
f13002d5 1059 * @param int parentid
1060 * @return boolean success
0fc7f624 1061 */
da3801e8 1062 public function set_parent($parentid, $source=null) {
f13002d5 1063 if ($this->parent == $parentid) {
1064 return true;
1065 }
1066
1067 if ($parentid == $this->id) {
2f137aa1 1068 print_error('cannotassignselfasparent');
f13002d5 1069 }
1070
1071 if (empty($this->parent) and $this->is_course_category()) {
2f137aa1 1072 print_error('cannothaveparentcate');
b3ac6c3e 1073 }
f13002d5 1074
1075 // find parent and check course id
f3ac8eb4 1076 if (!$parent_category = grade_category::fetch(array('id'=>$parentid, 'courseid'=>$this->courseid))) {
b3ac6c3e 1077 return false;
1078 }
1079
f8e6e4db 1080 $this->force_regrading();
b3ac6c3e 1081
1082 // set new parent category
f8e6e4db 1083 $this->parent = $parent_category->id;
1084 $this->parent_category =& $parent_category;
b3ac6c3e 1085 $this->path = null; // remove old path and depth - will be recalculated in update()
ec3717e1 1086 $this->depth = 0; // remove old path and depth - will be recalculated in update()
f8e6e4db 1087 $this->update($source);
b3ac6c3e 1088
15b462da 1089 return $this->update($source);
b3ac6c3e 1090 }
1091
1092 /**
1093 * Returns the final values for this grade category.
1094 * @param int $userid Optional: to retrieve a single final grade
1095 * @return mixed An array of all final_grades (stdClass objects) for this grade_item, or a single final_grade.
1096 */
da3801e8 1097 public function get_final($userid=NULL) {
b3ac6c3e 1098 $this->load_grade_item();
1099 return $this->grade_item->get_final($userid);
0fc7f624 1100 }
4a490db0 1101
0fc7f624 1102 /**
4a490db0 1103 * Returns the sortorder of the associated grade_item. This method is also available in
5fad5061 1104 * grade_item, for cases where the object type is not known.
0fc7f624 1105 * @return int Sort order
1106 */
da3801e8 1107 public function get_sortorder() {
b3ac6c3e 1108 $this->load_grade_item();
1109 return $this->grade_item->get_sortorder();
0fc7f624 1110 }
1111
be7c0693 1112 /**
1113 * Returns the idnumber of the associated grade_item. This method is also available in
1114 * grade_item, for cases where the object type is not known.
1115 * @return string idnumber
1116 */
da3801e8 1117 public function get_idnumber() {
be7c0693 1118 $this->load_grade_item();
1119 return $this->grade_item->get_idnumber();
1120 }
1121
0fc7f624 1122 /**
b3ac6c3e 1123 * Sets sortorder variable for this category.
4a490db0 1124 * This method is also available in grade_item, for cases where the object type is not know.
0fc7f624 1125 * @param int $sortorder
1126 * @return void
1127 */
da3801e8 1128 public function set_sortorder($sortorder) {
b3ac6c3e 1129 $this->load_grade_item();
1130 $this->grade_item->set_sortorder($sortorder);
1131 }
1132
6639ead3 1133 /**
1134 * Move this category after the given sortorder - does not change the parent
1135 * @param int $sortorder to place after
1136 */
da3801e8 1137 public function move_after_sortorder($sortorder) {
f13002d5 1138 $this->load_grade_item();
1139 $this->grade_item->move_after_sortorder($sortorder);
1140 }
1141
b3ac6c3e 1142 /**
f13002d5 1143 * Return true if this is the top most category that represents the total course grade.
b3ac6c3e 1144 * @return boolean
1145 */
da3801e8 1146 public function is_course_category() {
b3ac6c3e 1147 $this->load_grade_item();
1148 return $this->grade_item->is_course_item();
1149 }
1150
1151 /**
1152 * Return the top most course category.
1153 * @static
1154 * @return object grade_category instance for course grade
1155 */
da3801e8 1156 public function fetch_course_category($courseid) {
a4503119 1157 if (empty($courseid)) {
1158 debugging('Missing course id!');
1159 return false;
1160 }
b3ac6c3e 1161
1162 // course category has no parent
f3ac8eb4 1163 if ($course_category = grade_category::fetch(array('courseid'=>$courseid, 'parent'=>null))) {
b3ac6c3e 1164 return $course_category;
1165 }
1166
1167 // create a new one
f3ac8eb4 1168 $course_category = new grade_category();
b3ac6c3e 1169 $course_category->insert_course_category($courseid);
1170
1171 return $course_category;
0fc7f624 1172 }
4ac209d5 1173
79eabc2a 1174 /**
1175 * Is grading object editable?
1176 * @return boolean
1177 */
da3801e8 1178 public function is_editable() {
79eabc2a 1179 return true;
1180 }
1181
5fad5061 1182 /**
4a490db0 1183 * Returns the locked state/date of the associated grade_item. This method is also available in
1184 * grade_item, for cases where the object type is not known.
22e23c78 1185 * @return boolean
5fad5061 1186 */
da3801e8 1187 public function is_locked() {
5fad5061 1188 $this->load_grade_item();
22e23c78 1189 return $this->grade_item->is_locked();
5fad5061 1190 }
1191
1192 /**
1193 * Sets the grade_item's locked variable and updates the grade_item.
1194 * Method named after grade_item::set_locked().
1195 * @param int $locked 0, 1 or a timestamp int(10) after which date the item will be locked.
fb0e3570 1196 * @param boolean $cascade lock/unlock child objects too
2b0f65e2 1197 * @param boolean $refresh refresh grades when unlocking
9580a21f 1198 * @return boolean success if category locked (not all children mayb be locked though)
5fad5061 1199 */
da3801e8 1200 public function set_locked($lockedstate, $cascade=false, $refresh=true) {
5fad5061 1201 $this->load_grade_item();
2b0f65e2 1202
fb0e3570 1203 $result = $this->grade_item->set_locked($lockedstate, $cascade, true);
1204
1205 if ($cascade) {
1206 //process all children - items and categories
f3ac8eb4 1207 if ($children = grade_item::fetch_all(array('categoryid'=>$this->id))) {
fb0e3570 1208 foreach($children as $child) {
1209 $child->set_locked($lockedstate, true, false);
1210 if (empty($lockedstate) and $refresh) {
1211 //refresh when unlocking
1212 $child->refresh_grades();
1213 }
2b0f65e2 1214 }
7a7a53d3 1215 }
f3ac8eb4 1216 if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
fb0e3570 1217 foreach($children as $child) {
1218 $child->set_locked($lockedstate, true, true);
1219 }
7a7a53d3 1220 }
1221 }
2b0f65e2 1222
b121b544 1223 return $result;
5fad5061 1224 }
4a490db0 1225
5fad5061 1226 /**
4a490db0 1227 * Returns the hidden state/date of the associated grade_item. This method is also available in
22e23c78 1228 * grade_item.
1229 * @return boolean
5fad5061 1230 */
da3801e8 1231 public function is_hidden() {
5fad5061 1232 $this->load_grade_item();
22e23c78 1233 return $this->grade_item->is_hidden();
5fad5061 1234 }
1235
597f50e6 1236 /**
1237 * Check grade hidden status. Uses data from both grade item and grade.
1238 * @return boolean true if hiddenuntil, false if not
1239 */
da3801e8 1240 public function is_hiddenuntil() {
597f50e6 1241 $this->load_grade_item();
1242 return $this->grade_item->is_hiddenuntil();
1243 }
1244
5fad5061 1245 /**
4a490db0 1246 * Sets the grade_item's hidden variable and updates the grade_item.
5fad5061 1247 * Method named after grade_item::set_hidden().
1248 * @param int $hidden 0, 1 or a timestamp int(10) after which date the item will be hidden.
f60c61b1 1249 * @param boolean $cascade apply to child objects too
5fad5061 1250 * @return void
1251 */
da3801e8 1252 public function set_hidden($hidden, $cascade=false) {
5fad5061 1253 $this->load_grade_item();
22e23c78 1254 $this->grade_item->set_hidden($hidden);
f60c61b1 1255 if ($cascade) {
f3ac8eb4 1256 if ($children = grade_item::fetch_all(array('categoryid'=>$this->id))) {
f60c61b1 1257 foreach($children as $child) {
1258 $child->set_hidden($hidden, $cascade);
1259 }
f13002d5 1260 }
f3ac8eb4 1261 if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
f60c61b1 1262 foreach($children as $child) {
1263 $child->set_hidden($hidden, $cascade);
1264 }
f13002d5 1265 }
1266 }
5fad5061 1267 }
89a5f827 1268
190af29f 1269 /**
1270 * Applies default settings on this category
1271 * @return bool true if anything changed
1272 */
da3801e8 1273 public function apply_default_settings() {
190af29f 1274 global $CFG;
1275
1276 foreach ($this->forceable as $property) {
1277 if (isset($CFG->{"grade_$property"})) {
1278 if ($CFG->{"grade_$property"} == -1) {
1279 continue; //temporary bc before version bump
1280 }
1281 $this->$property = $CFG->{"grade_$property"};
1282 }
1283 }
1284 }
1285
89a5f827 1286 /**
1287 * Applies forced settings on this category
1288 * @return bool true if anything changed
1289 */
da3801e8 1290 public function apply_forced_settings() {
89a5f827 1291 global $CFG;
1292
1293 $updated = false;
1294 foreach ($this->forceable as $property) {
190af29f 1295 if (isset($CFG->{"grade_$property"}) and isset($CFG->{"grade_{$property}_flag"}) and ((int)$CFG->{"grade_{$property}_flag"} & 1)) {
1296 if ($CFG->{"grade_$property"} == -1) {
1297 continue; //temporary bc before version bump
1298 }
89a5f827 1299 $this->$property = $CFG->{"grade_$property"};
1300 $updated = true;
1301 }
1302 }
1303
1304 return $updated;
1305 }
1306
1307 /**
1308 * Notification of change in forced category settings.
1309 * @static
1310 */
da3801e8 1311 public static function updated_forced_settings() {
5b0af8c5 1312 global $CFG, $DB;
1313 $params = array(1, 'course', 'category');
1314 $sql = "UPDATE {grade_items} SET needsupdate=? WHERE itemtype=? or itemtype=?";
1315 $DB->execute($sql, $params);
89a5f827 1316 }
4a490db0 1317}
8a31e65c 1318?>