3 ///////////////////////////////////////////////////////////////////////////
5 // NOTICE OF COPYRIGHT //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.com //
10 // Copyright (C) 2001-2003 Martin Dougiamas http://dougiamas.com //
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. //
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: //
22 // http://www.gnu.org/copyleft/gpl.html //
24 ///////////////////////////////////////////////////////////////////////////
26 require_once('grade_object.php');
28 class grade_category
extends grade_object
{
33 var $table = 'grade_categories';
36 * Array of required table fields, must start with 'id'.
37 * @var array $required_fields
39 var $required_fields = array('id', 'courseid', 'parent', 'depth', 'path', 'fullname', 'aggregation',
40 'keephigh', 'droplow', 'aggregateonlygraded', 'aggregateoutcomes',
41 'aggregatesubcats', 'timecreated', 'timemodified');
44 * The course this category belongs to.
50 * The category this category belongs to (optional).
56 * The grade_category object referenced by $this->parent (PK).
57 * @var object $parent_category
62 * The number of parents this category has.
68 * Shows the hierarchical path for this category as /1/2/3/ (like course_categories), the last number being
69 * this category's autoincrement ID number.
75 * The name of this category.
76 * @var string $fullname
81 * A constant pointing to one of the predefined aggregation strategies (none, mean, median, sum etc) .
82 * @var int $aggregation
84 var $aggregation = GRADE_AGGREGATE_MEAN
;
87 * Keep only the X highest items.
93 * Drop the X lowest items.
99 * Aggregate only graded items
100 * @var int $aggregateonlygraded
102 var $aggregateonlygraded = 0;
105 * Aggregate outcomes together with normal items
106 * @var int $aggregateoutcomes
108 var $aggregateoutcomes = 0;
111 * Ignore subcategories when aggregating
112 * @var int $aggregatesubcats
114 var $aggregatesubcats = 0;
117 * Array of grade_items or grade_categories nested exactly 1 level below this category
118 * @var array $children
123 * A hierarchical array of all children below this category. This is stored separately from
124 * $children because it is more memory-intensive and may not be used as often.
125 * @var array $all_children
130 * An associated grade_item object, with itemtype=category, used to calculate and cache a set of grade values
132 * @var object $grade_item
137 * Temporary sortorder for speedup of children resorting
142 * Builds this category's path string based on its parents (if any) and its own id number.
143 * This is typically done just before inserting this object in the DB for the first time,
144 * or when a new parent is added or changed. It is a recursive function: once the calling
145 * object no longer has a parent, the path is complete.
148 * @param object $grade_category
149 * @return int The depth of this category (2 means there is one parent)
151 function build_path($grade_category) {
152 if (empty($grade_category->parent
)) {
153 return '/'.$grade_category->id
.'/';
155 $parent = get_record('grade_categories', 'id', $grade_category->parent
);
156 return grade_category
::build_path($parent).$grade_category->id
.'/';
161 * Finds and returns a grade_category instance based on params.
164 * @param array $params associative arrays varname=>value
165 * @return object grade_category instance or false if none found.
167 function fetch($params) {
168 return grade_object
::fetch_helper('grade_categories', 'grade_category', $params);
172 * Finds and returns all grade_category instances based on params.
175 * @param array $params associative arrays varname=>value
176 * @return array array of grade_category insatnces or false if none found.
178 function fetch_all($params) {
179 return grade_object
::fetch_all_helper('grade_categories', 'grade_category', $params);
183 * In addition to update() as defined in grade_object, call force_regrading of parent categories, if applicable.
184 * @param string $source from where was the object updated (mod/forum, manual, etc.)
185 * @return boolean success
187 function update($source=null) {
188 // load the grade item or create a new one
189 $this->load_grade_item();
191 // force recalculation of path;
192 if (empty($this->path
)) {
193 $this->path
= grade_category
::build_path($this);
194 $this->depth
= substr_count($this->path
, '/') - 1;
198 // Recalculate grades if needed
199 if ($this->qualifies_for_regrading()) {
200 $this->force_regrading();
203 return parent
::update($source);
207 * If parent::delete() is successful, send force_regrading message to parent category.
208 * @param string $source from where was the object deleted (mod/forum, manual, etc.)
209 * @return boolean success
211 function delete($source=null) {
212 $grade_item = $this->load_grade_item();
214 if ($this->is_course_category()) {
215 if ($categories = grade_category
::fetch_all(array('courseid'=>$this->courseid
))) {
216 foreach ($categories as $category) {
217 if ($category->id
== $this->id
) {
218 continue; // do not delete course category yet
220 $category->delete($source);
224 if ($items = grade_item
::fetch_all(array('courseid'=>$this->courseid
))) {
225 foreach ($items as $item) {
226 if ($item->id
== $grade_item->id
) {
227 continue; // do not delete course item yet
229 $item->delete($source);
234 $this->force_regrading();
236 $parent = $this->load_parent_category();
238 // Update children's categoryid/parent field first
239 if ($children = grade_item
::fetch_all(array('categoryid'=>$this->id
))) {
240 foreach ($children as $child) {
241 $child->set_parent($parent->id
);
244 if ($children = grade_category
::fetch_all(array('parent'=>$this->id
))) {
245 foreach ($children as $child) {
246 $child->set_parent($parent->id
);
251 // first delete the attached grade item and grades
252 $grade_item->delete($source);
254 // delete category itself
255 return parent
::delete($source);
259 * In addition to the normal insert() defined in grade_object, this method sets the depth
260 * and path for this object, and update the record accordingly. The reason why this must
261 * be done here instead of in the constructor, is that they both need to know the record's
262 * id number, which only gets created at insertion time.
263 * This method also creates an associated grade_item if this wasn't done during construction.
264 * @param string $source from where was the object inserted (mod/forum, manual, etc.)
265 * @return int PK ID if successful, false otherwise
267 function insert($source=null) {
269 if (empty($this->courseid
)) {
270 error('Can not insert grade category without course id!');
273 if (empty($this->parent
)) {
274 $course_category = grade_category
::fetch_course_category($this->courseid
);
275 $this->parent
= $course_category->id
;
280 if (!parent
::insert($source)) {
281 debugging("Could not insert this category: " . print_r($this, true));
285 $this->force_regrading();
287 // build path and depth
288 $this->update($source);
294 * Internal function - used only from fetch_course_category()
295 * Normal insert() can not be used for course category
296 * @param int $courseid
297 * @return bool success
299 function insert_course_category($courseid) {
300 $this->courseid
= $courseid;
301 $this->fullname
= get_string('coursegradecategory', 'grades');
303 $this->parent
= null;
304 $this->aggregate
= GRADE_AGGREGATE_MEAN
;
306 if (!parent
::insert('system')) {
307 debugging("Could not insert this category: " . print_r($this, true));
311 // build path and depth
312 $this->update('system');
318 * Compares the values held by this object with those of the matching record in DB, and returns
319 * whether or not these differences are sufficient to justify an update of all parent objects.
320 * This assumes that this object has an id number and a matching record in DB. If not, it will return false.
323 function qualifies_for_regrading() {
324 if (empty($this->id
)) {
325 debugging("Can not regrade non existing category");
329 $db_item = grade_category
::fetch(array('id'=>$this->id
));
331 $aggregationdiff = $db_item->aggregation
!= $this->aggregation
;
332 $keephighdiff = $db_item->keephigh
!= $this->keephigh
;
333 $droplowdiff = $db_item->droplow
!= $this->droplow
;
334 $aggonlygrddiff = $db_item->aggregateonlygraded
!= $this->aggregateonlygraded
;
335 $aggoutcomesdiff = $db_item->aggregateoutcomes
!= $this->aggregateoutcomes
;
336 $aggsubcatsdiff = $db_item->aggregatesubcats
!= $this->aggregatesubcats
;
338 return ($aggregationdiff ||
$keephighdiff ||
$droplowdiff ||
$aggonlygrddiff ||
$aggoutcomesdiff ||
$aggsubcatsdiff);
342 * Marks the category and course item as needing update - categories are always regraded.
345 function force_regrading() {
346 $grade_item = $this->load_grade_item();
347 $grade_item->force_regrading();
351 * Generates and saves raw_grades in associated category grade item.
352 * These immediate children must alrady have their own final grades.
353 * The category's aggregation method is used to generate raw grades.
355 * Please note that category grade is either calculated or aggregated - not both at the same time.
357 * This method must be used ONLY from grade_item::regrade_final_grades(),
358 * because the calculation must be done in correct order!
361 * 1. Get final grades from immediate children
362 * 3. Aggregate these grades
363 * 4. Save them in raw grades of associated category grade item
365 function generate_grades($userid=null) {
368 $this->load_grade_item();
370 if ($this->grade_item
->is_locked()) {
371 return true; // no need to recalculate locked items
374 $this->grade_item
->load_scale();
376 // find grade items of immediate children (category or grade items)
377 $depends_on = $this->grade_item
->depends_on();
379 if (empty($depends_on)) {
382 $gis = implode(',', $depends_on);
384 FROM {$CFG->prefix}grade_items
386 $items = get_records_sql($sql);
390 $usersql = "AND g.userid=$userid";
395 $grade_inst = new grade_grade();
396 $fields = 'g.'.implode(',g.', $grade_inst->required_fields
);
398 // where to look for final grades - include grade of this item too, we will store the results there
399 $gis = implode(',', array_merge($depends_on, array($this->grade_item
->id
)));
400 $sql = "SELECT $fields
401 FROM {$CFG->prefix}grade_grades g, {$CFG->prefix}grade_items gi
402 WHERE gi.id = g.itemid AND gi.id IN ($gis) $usersql
405 // group the results by userid and aggregate the grades for this user
406 if ($rs = get_recordset_sql($sql)) {
407 if ($rs->RecordCount() > 0) {
409 $grade_values = array();
412 while ($used = rs_fetch_next_record($rs)) {
413 if ($used->userid
!= $prevuser) {
414 $this->aggregate_grades($prevuser, $items, $grade_values, $oldgrade, $excluded);
415 $prevuser = $used->userid
;
416 $grade_values = array();
420 $grade_values[$used->itemid
] = $used->finalgrade
;
421 if ($used->excluded
) {
422 $excluded[] = $used->itemid
;
424 if ($this->grade_item
->id
== $used->itemid
) {
428 $this->aggregate_grades($prevuser, $items, $grade_values, $oldgrade, $excluded);//the last one
437 * internal function for category grades aggregation
439 function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded) {
441 if (empty($userid)) {
447 $grade = new grade_grade($oldgrade, false);
448 $grade->grade_item
=& $this->grade_item
;
451 // insert final grade - it will be needed later anyway
452 $grade = new grade_grade(array('itemid'=>$this->grade_item
->id
, 'userid'=>$userid), false);
453 $grade->insert('system');
454 $grade->grade_item
=& $this->grade_item
;
456 $oldgrade = new object();
457 $oldgrade->finalgrade
= $grade->finalgrade
;
458 $oldgrade->rawgrade
= $grade->rawgrade
;
459 $oldgrade->rawgrademin
= $grade->rawgrademin
;
460 $oldgrade->rawgrademax
= $grade->rawgrademax
;
461 $oldgrade->rawscaleid
= $grade->rawscaleid
;
464 // no need to recalculate locked or overridden grades
465 if ($grade->is_locked() or $grade->is_overridden()) {
469 // can not use own final category grade in calculation
470 unset($grade_values[$this->grade_item
->id
]);
472 // if no grades calculation possible or grading not allowed clear both final and raw
473 if (empty($grade_values) or empty($items) or ($this->grade_item
->gradetype
!= GRADE_TYPE_VALUE
and $this->grade_item
->gradetype
!= GRADE_TYPE_SCALE
)) {
474 $grade->finalgrade
= null;
475 $grade->rawgrade
= null;
476 if ($grade->finalgrade
!== $oldgrade->finalgrade
or $grade->rawgrade
!== $oldgrade->rawgrade
) {
477 $grade->update('system');
482 /// normalize the grades first - all will have value 0...1
483 // ungraded items are not used in aggregation
484 foreach ($grade_values as $itemid=>$v) {
486 // null means no grade
487 unset($grade_values[$itemid]);
489 } else if (in_array($itemid, $excluded)) {
490 unset($grade_values[$itemid]);
494 $grade_values[$itemid] = grade_grade
::standardise_score($v, $items[$itemid]->grademin
, $items[$itemid]->grademax
, 0, 1);
497 // If global aggregateonlygraded is set, override category value
498 if ($CFG->grade_aggregateonlygraded
!= -1) {
499 $this->aggregateonlygraded
= $CFG->grade_aggregateonlygraded
;
502 // use min grade if grade missing for these types
503 if (!$this->aggregateonlygraded
) {
504 foreach($items as $itemid=>$value) {
505 if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
506 $grade_values[$itemid] = 0;
512 $this->apply_limit_rules($grade_values);
513 asort($grade_values, SORT_NUMERIC
);
515 // let's see we have still enough grades to do any statistics
516 if (count($grade_values) == 0) {
517 // not enough attempts yet
518 $grade->finalgrade
= null;
519 $grade->rawgrade
= null;
520 if ($grade->finalgrade
!== $oldgrade->finalgrade
or $grade->rawgrade
!== $oldgrade->rawgrade
) {
521 $grade->update('system');
526 /// start the aggregation
527 switch ($this->aggregation
) {
528 case GRADE_AGGREGATE_MEDIAN
: // Middle point value in the set: ignores frequencies
529 $num = count($grade_values);
530 $grades = array_values($grade_values);
532 $agg_grade = ($grades[intval($num/2)-1] +
$grades[intval($num/2)]) / 2;
534 $agg_grade = $grades[intval(($num/2)-0.5)];
538 case GRADE_AGGREGATE_MIN
:
539 $agg_grade = reset($grade_values);
542 case GRADE_AGGREGATE_MAX
:
543 $agg_grade = array_pop($grade_values);
546 case GRADE_AGGREGATE_MODE
: // the most common value, average used if multimode
547 $freq = array_count_values($grade_values);
548 arsort($freq); // sort by frequency keeping keys
549 $top = reset($freq); // highest frequency count
550 $modes = array_keys($freq, $top); // search for all modes (have the same highest count)
551 rsort($modes, SORT_NUMERIC
); // get highes mode
552 $agg_grade = reset($modes);
555 case GRADE_AGGREGATE_WEIGHTED_MEAN
: // Weighted average of all existing final grades
558 foreach($grade_values as $itemid=>$grade_value) {
559 if ($items[$itemid]->aggregationcoef
<= 0) {
562 $weightsum +
= $items[$itemid]->aggregationcoef
;
563 $sum +
= $items[$itemid]->aggregationcoef
* $grade_value;
565 if ($weightsum == 0) {
568 $agg_grade = $sum / $weightsum;
572 case GRADE_AGGREGATE_EXTRACREDIT_MEAN
: // special average
575 foreach($grade_values as $itemid=>$grade_value) {
576 if ($items[$itemid]->aggregationcoef
== 0) {
578 $sum +
= $grade_value;
579 } else if ($items[$itemid]->aggregationcoef
> 0) {
580 $sum +
= $items[$itemid]->aggregationcoef
* $grade_value;
584 $agg_grade = $sum; // only extra credits or wrong coefs
586 $agg_grade = $sum / $num;
590 case GRADE_AGGREGATE_MEAN
: // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
592 $num = count($grade_values);
593 $sum = array_sum($grade_values);
594 $agg_grade = $sum / $num;
598 /// prepare update of new raw grade
599 $grade->rawgrademin
= $this->grade_item
->grademin
;
600 $grade->rawgrademax
= $this->grade_item
->grademax
;
601 $grade->rawscaleid
= $this->grade_item
->scaleid
;
602 $grade->rawgrade
= null; // categories do not use raw grades
604 // recalculate the rawgrade back to requested range
605 $finalgrade = grade_grade
::standardise_score($agg_grade, 0, 1, $this->grade_item
->grademin
, $this->grade_item
->grademax
);
607 if (!is_null($finalgrade)) {
608 $grade->finalgrade
= bounded_number($this->grade_item
->grademin
, $finalgrade, $this->grade_item
->grademax
);
610 $grade->finalgrade
= $finalgrade;
613 // update in db if changed
614 if ( $grade->finalgrade
!== $oldgrade->finalgrade
615 or $grade->rawgrade
!== $oldgrade->rawgrade
616 or $grade->rawgrademin
!== $oldgrade->rawgrademin
617 or $grade->rawgrademax
!== $oldgrade->rawgrademax
618 or $grade->rawscaleid
!== $oldgrade->rawscaleid
) {
620 $grade->update('system');
627 * Given an array of grade values (numerical indices), applies droplow or keephigh
628 * rules to limit the final array.
629 * @param array $grade_values
630 * @return array Limited grades.
632 function apply_limit_rules(&$grade_values) {
635 // If global keephigh and/or droplow are set, override category variable
636 if ($CFG->grade_keephigh
!= -1) {
637 $this->keephigh
= $CFG->grade_keephigh
;
640 if ($CFG->grade_droplow
!= -1) {
641 $this->droplow
= $CFG->grade_droplow
;
644 arsort($grade_values, SORT_NUMERIC
);
645 if (!empty($this->droplow
)) {
646 for ($i = 0; $i < $this->droplow
; $i++
) {
647 array_pop($grade_values);
649 } elseif (!empty($this->keephigh
)) {
650 while (count($grade_values) > $this->keephigh
) {
651 array_pop($grade_values);
658 * Returns true if category uses special aggregation coeficient
659 * @return boolean true if coeficient used
661 function is_aggregationcoef_used() {
662 return ($this->aggregation
== GRADE_AGGREGATE_WEIGHTED_MEAN
663 or $this->aggregation
== GRADE_AGGREGATE_EXTRACREDIT_MEAN
);
668 * Returns tree with all grade_items and categories as elements
670 * @param int $courseid
671 * @param boolean $include_category_items as category children
674 function fetch_course_tree($courseid, $include_category_items=false) {
675 $course_category = grade_category
::fetch_course_category($courseid);
676 $category_array = array('object'=>$course_category, 'type'=>'category', 'depth'=>1,
677 'children'=>$course_category->get_children($include_category_items));
679 $course_category->set_sortorder($sortorder);
680 $course_category->sortorder
= $sortorder;
681 return grade_category
::_fetch_course_tree_recursion($category_array, $sortorder);
684 function _fetch_course_tree_recursion($category_array, &$sortorder) {
685 // update the sortorder in db if needed
686 if ($category_array['object']->sortorder
!= $sortorder) {
687 $category_array['object']->set_sortorder($sortorder);
690 // store the grade_item or grade_category instance with extra info
691 $result = array('object'=>$category_array['object'], 'type'=>$category_array['type'], 'depth'=>$category_array['depth']);
693 // reuse final grades if there
694 if (array_key_exists('finalgrades', $category_array)) {
695 $result['finalgrades'] = $category_array['finalgrades'];
698 // recursively resort children
699 if (!empty($category_array['children'])) {
700 $result['children'] = array();
701 //process the category item first
703 foreach($category_array['children'] as $oldorder=>$child_array) {
704 if ($child_array['type'] == 'courseitem' or $child_array['type'] == 'categoryitem') {
705 $result['children'][$sortorder] = grade_category
::_fetch_course_tree_recursion($child_array, $sortorder);
708 foreach($category_array['children'] as $oldorder=>$child_array) {
709 if ($child_array['type'] != 'courseitem' and $child_array['type'] != 'categoryitem') {
710 $result['children'][++
$sortorder] = grade_category
::_fetch_course_tree_recursion($child_array, $sortorder);
719 * Fetches and returns all the children categories and/or grade_items belonging to this category.
720 * By default only returns the immediate children (depth=1), but deeper levels can be requested,
721 * as well as all levels (0). The elements are indexed by sort order.
722 * @return array Array of child objects (grade_category and grade_item).
724 function get_children($include_category_items=false) {
726 // This function must be as fast as possible ;-)
727 // fetch all course grade items and categories into memory - we do not expect hundreds of these in course
728 // we have to limit the number of queries though, because it will be used often in grade reports
730 $cats = get_records('grade_categories', 'courseid', $this->courseid
);
731 $items = get_records('grade_items', 'courseid', $this->courseid
);
733 // init children array first
734 foreach ($cats as $catid=>$cat) {
735 $cats[$catid]->children
= array();
738 //first attach items to cats and add category sortorder
739 foreach ($items as $item) {
740 if ($item->itemtype
== 'course' or $item->itemtype
== 'category') {
741 $cats[$item->iteminstance
]->sortorder
= $item->sortorder
;
743 if (!$include_category_items) {
746 $categoryid = $item->iteminstance
;
748 $categoryid = $item->categoryid
;
751 // prevent problems with duplicate sortorders in db
752 $sortorder = $item->sortorder
;
753 while(array_key_exists($sortorder, $cats[$categoryid]->children
)) {
754 //debugging("$sortorder exists in item loop");
758 $cats[$categoryid]->children
[$sortorder] = $item;
762 // now find the requested category and connect categories as children
764 foreach ($cats as $catid=>$cat) {
765 if (!empty($cat->parent
)) {
766 // prevent problems with duplicate sortorders in db
767 $sortorder = $cat->sortorder
;
768 while(array_key_exists($sortorder, $cats[$cat->parent
]->children
)) {
769 //debugging("$sortorder exists in cat loop");
773 $cats[$cat->parent
]->children
[$sortorder] = $cat;
776 if ($catid == $this->id
) {
777 $category = &$cats[$catid];
781 unset($items); // not needed
782 unset($cats); // not needed
784 $children_array = grade_category
::_get_children_recursion($category);
786 ksort($children_array);
788 return $children_array;
792 function _get_children_recursion($category) {
794 $children_array = array();
795 foreach($category->children
as $sortorder=>$child) {
796 if (array_key_exists('itemtype', $child)) {
797 $grade_item = new grade_item($child, false);
798 if (in_array($grade_item->itemtype
, array('course', 'category'))) {
799 $type = $grade_item->itemtype
.'item';
800 $depth = $category->depth
;
803 $depth = $category->depth
; // we use this to set the same colour
805 $children_array[$sortorder] = array('object'=>$grade_item, 'type'=>$type, 'depth'=>$depth);
808 $children = grade_category
::_get_children_recursion($child);
809 $grade_category = new grade_category($child, false);
810 if (empty($children)) {
813 $children_array[$sortorder] = array('object'=>$grade_category, 'type'=>'category', 'depth'=>$grade_category->depth
, 'children'=>$children);
818 ksort($children_array);
820 return $children_array;
824 * Uses get_grade_item to load or create a grade_item, then saves it as $this->grade_item.
825 * @return object Grade_item
827 function load_grade_item() {
828 if (empty($this->grade_item
)) {
829 $this->grade_item
= $this->get_grade_item();
831 return $this->grade_item
;
835 * Retrieves from DB and instantiates the associated grade_item object.
836 * If no grade_item exists yet, create one.
837 * @return object Grade_item
839 function get_grade_item() {
840 if (empty($this->id
)) {
841 debugging("Attempt to obtain a grade_category's associated grade_item without the category's ID being set.");
845 if (empty($this->parent
)) {
846 $params = array('courseid'=>$this->courseid
, 'itemtype'=>'course', 'iteminstance'=>$this->id
);
849 $params = array('courseid'=>$this->courseid
, 'itemtype'=>'category', 'iteminstance'=>$this->id
);
852 if (!$grade_items = grade_item
::fetch_all($params)) {
854 $grade_item = new grade_item($params, false);
855 $grade_item->gradetype
= GRADE_TYPE_VALUE
;
856 $grade_item->insert('system');
858 } else if (count($grade_items) == 1){
859 // found existing one
860 $grade_item = reset($grade_items);
863 debugging("Found more than one grade_item attached to category id:".$this->id
);
865 $grade_item = reset($grade_items);
872 * Uses $this->parent to instantiate $this->parent_category based on the
873 * referenced record in the DB.
874 * @return object Parent_category
876 function load_parent_category() {
877 if (empty($this->parent_category
) && !empty($this->parent
)) {
878 $this->parent_category
= $this->get_parent_category();
880 return $this->parent_category
;
884 * Uses $this->parent to instantiate and return a grade_category object.
885 * @return object Parent_category
887 function get_parent_category() {
888 if (!empty($this->parent
)) {
889 $parent_category = new grade_category(array('id' => $this->parent
));
890 return $parent_category;
897 * Returns the most descriptive field for this object. This is a standard method used
898 * when we do not know the exact type of an object.
899 * @return string name
901 function get_name() {
902 if (empty($this->parent
)) {
903 $course = get_record('course', 'id', $this->courseid
);
904 return format_string($course->fullname
);
906 return $this->fullname
;
911 * Sets this category's parent id. A generic method shared by objects that have a parent id of some kind.
912 * @param int parentid
913 * @return boolean success
915 function set_parent($parentid, $source=null) {
916 if ($this->parent
== $parentid) {
920 if ($parentid == $this->id
) {
921 error('Can not assign self as parent!');
924 if (empty($this->parent
) and $this->is_course_category()) {
925 error('Course category can not have parent!');
928 // find parent and check course id
929 if (!$parent_category = grade_category
::fetch(array('id'=>$parentid, 'courseid'=>$this->courseid
))) {
933 $this->force_regrading();
935 // set new parent category
936 $this->parent
= $parent_category->id
;
937 $this->parent_category
=& $parent_category;
938 $this->path
= null; // remove old path and depth - will be recalculated in update()
939 $this->depth
= null; // remove old path and depth - will be recalculated in update()
940 $this->update($source);
942 return $this->update($source);
946 * Returns the final values for this grade category.
947 * @param int $userid Optional: to retrieve a single final grade
948 * @return mixed An array of all final_grades (stdClass objects) for this grade_item, or a single final_grade.
950 function get_final($userid=NULL) {
951 $this->load_grade_item();
952 return $this->grade_item
->get_final($userid);
956 * Returns the sortorder of the associated grade_item. This method is also available in
957 * grade_item, for cases where the object type is not known.
958 * @return int Sort order
960 function get_sortorder() {
961 $this->load_grade_item();
962 return $this->grade_item
->get_sortorder();
966 * Returns the idnumber of the associated grade_item. This method is also available in
967 * grade_item, for cases where the object type is not known.
968 * @return string idnumber
970 function get_idnumber() {
971 $this->load_grade_item();
972 return $this->grade_item
->get_idnumber();
976 * Sets sortorder variable for this category.
977 * This method is also available in grade_item, for cases where the object type is not know.
978 * @param int $sortorder
981 function set_sortorder($sortorder) {
982 $this->load_grade_item();
983 $this->grade_item
->set_sortorder($sortorder);
987 * Move this category after the given sortorder - does not change the parent
988 * @param int $sortorder to place after
990 function move_after_sortorder($sortorder) {
991 $this->load_grade_item();
992 $this->grade_item
->move_after_sortorder($sortorder);
996 * Return true if this is the top most category that represents the total course grade.
999 function is_course_category() {
1000 $this->load_grade_item();
1001 return $this->grade_item
->is_course_item();
1005 * Return the top most course category.
1007 * @return object grade_category instance for course grade
1009 function fetch_course_category($courseid) {
1011 // course category has no parent
1012 if ($course_category = grade_category
::fetch(array('courseid'=>$courseid, 'parent'=>null))) {
1013 return $course_category;
1017 $course_category = new grade_category();
1018 $course_category->insert_course_category($courseid);
1020 return $course_category;
1024 * Is grading object editable?
1027 function is_editable() {
1032 * Returns the locked state/date of the associated grade_item. This method is also available in
1033 * grade_item, for cases where the object type is not known.
1036 function is_locked() {
1037 $this->load_grade_item();
1038 return $this->grade_item
->is_locked();
1042 * Sets the grade_item's locked variable and updates the grade_item.
1043 * Method named after grade_item::set_locked().
1044 * @param int $locked 0, 1 or a timestamp int(10) after which date the item will be locked.
1045 * @param boolean $cascade lock/unlock child objects too
1046 * @param boolean $refresh refresh grades when unlocking
1047 * @return boolean success if category locked (not all children mayb be locked though)
1049 function set_locked($lockedstate, $cascade=false, $refresh=true) {
1050 $this->load_grade_item();
1052 $result = $this->grade_item
->set_locked($lockedstate, $cascade, true);
1055 //process all children - items and categories
1056 if ($children = grade_item
::fetch_all(array('categoryid'=>$this->id
))) {
1057 foreach($children as $child) {
1058 $child->set_locked($lockedstate, true, false);
1059 if (empty($lockedstate) and $refresh) {
1060 //refresh when unlocking
1061 $child->refresh_grades();
1065 if ($children = grade_category
::fetch_all(array('parent'=>$this->id
))) {
1066 foreach($children as $child) {
1067 $child->set_locked($lockedstate, true, true);
1076 * Returns the hidden state/date of the associated grade_item. This method is also available in
1080 function is_hidden() {
1081 $this->load_grade_item();
1082 return $this->grade_item
->is_hidden();
1086 * Sets the grade_item's hidden variable and updates the grade_item.
1087 * Method named after grade_item::set_hidden().
1088 * @param int $hidden 0, 1 or a timestamp int(10) after which date the item will be hidden.
1089 * @param boolean $cascade apply to child objects too
1092 function set_hidden($hidden, $cascade=false) {
1093 $this->load_grade_item();
1094 $this->grade_item
->set_hidden($hidden);
1096 if ($children = grade_item
::fetch_all(array('categoryid'=>$this->id
))) {
1097 foreach($children as $child) {
1098 $child->set_hidden($hidden, $cascade);
1101 if ($children = grade_category
::fetch_all(array('parent'=>$this->id
))) {
1102 foreach($children as $child) {
1103 $child->set_hidden($hidden, $cascade);