MDL-9506 Implemented grade_tree->update_db, fixed bugs in grade_category etc...
[moodle-pu.git] / lib / grade / grade_item.php
blob1da7943f8e8642b93418a6eb1117110db6c7ab8b
1 <?php // $Id$
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 ///////////////////////////////////////////////////////////////////////////
26 require_once('grade_object.php');
27 global $db;
28 /**
29 * Class representing a grade item. It is responsible for handling its DB representation,
30 * modifying and returning its metadata.
32 class grade_item extends grade_object {
33 /**
34 * DB Table (used by grade_object).
35 * @var string $table
37 var $table = 'grade_items';
39 /**
40 * Array of class variables that are not part of the DB table fields
41 * @var array $nonfields
43 var $nonfields = array('table', 'nonfields', 'calculation', 'grade_grades_raw', 'grade_grades_final', 'scale', 'category', 'outcome');
45 /**
46 * The course this grade_item belongs to.
47 * @var int $courseid
49 var $courseid;
51 /**
52 * The category this grade_item belongs to (optional).
53 * @var int $categoryid
55 var $categoryid;
57 /**
58 * The grade_category object referenced by $this->categoryid or $this->iteminstance (itemtype must be == 'category' in that case).
59 * @var object $category
61 var $category;
63 /**
64 * The name of this grade_item (pushed by the module).
65 * @var string $itemname
67 var $itemname;
69 /**
70 * e.g. 'mod', 'blocks', 'import', 'calculate' etc...
71 * @var string $itemtype
73 var $itemtype;
75 /**
76 * The module pushing this grade (e.g. 'forum', 'quiz', 'assignment' etc).
77 * @var string $itemmodule
79 var $itemmodule;
81 /**
82 * ID of the item module
83 * @var int $iteminstance
85 var $iteminstance;
87 /**
88 * Number of the item in a series of multiple grades pushed by an activity.
89 * @var int $itemnumber
91 var $itemnumber;
93 /**
94 * Info and notes about this item.
95 * @var string $iteminfo
97 var $iteminfo;
99 /**
100 * Arbitrary idnumber provided by the module responsible.
101 * @var string $idnumber
103 var $idnumber;
106 * The type of grade (0 = value, 1 = scale, 2 = text)
107 * @var int $gradetype
109 var $gradetype;
112 * Maximum allowable grade.
113 * @var float $grademax
115 var $grademax;
118 * Minimum allowable grade.
119 * @var float $grademin
121 var $grademin;
124 * id of the scale, if this grade is based on a scale.
125 * @var int $scaleid
127 var $scaleid;
130 * A grade_scale object (referenced by $this->scaleid).
131 * @var object $scale
133 var $scale;
136 * The id of the optional grade_outcome associated with this grade_item.
137 * @var int $outcomeid
139 var $outcomeid;
142 * The grade_outcome this grade is associated with, if applicable.
143 * @var object $outcome
145 var $outcome;
148 * grade required to pass. (grademin < gradepass <= grademax)
149 * @var float $gradepass
151 var $gradepass;
154 * Multiply all grades by this number.
155 * @var float $multfactor
157 var $multfactor;
160 * Add this to all grades.
161 * @var float $plusfactor
163 var $plusfactor;
166 * Sorting order of the columns.
167 * @var int $sortorder
169 var $sortorder;
172 * Date until which to hide this grade_item. If null, 0 or false, grade_item is not hidden. Hiding prevents viewing.
173 * @var int $hidden
175 var $hidden;
178 * Date until which to lock this grade_item. If null, 0 or false, grade_item is not locked. Locking prevents updating.
179 * @var int $locked
181 var $locked = false;
184 * If set, the whole column will be recalculated, then this flag will be switched off.
185 * @var boolean $needsupdate
187 var $needsupdate;
190 * Calculation string used for this item.
191 * @var string $calculation
193 var $calculation;
196 * Array of grade_grades_raw objects linked to this grade_item. They are indexed by userid.
197 * @var array $grade_grades_raw
199 var $grade_grades_raw = array();
202 * Array of grade_grades_final objects linked to this grade_item. They are indexed by userid.
203 * @var array $grade_grades_final
205 var $grade_grades_final = array();
208 * Constructor. Extends the basic functionality defined in grade_object.
209 * @param array $params Can also be a standard object.
210 * @param boolean $fetch Wether or not to fetch the corresponding row from the DB.
212 function grade_item($params=NULL, $fetch=true) {
213 $this->grade_object($params, $fetch);
217 * Instantiates a grade_scale object whose data is retrieved from the DB,
218 * if this item's scaleid variable is set.
219 * @return object grade_scale
221 function load_scale() {
222 if (!empty($this->scaleid)) {
223 $this->scale = grade_scale::fetch('id', $this->scaleid);
224 $this->scale->load_items();
226 return $this->scale;
230 * Instantiates a grade_outcome object whose data is retrieved from the DB,
231 * if this item's outcomeid variable is set.
232 * @return object grade_outcome
234 function load_outcome() {
235 if (!empty($this->outcomeid)) {
236 $this->outcome = grade_outcome::fetch('id', $this->outcomeid);
238 return $this->outcome;
242 * Loads all the grade_grades_raw objects for this grade_item from the DB into grade_item::$grade_grades_raw array.
243 * @return array grade_grades_raw objects
245 function load_raw() {
246 $grade_raw_array = get_records('grade_grades_raw', 'itemid', $this->id);
248 if (empty($grade_raw_array)) {
249 return null;
252 foreach ($grade_raw_array as $r) {
253 $this->grade_grades_raw[$r->userid] = new grade_grades_raw($r);
255 return $this->grade_grades_raw;
259 * Loads all the grade_grades_final objects for this grade_item from the DB into grade_item::$grade_grades_final array.
260 * @param boolean $generatefakenullgrades If set to true, AND $CFG->usenullgrades is true, will replace missing grades with grades, gradevalue=grademin
261 * @return array grade_grades_final objects
263 function load_final($generatefakenullgrades=false) {
264 global $CFG;
266 $grade_final_array = get_records('grade_grades_final', 'itemid', $this->id);
268 if (empty($grade_final_array)) {
269 $this->generate_final();
270 $grade_final_array = get_records('grade_grades_final', 'itemid', $this->id);
273 if (empty($grade_final_array)) {
274 debugging("No final grades recorded for this grade_item");
275 return false;
278 foreach ($grade_final_array as $f) {
279 $this->grade_grades_final[$f->userid] = new grade_grades_final($f);
282 $returnarray = fullclone($this->grade_grades_final);
284 // If we are generating fake null grades, we have to get a list of users
285 if ($generatefakenullgrades && $CFG->usenullgrades) {
286 $users = get_records_sql_menu('SELECT userid AS "user", userid FROM ' . $CFG->prefix . 'grade_grades_final GROUP BY userid ORDER BY userid');
287 if (!empty($users) && is_array($users)) {
288 foreach ($users as $userid) {
289 if (!isset($returnarray[$userid])) {
290 $fakefinal = new grade_grades_final();
291 $fakefinal->itemid = $this->id;
292 $fakefinal->userid = $userid;
293 $fakefinal->gradevalue = $this->grademin;
294 $returnarray[$userid] = $fakefinal;
300 return $returnarray;
304 * Returns an array of values (NOT objects) standardised from the final grades of this grade_item. They are indexed by userid.
305 * @return array integers
307 function get_standardised_final() {
308 $standardised_finals = array();
310 $final_grades = $this->load_final(true);
311 foreach ($final_grades as $userid => $final) {
312 $standardised_finals[$userid] = standardise_score($final->gradevalue, $this->grademin, $this->grademax, 0, 1);
315 return $standardised_finals;
319 * Returns the grade_category object this grade_item belongs to (if any).
320 * This category object may be the parent (referenced by categoryid) or the associated category
321 * (referenced by iteminstance).
323 * @return mixed grade_category object if applicable, NULL otherwise
325 function get_category() {
326 $category = null;
328 if (!empty($this->categoryid)) {
329 $category = grade_category::fetch('id', $this->categoryid);
330 } elseif (!empty($this->iteminstance) && $this->itemtype == 'category') {
331 $category = grade_category::fetch('id', $this->iteminstance);
334 return $category;
338 * Calls upon the get_category method to retrieve the grade_category object
339 * from the DB and assigns it to $this->category. It also returns the object.
340 * @return object Grade_category
342 function load_category() {
343 $this->category = $this->get_category();
344 return $this->category;
348 * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects.
350 function update() {
351 if (!empty($this->outcome->id)) {
352 $this->outcomeid = $this->outcome->id;
355 if (!empty($this->scale->id)) {
356 $this->scaleid = $this->scale->id;
359 $qualifies = $this->qualifies_for_update();
361 $result = parent::update();
363 if ($result && $qualifies) {
364 $category = $this->get_category();
366 if (!empty($category)) {
367 $result = $result && $category->flag_for_update();
371 return $result;
375 * Compares the values held by this object with those of the matching record in DB, and returns
376 * whether or not these differences are sufficient to justify an update of all parent objects.
377 * This assumes that this object has an id number and a matching record in DB. If not, it will return false.
378 * @return boolean
380 function qualifies_for_update() {
381 if (empty($this->id)) {
382 return false;
385 $db_item = new grade_item(array('id' => $this->id));
387 $gradetypediff = $db_item->gradetype != $this->gradetype;
388 $grademaxdiff = $db_item->grademax != $this->grademax;
389 $grademindiff = $db_item->grademin != $this->grademin;
390 $scaleiddiff = $db_item->scaleid != $this->scaleid;
391 $outcomeiddiff = $db_item->outcomeid != $this->outcomeid;
392 $multfactordiff = $db_item->multfactor != $this->multfactor;
393 $plusfactordiff = $db_item->plusfactor != $this->plusfactor;
394 $needsupdatediff = $db_item->needsupdate != $this->needsupdate;
396 if ($gradetypediff || $grademaxdiff || $grademindiff || $scaleiddiff || $outcomeiddiff ||
397 $multfactordiff || $plusfactordiff || $needsupdatediff) {
398 return true;
399 } else {
400 return false;
405 * Finds and returns a grade_item object based on 1-3 field values.
407 * @param string $field1
408 * @param string $value1
409 * @param string $field2
410 * @param string $value2
411 * @param string $field3
412 * @param string $value3
413 * @param string $fields
414 * @return object grade_item object or false if none found.
416 function fetch($field1, $value1, $field2='', $value2='', $field3='', $value3='', $fields="*") {
417 if ($grade_item = get_record('grade_items', $field1, $value1, $field2, $value2, $field3, $value3, $fields)) {
418 if (isset($this) && get_class($this) == 'grade_item') {
419 foreach ($grade_item as $param => $value) {
420 $this->$param = $value;
423 return $this;
424 } else {
425 $grade_item = new grade_item($grade_item);
426 return $grade_item;
428 } else {
429 return false;
434 * If parent::delete() is successful, send flag_for_update message to parent category.
435 * @return boolean Success or failure.
437 function delete() {
438 $result = parent::delete();
439 if ($result) {
440 $category = $this->get_category();
441 if (!empty($category)) {
442 return $category->flag_for_update();
445 return $result;
449 * In addition to perform parent::insert(), this calls the grade_item's category's (if applicable) flag_for_update() method.
450 * @return int ID of the new grade_item record.
452 function insert() {
453 $result = parent::insert();
455 // Notify parent category of need to update. Note that a grade_item may not have a categoryid.
456 if ($result) {
457 $category = $this->get_category();
458 if (!empty($category)) {
459 if (!$category->flag_for_update()) {
460 debugging("Could not notify parent category of the need to update its final grades.");
461 return false;
464 } else {
465 debugging("Could not insert this grade_item in the database!");
468 return $result;
472 * Returns the raw values for this grade item (as imported by module or other source).
473 * @param int $userid Optional: to retrieve a single raw grade
474 * @return mixed An array of all raw_grades (stdClass objects) for this grade_item, or a single raw_grade.
476 function get_raw($userid=NULL) {
477 if (empty($this->grade_grades_raw)) {
478 $this->load_raw();
481 $grade_raw_array = null;
482 if (!empty($userid)) {
483 $r = get_record('grade_grades_raw', 'itemid', $this->id, 'userid', $userid);
484 $grade_raw_array[$r->userid] = new grade_grades_raw($r);
485 } else {
486 $grade_raw_array = $this->grade_grades_raw;
488 return $grade_raw_array;
492 * Takes an array of grade_grades_raw objects, indexed by userid, and saves each as a raw grade
493 * under this grade_item. This replaces any existing grades, after having logged each change in the history table.
494 * @param array $raw_grades
495 * @return boolean success or failure
497 function save_raw($raw_grades, $howmodified='module', $note=NULL) {
498 if (!empty($raw_grades) && is_array($raw_grades)) {
499 $this->load_raw();
501 foreach ($raw_grades as $userid => $raw_grade) {
502 if (!empty($this->grade_grades_raw[$userid])) {
503 $raw_grade->update($raw_grade->gradevalue, $howmodified, $note);
504 } else {
505 $raw_grade->itemid = $this->id;
506 if ($raw_grade->gradevalue > $raw_grade->grademax) {
507 die("raw GRADE EXCEEDED grademax FIRST");
509 $raw_grade->insert();
512 $this->grade_grades_raw[$userid] = $raw_grade;
514 } else {
515 debugging("The data given to grade_item::save_raw($raw_grades) was not valid, it must be an array of raw grades.");
516 return false;
521 * Returns the final values for this grade item (as imported by module or other source).
522 * @param int $userid Optional: to retrieve a single final grade
523 * @return mixed An array of all final_grades (stdClass objects) for this grade_item, or a single final_grade.
525 function get_final($userid=NULL) {
526 if (empty($this->grade_grades_final)) {
527 $this->load_final();
530 $grade_final_array = null;
531 if (!empty($userid)) {
532 $f = get_record('grade_grades_final', 'itemid', $this->id, 'userid', $userid);
533 $grade_final_array[$f->userid] = new grade_grades_final($f);
534 } else {
535 $grade_final_array = $this->grade_grades_final;
537 return $grade_final_array;
541 * Once the raw_grades are imported or entered, this method uses the grade_item's calculation and rules to
542 * generate final grade entries in the DB.
543 * @return array final grade objects (grade_grades_final).
545 function generate_final() {
546 if (empty($this->grade_grades_raw)) {
547 $this->load_raw();
550 $success = true;
552 foreach ($this->grade_grades_raw as $raw_grade) {
553 $final_grade = new grade_grades_final();
554 $final_grade->gradevalue = $this->adjust_grade($raw_grade);
555 $final_grade->itemid = $this->id;
556 $final_grade->userid = $raw_grade->userid;
557 if ($final_grade->gradevalue > $this->grademax) {
558 die("FINAL GRADE EXCEEDED grademax FIRST");
560 $success = $success & $final_grade->insert();
561 $this->grade_grades_final[$final_grade->userid] = $final_grade;
564 return $success;
568 * Returns this object's calculation.
569 * @param boolean $fetch Whether to fetch the value from the DB or not (false == just use the object's value)
570 * @return mixed $calculation A string if found, false otherwise.
572 function get_calculation($fetch = false) {
573 if (!$fetch && get_class($this->calculation) == 'grade_calculation') {
574 return $this->calculation;
576 $grade_calculation = grade_calculation::fetch('itemid', $this->id);
578 if (empty($grade_calculation)) { // There is no calculation in DB
579 return false;
580 } elseif ($grade_calculation->calculation != $this->calculation->calculation) { // The object's calculation is not in sync with the DB (new value??)
581 $this->calculation = $grade_calculation;
582 return $grade_calculation;
583 } else { // The object's calculation is already in sync with the database
584 return $this->calculation;
589 * Sets this item's calculation (creates it) if not yet set, or
590 * updates it if already set (in the DB). If no calculation is given,
591 * the method will attempt to retrieve one from the Database, based on
592 * the variables set in the current object.
593 * @param string $calculation
594 * @return boolean
596 function set_calculation($calculation = null) {
597 if (empty($calculation)) { // We are setting this item object's calculation variable from the DB
598 $grade_calculation = $this->get_calculation(true);
599 if (empty($grade_calculation)) {
600 debugging("No calculation to set for this grade_item.");
601 return false;
602 } else {
603 $this->calculation = $grade_calculation;
605 } else { // We are updating or creating the calculation entry in the DB
606 $grade_calculation = $this->get_calculation();
608 if (empty($grade_calculation)) { // Creating
609 $grade_calculation = new grade_calculation();
610 $grade_calculation->calculation = $calculation;
611 $grade_calculation->itemid = $this->id;
613 if ($grade_calculation->insert()) {
614 $this->calculation = $grade_calculation;
615 return true;
616 } else {
617 debugging("Could not save the calculation in the database, for this grade_item.");
618 return false;
620 } else { // Updating
621 $grade_calculation->calculation = $calculation;
622 $grade_calculation = new grade_calculation($grade_calculation);
623 $this->calculation = $grade_calculation;
624 return $grade_calculation->update();
630 * Returns the locked state of this grade_item (if the grade_item is locked OR no specific
631 * $userid is given) or the locked state of a specific grade within this item if a specific
632 * $userid is given and the grade_item is unlocked.
634 * @param int $userid
635 * @return boolean Locked state
637 function is_locked($userid=NULL) {
638 if ($this->locked || empty($userid)) {
639 return $this->locked; // This could be true or false (false only if no $userid given)
640 } else {
641 $final = $this->get_final($userid);
642 return $final->locked;
647 * Locks or unlocks this grade_item and (optionally) all its associated final grades.
648 * @param boolean $update_final Whether to update final grades too
649 * @param boolean $new_state Optional new state. Will use inverse of current state otherwise.
650 * @return int Number of final grades changed, or false if error occurred during update.
652 function toggle_locking($update_final=false, $new_state=NULL) {
653 $this->locked = !$this->locked;
655 if (!empty($new_state)) {
656 $this->locked = $new_state;
659 if (!$this->update()) {
660 debugging("Could not update this grade_item's locked state in the database.");
661 return false;
664 $count = 0;
666 if ($update_final) {
667 $this->load_final();
668 foreach ($this->grade_grades_final as $id => $final) {
669 $final->locked = $this->locked;
670 if (!$final->update()) {
671 debugging("Could not update this grade_item's final grade's locked state in the database.");
672 return false;
674 $count++;
676 $this->load_final();
679 return $count;
683 * Locks or unlocks this grade_item and (optionally) all its associated final grades.
684 * @param boolean $update_final Whether to update final grades too
685 * @param boolean $new_state Optional new state. Will use inverse of current state otherwise.
686 * @return int Number of final grades changed, or false if error occurred during update.
688 function toggle_hiding($update_final=false, $new_state=NULL) {
689 $this->hidden = !$this->hidden;
691 if (!empty($new_state)) {
692 $this->hidden = $new_state;
695 if (!$this->update()) {
696 debugging("Could not update this grade_item's hidden state in the database.");
697 return false;
700 $count = 0;
702 if ($update_final) {
703 $this->load_final();
704 foreach ($this->grade_grades_final as $id => $final) {
705 $final->hidden = $this->hidden;
706 if (!$final->update()) {
707 debugging("Could not update this grade_item's final grade's hidden state in the database.");
708 return false;
710 $count++;
712 $this->load_final();
715 return $count;
720 * Performs the necessary calculations on the grades_final referenced by this grade_item,
721 * and stores the results in grade_grades_final. Performs this for only one userid if
722 * requested. Also resets the needs_update flag once successfully performed.
724 * @param int $userid
725 * @return int Number of grades updated, or false if error
727 function update_final_grade($userid=NULL) {
728 if (empty($this->grade_grades_final)) {
729 $this->load_final();
731 if (empty($this->grade_grades_raw)) {
732 $this->load_raw();
735 $count = 0;
737 $grade_final_array = array();
738 $grade_raw_array = array();
740 if (!empty($userid)) {
741 $grade_final_array[$userid] = $this->grade_grades_final[$userid];
742 $grade_raw_array[$userid] = $this->grade_grades_raw[$userid];
743 } else {
744 $grade_final_array = $this->grade_grades_final;
745 $grade_raw_array = $this->grade_grades_raw;
748 // The following code assumes that there is a grade_final object in DB for every
749 // grade_raw object. This assumption depends on the correct creation of grade_final entries.
750 // This also assumes that the two arrays $this->grade_grades_raw and its final counterpart are
751 // indexed by userid, not sequentially or by grade_id
752 if (count($this->grade_grades_final) != count($this->grade_grades_raw)) {
753 $this->generate_final();
756 foreach ($grade_raw_array as $userid => $raw) {
757 $newgradevalue = $raw->gradevalue;
759 if (!empty($this->calculation)) {
760 $this->upgrade_calculation_to_object();
761 $newgradevalue = $this->calculation->compute($raw->gradevalue);
764 $final = $this->grade_grades_final[$userid];
766 $final->gradevalue = $this->adjust_grade($raw, $newgradevalue);
768 if ($final->update()) {
769 $count++;
770 } else {
771 debugging("Could not update a final grade in this grade_item.");
772 return false;
776 return $count;
780 * Use this when the calculation object is a stdClass (rare) and you need it to have full
781 * object status (with methods and all).
783 function upgrade_calculation_to_object() {
784 if (!is_a($this->calculation, 'grade_calculation')) {
785 $this->calculation = new grade_calculation($this->calculation, false);
790 * Given a float grade value or integer grade scale, applies a number of adjustment based on
791 * grade_item variables and returns the result.
792 * @param object $grade_raw The raw object to compare with this grade_item's rules
793 * @param mixed $gradevalue The new gradevalue (after calculations are performed).
794 * If null, the raw_grade's gradevalue will be used.
795 * @return mixed
797 function adjust_grade($grade_raw, $gradevalue=NULL) {
798 $raw_offset = 0;
799 $item_offset = 0;
801 if ($this->gradetype == GRADE_TYPE_VALUE) { // Dealing with numerical grade
802 if (empty($gradevalue)) {
803 $gradevalue = $grade_raw->gradevalue;
806 } elseif($this->gradetype == GRADE_TYPE_SCALE) { // Dealing with a scale value
807 if (empty($gradevalue)) {
808 $gradevalue = $grade_raw->gradevalue;
811 // In case the scale objects haven't been loaded, do it now
812 if (empty($grade_raw->scale)) {
813 $grade_raw->load_scale();
816 if (empty($this->scale)) {
817 $this->load_scale();
820 $grade_raw->grademax = count($grade_raw->scale->scale_items) - 1;
821 $this->grademax = count($this->scale->scale_items) - 1;
822 $grade_raw->grademin = 0;
823 $this->grademin = 0;
825 } elseif ($this->gradetype != GRADE_TYPE_TEXT) { // Something's wrong, the raw grade has no value!?
826 return "Error: The gradeitem did not have a valid gradetype value, was $this->gradetype instead";
829 // Standardise score to the new grade range
830 $gradevalue = standardise_score($gradevalue, $grade_raw->grademin,
831 $grade_raw->grademax, $this->grademin, $this->grademax);
833 // Apply factors, depending on whether it's a scale or value
834 if ($this->gradetype == GRADE_TYPE_VALUE) {
835 // Apply other grade_item factors
836 $gradevalue *= $this->multfactor;
837 $gradevalue += $this->plusfactor;
839 return $gradevalue;
843 * Sets this grade_item's needsupdate to true. Also looks at parent category, if any, and calls
844 * its flag_for_update() method.
845 * This is triggered whenever any change in any grade_raw may cause grade_finals
846 * for this grade_item to require an update. The flag needs to be propagated up all
847 * levels until it reaches the top category. This is then used to determine whether or not
848 * to regenerate the raw and final grades for each category grade_item.
849 * @return boolean Success or failure
851 function flag_for_update() {
852 $this->needsupdate = true;
854 $result = $this->update();
855 $category = $this->get_category();
857 if (!empty($category)) {
858 $result = $result && $category->flag_for_update();
861 return $result;
865 * Returns the sortorder of this grade_item. This method is also available in
866 * grade_category, for cases where the object type is not know. It will act as a virtual
867 * variable for a grade_category.
868 * @return int Sort order
870 function get_sortorder() {
871 return $this->sortorder;