Changed Ojective Rollup Process
[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// //
10// Copyright (C) 2001-2003 Martin Dougiamas http://dougiamas.com //
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 */
33 var $table = 'grade_categories';
34
35 /**
36 * Array of class variables that are not part of the DB table fields
37 * @var array $nonfields
38 */
7c8a963f 39 var $nonfields = array('table', 'nonfields', 'children', 'all_children');
8a31e65c 40
41 /**
42 * The course this category belongs to.
43 * @var int $courseid
44 */
45 var $courseid;
46
47 /**
48 * The category this category belongs to (optional).
e5c674f1 49 * @var int $parent
8a31e65c 50 */
e5c674f1 51 var $parent;
27f95e9b 52
e5c674f1 53 /**
54 * The number of parents this category has.
55 * @var int $depth
56 */
57 var $depth = 0;
58
59 /**
60 * Shows the hierarchical path for this category as /1/2/3 (like course_categories), the last number being
61 * this category's autoincrement ID number.
62 * @var string $path
63 */
64 var $path;
65
8a31e65c 66 /**
67 * The name of this category.
68 * @var string $fullname
69 */
70 var $fullname;
71
72 /**
73 * A constant pointing to one of the predefined aggregation strategies (none, mean, median, sum etc) .
74 * @var int $aggregation
75 */
76 var $aggregation;
77
78 /**
79 * Keep only the X highest items.
80 * @var int $keephigh
81 */
82 var $keephigh;
83
84 /**
85 * Drop the X lowest items.
86 * @var int $droplow
87 */
88 var $droplow;
89
90 /**
91 * Date until which to hide this category. If null, 0 or false, category is not hidden.
92 * @var int $hidden
93 */
94 var $hidden;
95
96 /**
97 * Array of grade_items or grade_categories nested exactly 1 level below this category
98 * @var array $children
99 */
100 var $children;
8a31e65c 101
7c8a963f 102 /**
103 * A hierarchical array of all children below this category. This is stored separately from
104 * $children because it is more memory-intensive and may not be used as often.
105 * @var array $all_children
106 */
107 var $all_children;
108
f151b073 109 /**
110 * An associated grade_item object, with itemtype=category, used to calculate and cache a set of grade values
111 * for this category.
112 * @var object $grade_item
113 */
114 var $grade_item;
115
e5c674f1 116 /**
117 * Constructor. Extends the basic functionality defined in grade_object.
118 * @param array $params Can also be a standard object.
f151b073 119 * @param boolean $fetch Whether or not to fetch the corresponding row from the DB.
120 * @param object $grade_item The associated grade_item object can be passed during construction.
e5c674f1 121 */
f151b073 122 function grade_category($params=NULL, $fetch=true, $grade_item=NULL) {
e5c674f1 123 $this->grade_object($params, $fetch);
f151b073 124 if (!empty($grade_item) && $grade_item->itemtype == 'category') {
125 $this->grade_item = $grade_item;
126 if (empty($this->grade_item->iteminstance)) {
127 $this->grade_item->iteminstance = $this->id;
128 $this->grade_item->update();
129 }
130 }
e5c674f1 131 }
132
133
134 /**
135 * Builds this category's path string based on its parents (if any) and its own id number.
136 * This is typically done just before inserting this object in the DB for the first time,
ce385eb4 137 * or when a new parent is added or changed. It is a recursive function: once the calling
138 * object no longer has a parent, the path is complete.
139 *
140 * @static
141 * @param object $grade_category
142 * @return int The depth of this category (2 means there is one parent)
e5c674f1 143 */
ce385eb4 144 function build_path($grade_category) {
145 if (empty($grade_category->parent)) {
146 return "/$grade_category->id";
147 } else {
148 $parent = get_record('grade_categories', 'id', $grade_category->parent);
149 return grade_category::build_path($parent) . "/$grade_category->id";
150 }
e5c674f1 151 }
152
8a31e65c 153
8a31e65c 154 /**
155 * Finds and returns a grade_category object based on 1-3 field values.
156 *
8a31e65c 157 * @param string $field1
158 * @param string $value1
159 * @param string $field2
160 * @param string $value2
161 * @param string $field3
162 * @param string $value3
163 * @param string $fields
164 * @return object grade_category object or false if none found.
165 */
27f95e9b 166 function fetch($field1, $value1, $field2='', $value2='', $field3='', $value3='', $fields="*") {
8a31e65c 167 if ($grade_category = get_record('grade_categories', $field1, $value1, $field2, $value2, $field3, $value3, $fields)) {
7c8a963f 168 if (isset($this) && get_class($this) == 'grade_category') {
8a31e65c 169 foreach ($grade_category as $param => $value) {
170 $this->$param = $value;
171 }
172 return $this;
7c8a963f 173 } else {
174 $grade_category = new grade_category($grade_category);
175 return $grade_category;
8a31e65c 176 }
177 } else {
178 return false;
179 }
ce385eb4 180 }
181
182 /**
183 * In addition to the normal insert() defined in grade_object, this method sets the depth
184 * and path for this object, and update the record accordingly. The reason why this must
185 * be done here instead of in the constructor, is that they both need to know the record's
27f95e9b 186 * id number, which only gets created at insertion time.
f151b073 187 * This method also creates an associated grade_item if this wasn't done during construction.
ce385eb4 188 */
189 function insert() {
190 $result = parent::insert();
191
192 // Build path and depth variables
193 if (!empty($this->parent)) {
194 $this->path = grade_category::build_path($this);
195 $this->depth = $this->get_depth_from_path();
196 } else {
197 $this->depth = 1;
198 $this->path = "/$this->id";
199 }
200
201 $this->update();
f151b073 202
203 if (empty($this->grade_item)) {
204 $grade_item = new grade_item();
205 $grade_item->iteminstance = $this->id;
206 $grade_item->itemtype = 'category';
207 $result = $result & $grade_item->insert();
208 $this->grade_item = $grade_item;
209 }
210
ce385eb4 211 return $result;
212 }
0aa32279 213
214 /**
215 * Generates and saves raw_grades, based on this category's immediate children, then uses the
216 * associated grade_item to generate matching final grades. These immediate children must first have their own
217 * raw and final grades, which means that ultimately we must get grade_items as children. The category's aggregation
218 * method is used to generate these raw grades, which can then be used by the category's associated grade_item
219 * to apply calculations to and generate final grades.
220 */
221 function generate_grades() {
222 // Check that the children have final grades. If not, call their generate_raw_grades method (recursion)
223 if (empty($this->children)) {
224 $this->children = $this->get_children(1, 'flat');
225 }
226
227 $category_raw_grades = array();
228 $aggregated_grades = array();
229
230 foreach ($this->children as $child) {
231 if (get_class($child) == 'grade_item') {
232 $category_raw_grades[$child->id] = $child->load_final();
233 } elseif ($get_class($child) == 'grade_category') {
234 $category_raw_grades[$child->id] = $child->load_final();
235 if (empty($category_raw_grades)) {
236 $category_raw_grades[$child->id] = $child->generate_grades();
237 }
238 }
239 }
240
241 if (empty($category_raw_grades)) {
242 return null;
243 } else {
244 $aggregated_grades = $this->aggregate_grades($category_raw_grades);
245 foreach ($aggregated_grades as $raw_grade) {
246 $raw_grade->insert();
247 }
248 $this->grade_item->generate_final();
249 }
250 }
251
252 /**
253 * Given an array of arrays of grade objects (raw or final), uses this category's aggregation method to
254 * compute and return a single array of grade_raw objects with the aggregated gradevalue.
255 * @param array $raw_grade_sets
256 * @return array Raw grade objects
257 */
258 function aggregate_grades($raw_grade_sets) {
259
260 }
261
ce385eb4 262 /**
263 * Looks at a path string (e.g. /2/45/56) and returns the depth level represented by this path (in this example, 3).
264 * If no string is given, it looks at the obect's path and assigns the resulting depth to its $depth variable.
265 * @param string $path
266 * @return int Depth level
267 */
268 function get_depth_from_path($path=NULL) {
269 if (empty($path)) {
270 $path = $this->path;
271 }
272 preg_match_all('/\/([0-9]+)+?/', $path, $matches);
273 $depth = count($matches[0]);
274
275 return $depth;
276 }
7c8a963f 277
278 /**
279 * Fetches and returns all the children categories and/or grade_items belonging to this category.
280 * By default only returns the immediate children (depth=1), but deeper levels can be requested,
281 * as well as all levels (0).
282 * @param int $depth 1 for immediate children, 0 for all children, and 2+ for specific levels deeper than 1.
283 * @param string $arraytype Either 'nested' or 'flat'. A nested array represents the true hierarchy, but is more difficult to work with.
284 * @return array Array of child objects (grade_category and grade_item).
285 */
286 function get_children($depth=1, $arraytype='nested') {
27f95e9b 287 $children_array = array();
288
289 // Set up $depth for recursion
290 $newdepth = $depth;
291 if ($depth > 1) {
292 $newdepth--;
293 }
294
295 $childrentype = $this->get_childrentype();
f151b073 296
27f95e9b 297 if ($childrentype == 'grade_item') {
f151b073 298 $children = get_records('grade_items', 'categoryid', $this->id);
27f95e9b 299 // No need to proceed with recursion
300 $children_array = $this->children_to_array($children, $arraytype, 'grade_item');
301 $this->children = $this->children_to_array($children, 'flat', 'grade_item');
302 } elseif ($childrentype == 'grade_category') {
303 $children = get_records('grade_categories', 'parent', $this->id, 'id');
f151b073 304
27f95e9b 305 if ($depth == 1) {
306 $children_array = $this->children_to_array($children, $arraytype, 'grade_category');
307 $this->children = $this->children_to_array($children, 'flat', 'grade_category');
7c8a963f 308 } else {
27f95e9b 309 foreach ($children as $id => $child) {
310 $cat = new grade_category($child, false);
311
312 if ($cat->has_children()) {
313 if ($arraytype == 'nested') {
314 $children_array[] = array('object' => $cat, 'children' => $cat->get_children($newdepth, $arraytype));
315 } else {
316 $children_array[] = $cat;
317 $cat_children = $cat->get_children($newdepth, $arraytype);
318 foreach ($cat_children as $id => $cat_child) {
319 $children_array[] = new grade_category($cat_child, false);
320 }
321 }
322 } else {
323 if ($arraytype == 'nested') {
324 $children_array[] = array('object' => $cat);
325 } else {
326 $children_array[] = $cat;
327 }
328 }
7c8a963f 329 }
27f95e9b 330 }
331 } else {
332 return null;
333 }
334
335 return $children_array;
336 }
337
338 /**
339 * Given an array of stdClass children of a certain $object_type, returns a flat or nested
340 * array of these children, ready for appending to a tree built by get_children.
341 * @static
342 * @param array $children
343 * @param string $arraytype
344 * @param string $object_type
345 * @return array
346 */
347 function children_to_array($children, $arraytype='nested', $object_type='grade_item') {
348 $children_array = array();
349
350 foreach ($children as $id => $child) {
351 if ($arraytype == 'nested') {
352 $children_array[] = array('object' => new $object_type($child, false));
353 } else {
354 $children_array[] = new $object_type($child);
355 }
356 }
7c8a963f 357
27f95e9b 358 return $children_array;
359 }
360
361 /**
362 * Returns true if this category has any child grade_category or grade_item.
363 * @return int number of direct children, or false if none found.
364 */
365 function has_children() {
366 return count_records('grade_categories', 'parent', $this->id) + count_records('grade_items', 'categoryid', $this->id);
367 }
368
369 /**
370 * This method checks whether an existing child exists for this
371 * category. If the new child is of a different type, the method will return false (not allowed).
372 * Otherwise it will return true.
373 * @param object $child This must be a complete object, not a stdClass
374 * @return boolean Success or failure
375 */
376 function can_add_child($child) {
377 if ($this->has_children()) {
378 if (get_class($child) != $this->get_childrentype()) {
379 return false;
380 } else {
381 return true;
382 }
383 } else {
384 return true;
385 }
386 }
387
388 /**
389 * Check the type of the first child of this category, to see whether it is a
390 * grade_category or a grade_item, and returns that type as a string (get_class).
391 * @return string
392 */
393 function get_childrentype() {
394 $children = $this->children;
395 if (empty($this->children)) {
396 $count_item_children = count_records('grade_items', 'categoryid', $this->id);
397 $count_cat_children = count_records('grade_categories', 'parent', $this->id);
f151b073 398
27f95e9b 399 if ($count_item_children > 0) {
400 return 'grade_item';
401 } elseif ($count_cat_children > 0) {
402 return 'grade_category';
403 } else {
404 return null;
7c8a963f 405 }
7c8a963f 406 }
27f95e9b 407 return get_class($children[0]);
7c8a963f 408 }
f151b073 409
410 /**
411 * Retrieves from DB, instantiates and saves the associated grade_item object.
412 * @return object Grade_item
413 */
414 function load_grade_item() {
415 $params = get_record('grade_items', 'categoryid', $this->id, 'itemtype', 'category');
416 $this->grade_item = new grade_item($params);
417 return $this->grade_item;
418 }
8a31e65c 419}
420
421?>