2007-04-27 01:17:02 +00:00
< ? php // $Id$
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.com //
// //
// Copyright (C) 2001-2003 Martin Dougiamas http://dougiamas.com //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation; either version 2 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
require_once ( 'grade_object.php' );
2007-04-27 07:28:41 +00:00
class grade_category extends grade_object {
2007-04-27 01:17:02 +00:00
/**
2007-05-02 07:20:13 +00:00
* The DB table .
2007-04-27 01:17:02 +00:00
* @ var string $table
*/
var $table = 'grade_categories' ;
/**
* Array of class variables that are not part of the DB table fields
* @ var array $nonfields
*/
2007-05-02 07:20:13 +00:00
var $nonfields = array ( 'table' , 'nonfields' , 'children' , 'all_children' );
2007-04-27 01:17:02 +00:00
/**
* The course this category belongs to .
* @ var int $courseid
*/
var $courseid ;
/**
* The category this category belongs to ( optional ) .
2007-04-27 08:12:16 +00:00
* @ var int $parent
2007-04-27 01:17:02 +00:00
*/
2007-04-27 08:12:16 +00:00
var $parent ;
2007-05-10 02:34:01 +00:00
/**
* The grade_category object referenced by $this -> parent ( PK ) .
* @ var object $parent_category
*/
var $parent_category ;
2007-05-03 07:10:22 +00:00
2007-05-31 08:57:05 +00:00
/**
* A grade_category object this category used to belong to before getting updated . Will be deleted shortly .
* @ var object $old_parent
*/
var $old_parent ;
2007-04-27 08:12:16 +00:00
/**
* The number of parents this category has .
* @ var int $depth
*/
var $depth = 0 ;
/**
* Shows the hierarchical path for this category as / 1 / 2 / 3 ( like course_categories ), the last number being
* this category ' s autoincrement ID number .
* @ var string $path
*/
var $path ;
2007-04-27 01:17:02 +00:00
/**
* The name of this category .
* @ var string $fullname
*/
var $fullname ;
/**
* A constant pointing to one of the predefined aggregation strategies ( none , mean , median , sum etc ) .
* @ var int $aggregation
*/
var $aggregation ;
/**
* Keep only the X highest items .
* @ var int $keephigh
*/
var $keephigh ;
/**
* Drop the X lowest items .
* @ var int $droplow
*/
var $droplow ;
/**
* Date until which to hide this category . If null , 0 or false , category is not hidden .
* @ var int $hidden
*/
var $hidden ;
/**
* Array of grade_items or grade_categories nested exactly 1 level below this category
* @ var array $children
*/
var $children ;
2007-05-02 07:20:13 +00:00
/**
* A hierarchical array of all children below this category . This is stored separately from
* $children because it is more memory - intensive and may not be used as often .
* @ var array $all_children
*/
var $all_children ;
2007-05-07 07:33:11 +00:00
/**
* An associated grade_item object , with itemtype = category , used to calculate and cache a set of grade values
* for this category .
* @ var object $grade_item
*/
var $grade_item ;
2007-04-27 08:12:16 +00:00
/**
* Constructor . Extends the basic functionality defined in grade_object .
* @ param array $params Can also be a standard object .
2007-05-07 07:33:11 +00:00
* @ param boolean $fetch Whether or not to fetch the corresponding row from the DB .
* @ param object $grade_item The associated grade_item object can be passed during construction .
2007-04-27 08:12:16 +00:00
*/
2007-05-07 07:33:11 +00:00
function grade_category ( $params = NULL , $fetch = true , $grade_item = NULL ) {
2007-04-27 08:12:16 +00:00
$this -> grade_object ( $params , $fetch );
2007-05-07 07:33:11 +00:00
if ( ! empty ( $grade_item ) && $grade_item -> itemtype == 'category' ) {
$this -> grade_item = $grade_item ;
if ( empty ( $this -> grade_item -> iteminstance )) {
$this -> grade_item -> iteminstance = $this -> id ;
$this -> grade_item -> update ();
}
}
2007-05-11 08:46:34 +00:00
$this -> path = grade_category :: build_path ( $this );
2007-04-27 08:12:16 +00:00
}
/**
* Builds this category ' s path string based on its parents ( if any ) and its own id number .
* This is typically done just before inserting this object in the DB for the first time ,
2007-05-01 08:56:37 +00:00
* or when a new parent is added or changed . It is a recursive function : once the calling
* object no longer has a parent , the path is complete .
*
* @ static
* @ param object $grade_category
* @ return int The depth of this category ( 2 means there is one parent )
2007-04-27 08:12:16 +00:00
*/
2007-05-01 08:56:37 +00:00
function build_path ( $grade_category ) {
if ( empty ( $grade_category -> parent )) {
return " / $grade_category->id " ;
} else {
$parent = get_record ( 'grade_categories' , 'id' , $grade_category -> parent );
return grade_category :: build_path ( $parent ) . " / $grade_category->id " ;
}
2007-04-27 08:12:16 +00:00
}
2007-04-27 01:17:02 +00:00
/**
* Finds and returns a grade_category object based on 1 - 3 field values .
*
* @ param string $field1
* @ param string $value1
* @ param string $field2
* @ param string $value2
* @ param string $field3
* @ param string $value3
* @ param string $fields
* @ return object grade_category object or false if none found .
*/
2007-05-03 07:10:22 +00:00
function fetch ( $field1 , $value1 , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' , $fields = " * " ) {
2007-04-27 01:17:02 +00:00
if ( $grade_category = get_record ( 'grade_categories' , $field1 , $value1 , $field2 , $value2 , $field3 , $value3 , $fields )) {
2007-05-02 07:20:13 +00:00
if ( isset ( $this ) && get_class ( $this ) == 'grade_category' ) {
2007-04-27 01:17:02 +00:00
foreach ( $grade_category as $param => $value ) {
$this -> $param = $value ;
}
return $this ;
2007-05-02 07:20:13 +00:00
} else {
$grade_category = new grade_category ( $grade_category );
return $grade_category ;
2007-04-27 01:17:02 +00:00
}
} else {
return false ;
}
2007-05-01 08:56:37 +00:00
}
2007-05-10 08:08:43 +00:00
/**
* In addition to update () as defined in grade_object , call flag_for_update of parent categories , if applicable .
*/
function update () {
$qualifies = $this -> qualifies_for_update ();
2007-05-31 03:15:43 +00:00
// Update the grade_item's sortorder if needed
if ( ! empty ( $this -> sortorder )) {
$this -> load_grade_item ();
if ( ! empty ( $this -> grade_item )) {
$this -> grade_item -> sortorder = $this -> sortorder ;
$this -> grade_item -> update ();
}
unset ( $this -> sortorder );
}
2007-05-10 08:08:43 +00:00
$result = parent :: update ();
2007-05-10 08:58:00 +00:00
// Use $this->path to update all parent categories
2007-05-10 08:08:43 +00:00
if ( $result && $qualifies ) {
2007-05-10 08:58:00 +00:00
$this -> flag_for_update ();
2007-05-10 08:08:43 +00:00
}
return $result ;
}
/**
* If parent :: delete () is successful , send flag_for_update message to parent category .
* @ return boolean Success or failure .
*/
function delete () {
$result = parent :: delete ();
if ( $result ) {
$this -> load_parent_category ();
if ( ! empty ( $this -> parent_category )) {
$result = $result && $this -> parent_category -> flag_for_update ();
}
}
return $result ;
}
2007-05-01 08:56:37 +00:00
/**
* In addition to the normal insert () defined in grade_object , this method sets the depth
* and path for this object , and update the record accordingly . The reason why this must
* be done here instead of in the constructor , is that they both need to know the record ' s
2007-05-03 07:10:22 +00:00
* id number , which only gets created at insertion time .
2007-05-07 07:33:11 +00:00
* This method also creates an associated grade_item if this wasn ' t done during construction .
2007-05-01 08:56:37 +00:00
*/
function insert () {
$result = parent :: insert ();
2007-05-11 03:29:00 +00:00
$this -> path = grade_category :: build_path ( $this );
2007-05-01 08:56:37 +00:00
// Build path and depth variables
if ( ! empty ( $this -> parent )) {
$this -> depth = $this -> get_depth_from_path ();
} else {
$this -> depth = 1 ;
}
$this -> update ();
2007-05-07 07:33:11 +00:00
if ( empty ( $this -> grade_item )) {
$grade_item = new grade_item ();
$grade_item -> iteminstance = $this -> id ;
$grade_item -> itemtype = 'category' ;
2007-05-09 07:50:28 +00:00
if ( ! $grade_item -> insert ()) {
2007-05-17 09:04:52 +00:00
debugging ( " Could not insert this grade_item in the database: " . print_r ( $grade_item , true ));
2007-05-09 07:50:28 +00:00
return false ;
}
2007-05-07 07:33:11 +00:00
$this -> grade_item = $grade_item ;
}
2007-05-10 08:58:00 +00:00
2007-05-10 08:08:43 +00:00
// Notify parent category of need to update.
if ( $result ) {
$this -> load_parent_category ();
if ( ! empty ( $this -> parent_category )) {
if ( ! $this -> parent_category -> flag_for_update ()) {
2007-05-17 09:04:52 +00:00
debugging ( " Could not notify parent category of the need to update its final grades. " );
2007-05-10 08:08:43 +00:00
return false ;
}
}
}
2007-05-01 08:56:37 +00:00
return $result ;
}
2007-05-10 08:08:43 +00:00
/**
* Compares the values held by this object with those of the matching record in DB , and returns
* whether or not these differences are sufficient to justify an update of all parent objects .
* This assumes that this object has an id number and a matching record in DB . If not , it will return false .
* @ return boolean
*/
function qualifies_for_update () {
if ( empty ( $this -> id )) {
return false ;
}
$db_item = new grade_category ( array ( 'id' => $this -> id ));
$aggregationdiff = $db_item -> aggregation != $this -> aggregation ;
$keephighdiff = $db_item -> keephigh != $this -> keephigh ;
$droplowdiff = $db_item -> droplow != $this -> droplow ;
if ( $aggregationdiff || $keephighdiff || $droplowdiff ) {
return true ;
} else {
return false ;
}
}
2007-05-10 02:34:01 +00:00
/**
* Sets this category 's and its parent' s grade_item . needsupdate to true .
* This is triggered whenever any change in any lower level may cause grade_finals
* for this category to require an update . The flag needs to be propagated up all
* levels until it reaches the top category . This is then used to determine whether or not
2007-05-10 08:58:00 +00:00
* to regenerate the raw and final grades for each category grade_item . This is accomplished
* thanks to the path variable , so we don ' t need to use recursion .
2007-05-10 02:34:01 +00:00
* @ return boolean Success or failure
*/
function flag_for_update () {
$result = true ;
2007-05-10 08:08:43 +00:00
2007-05-10 02:34:01 +00:00
$this -> load_grade_item ();
2007-05-10 08:08:43 +00:00
if ( empty ( $this -> grade_item )) {
die ( " Associated grade_item object does not exist for this grade_category! " . print_object ( $this ));
2007-05-11 03:29:00 +00:00
// TODO Send error message, this is a critical error: each category MUST have a matching grade_item object and load_grade_item() is supposed to create one!
2007-05-10 08:08:43 +00:00
}
2007-05-10 08:58:00 +00:00
$paths = explode ( '/' , $this -> path );
2007-05-11 02:43:46 +00:00
2007-05-11 03:29:00 +00:00
// Remove the first index, which is always empty
unset ( $paths [ 0 ]);
2007-05-11 02:43:46 +00:00
2007-05-11 03:29:00 +00:00
if ( ! empty ( $paths )) {
$wheresql = '' ;
foreach ( $paths as $categoryid ) {
$wheresql .= " iteminstance = $categoryid OR " ;
}
$wheresql = substr ( $wheresql , 0 , strrpos ( $wheresql , 'OR' ));
2007-05-30 03:09:38 +00:00
$grade_items = set_field_select ( 'grade_items' , 'needsupdate' , '1' , $wheresql . ' AND courseid = ' . $this -> courseid );
2007-05-11 03:29:00 +00:00
$this -> grade_item -> update_from_db ();
2007-05-10 02:34:01 +00:00
}
return $result ;
}
2007-05-08 02:20:26 +00:00
/**
* Generates and saves raw_grades , based on this category ' s immediate children , then uses the
* associated grade_item to generate matching final grades . These immediate children must first have their own
* raw and final grades , which means that ultimately we must get grade_items as children . The category ' s aggregation
* method is used to generate these raw grades , which can then be used by the category ' s associated grade_item
* to apply calculations to and generate final grades .
2007-05-11 08:46:34 +00:00
* Steps to follow :
* 1. If the children are categories , AND their grade_item ' s needsupdate is true call generate_grades () on each of them ( recursion )
* 2. Get final grades from immediate children ( if the children are categories , get the final grades from their grade_item )
* 3. Aggregate these grades
* 4. Save them under $this -> grade_item -> grade_grades_raw
* 5. Use the grade_item ' s methods for generating the final grades .
2007-05-08 02:20:26 +00:00
*/
function generate_grades () {
2007-05-11 08:46:34 +00:00
// 1. Get immediate children
$children = $this -> get_children ( 1 , 'flat' );
2007-05-08 02:20:26 +00:00
2007-05-11 08:46:34 +00:00
if ( empty ( $children )) {
2007-05-17 09:04:52 +00:00
debugging ( " Could not generate grades for this category, it has no children. " );
2007-05-11 08:46:34 +00:00
return false ;
2007-05-08 02:20:26 +00:00
}
2007-05-11 08:46:34 +00:00
// This assumes that all immediate children are of the same type (category OR item)
$childrentype = get_class ( current ( $children ));
2007-05-08 08:01:55 +00:00
2007-05-11 08:46:34 +00:00
$final_grades_for_aggregation = array ();
// 2. Get final grades from immediate children, after generating them if needed.
// NOTE: Make sure that the arrays of final grades are indexed by userid. The resulting arrays are unlikely to match in sizes.
if ( $childrentype == 'grade_category' ) {
foreach ( $children as $id => $category ) {
$category -> load_grade_item ();
if ( $category -> grade_item -> needsupdate ) {
$category -> generate_grades ();
}
$final_grades_for_aggregation [] = $category -> grade_item -> get_standardised_final ();
2007-05-08 08:01:55 +00:00
}
2007-05-11 08:46:34 +00:00
} elseif ( $childrentype == 'grade_item' ) {
foreach ( $children as $id => $item ) {
if ( $item -> needsupdate ) {
$item -> generate_final ();
}
$final_grades_for_aggregation [] = $item -> get_standardised_final ();
2007-05-08 02:20:26 +00:00
}
}
2007-05-14 08:18:03 +00:00
2007-05-11 08:46:34 +00:00
// 3. Aggregate the grades
$aggregated_grades = $this -> aggregate_grades ( $final_grades_for_aggregation );
2007-05-14 04:14:22 +00:00
2007-05-11 08:46:34 +00:00
// 4. Save the resulting array of grades as raw grades
$this -> load_grade_item ();
$this -> grade_item -> save_raw ( $aggregated_grades );
// 5. Use the grade_item's generate_final method
$this -> grade_item -> generate_final ();
return true ;
2007-05-08 02:20:26 +00:00
}
2007-05-21 08:56:44 +00:00
/**
* Given an array of grade values ( numerical indices ), applies droplow or keephigh
* rules to limit the final array .
* @ param array $grades
* @ return array Limited grades .
*/
function apply_limit_rules ( $grades ) {
rsort ( $grades , SORT_NUMERIC );
if ( ! empty ( $this -> droplow )) {
for ( $i = 0 ; $i < $this -> droplow ; $i ++ ) {
array_pop ( $grades );
}
} elseif ( ! empty ( $this -> keephigh )) {
while ( count ( $grades ) > $this -> keephigh ) {
array_pop ( $grades );
}
}
sort ( $grades , SORT_NUMERIC );
return $grades ;
}
2007-05-08 02:20:26 +00:00
/**
2007-05-11 08:46:34 +00:00
* Given an array of arrays of values , standardised from 0 to 1 and indexed by userid ,
* uses this category ' s aggregation method to
* compute and return a single array of grade_raw objects with the aggregated gradevalue .
2007-05-08 02:20:26 +00:00
* @ param array $raw_grade_sets
* @ return array Raw grade objects
*/
2007-05-11 08:46:34 +00:00
function aggregate_grades ( $final_grade_sets ) {
if ( empty ( $final_grade_sets )) {
2007-05-17 09:04:52 +00:00
debugging ( " Could not aggregate grades: no array of grades given to aggregate. " );
2007-05-08 08:01:55 +00:00
return null ;
}
2007-05-14 04:14:22 +00:00
2007-05-08 08:01:55 +00:00
$aggregated_grades = array ();
$pooled_grades = array ();
2007-05-11 08:46:34 +00:00
foreach ( $final_grade_sets as $setkey => $set ) {
foreach ( $set as $userid => $final_grade ) {
2007-05-08 08:01:55 +00:00
$this -> load_grade_item ();
2007-05-11 08:46:34 +00:00
$value = standardise_score (( float ) $final_grade , 0 , 1 , $this -> grade_item -> grademin , $this -> grade_item -> grademax );
2007-05-21 05:54:12 +00:00
$pooled_grades [ $userid ][] = ( string ) $value ;
2007-05-08 08:01:55 +00:00
}
}
2007-05-14 04:14:22 +00:00
2007-05-08 08:01:55 +00:00
foreach ( $pooled_grades as $userid => $grades ) {
$aggregated_value = null ;
2007-05-21 05:54:12 +00:00
2007-05-21 08:56:44 +00:00
$grades = $this -> apply_limit_rules ( $grades );
2007-05-14 04:14:22 +00:00
2007-05-21 05:54:12 +00:00
if ( count ( $grades ) > 1 ) {
switch ( $this -> aggregation ) {
case GRADE_AGGREGATE_MEAN : // Arithmetic average
$num = count ( $grades );
$sum = array_sum ( $grades );
$aggregated_value = $sum / $num ;
break ;
case GRADE_AGGREGATE_MEDIAN : // Middle point value in the set: ignores frequencies
sort ( $grades );
$num = count ( $grades );
$halfpoint = intval ( $num / 2 );
if ( $num % 2 == 0 ) {
$aggregated_value = ( $grades [ ceil ( $halfpoint )] + $grades [ floor ( $halfpoint )]) / 2 ;
} else {
$aggregated_value = $grades [ $halfpoint ];
}
break ;
case GRADE_AGGREGATE_MODE : // Value that occurs most frequently. Not always useful (all values are likely to be different)
// TODO implement or reject
break ;
case GRADE_AGGREGATE_SUM : // I don't see much point to this one either
$aggregated_value = array_sum ( $grades );
break ;
default :
$num = count ( $grades );
$sum = array_sum ( $grades );
$aggregated_value = $sum / $num ;
break ;
}
} elseif ( count ( $grades ) == 1 ) {
$aggregated_value = $grades [ 0 ];
} else {
// TODO what happens if the droplow and keephigh rules have deleted all grades?
$aggregated_value = 0 ;
}
2007-05-14 08:18:03 +00:00
2007-05-08 08:01:55 +00:00
$grade_raw = new grade_grades_raw ();
2007-05-14 08:18:03 +00:00
2007-05-08 08:01:55 +00:00
$grade_raw -> userid = $userid ;
$grade_raw -> gradevalue = $aggregated_value ;
$grade_raw -> grademin = $this -> grade_item -> grademin ;
$grade_raw -> grademax = $this -> grade_item -> grademax ;
$grade_raw -> itemid = $this -> grade_item -> id ;
$aggregated_grades [ $userid ] = $grade_raw ;
}
2007-05-14 04:14:22 +00:00
2007-05-08 08:01:55 +00:00
return $aggregated_grades ;
2007-05-08 02:20:26 +00:00
}
2007-05-31 03:15:43 +00:00
/**
* Given an array of stdClass children of a certain $object_type , returns a flat or nested
* array of these children , ready for appending to a tree built by get_children .
* @ static
* @ param array $children
* @ param string $arraytype
* @ param string $object_type
* @ return array
*/
function children_to_array ( $children , $arraytype = 'nested' , $object_type = 'grade_item' ) {
$children_array = array ();
foreach ( $children as $id => $child ) {
$child = new $object_type ( $child , false );
if ( $arraytype == 'nested' ) {
$children_array [ $child -> get_sortorder ()] = array ( 'object' => $child );
} else {
$children_array [ $child -> get_sortorder ()] = $child ;
}
}
return $children_array ;
}
/**
* Returns true if this category has any child grade_category or grade_item .
* @ return int number of direct children , or false if none found .
*/
function has_children () {
return count_records ( 'grade_categories' , 'parent' , $this -> id ) + count_records ( 'grade_items' , 'categoryid' , $this -> id );
}
/**
* This method checks whether an existing child exists for this
* category . If the new child is of a different type , the method will return false ( not allowed ) .
* Otherwise it will return true .
* @ param object $child This must be a complete object , not a stdClass
* @ return boolean Success or failure
*/
function can_add_child ( $child ) {
if ( $this -> has_children ()) {
if ( get_class ( $child ) != $this -> get_childrentype ()) {
return false ;
} else {
return true ;
}
} else {
return true ;
}
}
2007-05-01 08:56:37 +00:00
/**
* Looks at a path string ( e . g . / 2 / 45 / 56 ) and returns the depth level represented by this path ( in this example , 3 ) .
* If no string is given , it looks at the obect ' s path and assigns the resulting depth to its $depth variable .
* @ param string $path
* @ return int Depth level
*/
function get_depth_from_path ( $path = NULL ) {
if ( empty ( $path )) {
$path = $this -> path ;
}
preg_match_all ( '/\/([0-9]+)+?/' , $path , $matches );
$depth = count ( $matches [ 0 ]);
return $depth ;
}
2007-05-02 07:20:13 +00:00
/**
* Fetches and returns all the children categories and / or grade_items belonging to this category .
* By default only returns the immediate children ( depth = 1 ), but deeper levels can be requested ,
2007-05-17 09:04:52 +00:00
* as well as all levels ( 0 ) . The elements are indexed by sort order .
2007-05-02 07:20:13 +00:00
* @ param int $depth 1 for immediate children , 0 for all children , and 2 + for specific levels deeper than 1.
* @ param string $arraytype Either 'nested' or 'flat' . A nested array represents the true hierarchy , but is more difficult to work with .
* @ return array Array of child objects ( grade_category and grade_item ) .
*/
function get_children ( $depth = 1 , $arraytype = 'nested' ) {
2007-05-03 07:10:22 +00:00
$children_array = array ();
// Set up $depth for recursion
$newdepth = $depth ;
if ( $depth > 1 ) {
$newdepth -- ;
}
$childrentype = $this -> get_childrentype ();
2007-05-07 07:33:11 +00:00
2007-05-03 07:10:22 +00:00
if ( $childrentype == 'grade_item' ) {
2007-05-07 07:33:11 +00:00
$children = get_records ( 'grade_items' , 'categoryid' , $this -> id );
2007-05-03 07:10:22 +00:00
// No need to proceed with recursion
$children_array = $this -> children_to_array ( $children , $arraytype , 'grade_item' );
$this -> children = $this -> children_to_array ( $children , 'flat' , 'grade_item' );
} elseif ( $childrentype == 'grade_category' ) {
$children = get_records ( 'grade_categories' , 'parent' , $this -> id , 'id' );
2007-05-07 07:33:11 +00:00
2007-05-03 07:10:22 +00:00
if ( $depth == 1 ) {
$children_array = $this -> children_to_array ( $children , $arraytype , 'grade_category' );
$this -> children = $this -> children_to_array ( $children , 'flat' , 'grade_category' );
2007-05-02 07:20:13 +00:00
} else {
2007-05-03 07:10:22 +00:00
foreach ( $children as $id => $child ) {
$cat = new grade_category ( $child , false );
if ( $cat -> has_children ()) {
if ( $arraytype == 'nested' ) {
2007-05-17 09:04:52 +00:00
$children_array [ $cat -> get_sortorder ()] = array ( 'object' => $cat , 'children' => $cat -> get_children ( $newdepth , $arraytype ));
2007-05-03 07:10:22 +00:00
} else {
2007-05-17 09:04:52 +00:00
$children_array [ $cat -> get_sortorder ()] = $cat ;
2007-05-03 07:10:22 +00:00
$cat_children = $cat -> get_children ( $newdepth , $arraytype );
foreach ( $cat_children as $id => $cat_child ) {
2007-05-17 09:04:52 +00:00
$children_array [ $cat_child -> get_sortorder ()] = new grade_category ( $cat_child , false );
2007-05-03 07:10:22 +00:00
}
}
} else {
if ( $arraytype == 'nested' ) {
2007-05-17 09:04:52 +00:00
$children_array [ $cat -> get_sortorder ()] = array ( 'object' => $cat );
2007-05-03 07:10:22 +00:00
} else {
2007-05-17 09:04:52 +00:00
$children_array [ $cat -> get_sortorder ()] = $cat ;
2007-05-03 07:10:22 +00:00
}
}
2007-05-02 07:20:13 +00:00
}
2007-05-03 07:10:22 +00:00
}
} else {
return null ;
}
return $children_array ;
}
2007-05-17 09:04:52 +00:00
2007-05-03 07:10:22 +00:00
/**
* Check the type of the first child of this category , to see whether it is a
* grade_category or a grade_item , and returns that type as a string ( get_class ) .
* @ return string
*/
function get_childrentype () {
if ( empty ( $this -> children )) {
$count_item_children = count_records ( 'grade_items' , 'categoryid' , $this -> id );
$count_cat_children = count_records ( 'grade_categories' , 'parent' , $this -> id );
2007-05-07 07:33:11 +00:00
2007-05-03 07:10:22 +00:00
if ( $count_item_children > 0 ) {
return 'grade_item' ;
} elseif ( $count_cat_children > 0 ) {
return 'grade_category' ;
} else {
return null ;
2007-05-02 07:20:13 +00:00
}
}
2007-05-24 02:08:20 +00:00
reset ( $this -> children );
return get_class ( current ( $this -> children ));
2007-05-02 07:20:13 +00:00
}
2007-05-07 07:33:11 +00:00
/**
2007-05-21 05:54:12 +00:00
* Uses get_grade_item to load or create a grade_item , then saves it as $this -> grade_item .
2007-05-07 07:33:11 +00:00
* @ return object Grade_item
*/
function load_grade_item () {
2007-05-21 05:54:12 +00:00
$this -> grade_item = $this -> get_grade_item ();
return $this -> grade_item ;
}
/**
* Retrieves from DB and instantiates the associated grade_item object .
* If no grade_item exists yet , create one .
* @ return object Grade_item
*/
function get_grade_item () {
2007-05-28 08:00:19 +00:00
if ( empty ( $this -> id )) {
debugging ( " Attempt to obtain a grade_category's associated grade_item without the category's ID being set. " );
return false ;
}
2007-05-10 08:08:43 +00:00
$grade_items = get_records_select ( 'grade_items' , " iteminstance = $this->id AND itemtype = 'category' " , null , '*' , 0 , 1 );
2007-05-11 02:43:46 +00:00
if ( $grade_items ){
$params = current ( $grade_items );
2007-05-21 05:54:12 +00:00
$grade_item = new grade_item ( $params );
2007-05-11 02:43:46 +00:00
} else {
2007-05-21 05:54:12 +00:00
$grade_item = new grade_item ();
2007-05-11 02:43:46 +00:00
}
2007-05-08 08:01:55 +00:00
2007-05-10 08:08:43 +00:00
// If the associated grade_item isn't yet created, do it now. But first try loading it, in case it exists in DB.
2007-05-21 05:54:12 +00:00
if ( empty ( $grade_item -> id )) {
$grade_item -> iteminstance = $this -> id ;
2007-05-30 03:09:38 +00:00
$grade_item -> courseid = $this -> courseid ;
2007-05-21 05:54:12 +00:00
$grade_item -> itemtype = 'category' ;
$grade_item -> insert ();
$grade_item -> update_from_db ();
2007-05-08 08:01:55 +00:00
}
2007-05-21 05:54:12 +00:00
return $grade_item ;
2007-05-07 07:33:11 +00:00
}
2007-05-10 02:34:01 +00:00
/**
* Uses $this -> parent to instantiate $this -> parent_category based on the
* referenced record in the DB .
* @ return object Parent_category
*/
function load_parent_category () {
if ( empty ( $this -> parent_category ) && ! empty ( $this -> parent )) {
2007-05-21 05:54:12 +00:00
$this -> parent_category = $this -> get_parent_category ();
2007-05-10 02:34:01 +00:00
}
return $this -> parent_category ;
2007-05-17 09:04:52 +00:00
}
2007-05-21 05:54:12 +00:00
/**
* Uses $this -> parent to instantiate and return a grade_category object .
* @ return object Parent_category
*/
function get_parent_category () {
if ( ! empty ( $this -> parent )) {
$parent_category = new grade_category ( array ( 'id' => $this -> parent ));
return $parent_category ;
} else {
return null ;
}
}
2007-05-18 08:05:53 +00:00
/**
2007-05-28 08:00:19 +00:00
* Sets this category as the parent for the given children . If the category 's courseid isn' t set , it uses that of the children items .
2007-05-18 08:05:53 +00:00
* A number of constraints are necessary :
* - The children must all be of the same type and at the same level
* - The children cannot already be top categories
2007-05-28 08:00:19 +00:00
* - The children cannot already have a top categorya
* - The children all belong to the same course
2007-05-18 08:05:53 +00:00
* @ param array $children An array of fully instantiated grade_category OR grade_item objects
* @ return boolean Success or Failure
*/
function set_as_parent ( $children ) {
global $CFG ;
// Check type and sortorder of first child
$first_child = current ( $children );
$first_child_type = get_class ( $first_child );
2007-05-28 08:00:19 +00:00
$first_child_courseid = $first_child -> courseid ;
2007-05-18 08:05:53 +00:00
foreach ( $children as $child ) {
if ( get_class ( $child ) != $first_child_type ) {
debugging ( " Violated constraint: Attempted to set a category as a parent over children of 2 different types. " );
return false ;
}
2007-05-28 01:26:58 +00:00
$grade_tree = new grade_tree ();
if ( $grade_tree -> get_element_type ( $child ) == 'topcat' ) {
2007-05-18 08:05:53 +00:00
debugging ( " Violated constraint: Attempted to set a category over children which are already top categories. " );
return false ;
}
if ( $first_child_type == 'grade_item' ) {
$child -> load_category ();
if ( ! empty ( $child -> category -> parent )) {
debugging ( " Violated constraint: Attempted to set a category over children that already have a top category. " );
return false ;
}
} elseif ( $first_child_type == 'grade_category' ) {
if ( ! empty ( $child -> parent )) {
debugging ( " Violated constraint: Attempted to set a category over children that already have a top category. " );
return false ;
}
} else {
debugging ( " Attempted to set a category over children that are neither grade_items nor grade_categories. " );
return false ;
}
2007-05-28 08:00:19 +00:00
if ( $child -> courseid != $first_child_courseid ) {
debugging ( " Attempted to set a category over children which do not belong to the same course. " );
return false ;
}
2007-05-18 08:05:53 +00:00
}
// We passed all the checks, time to set the category as a parent.
foreach ( $children as $child ) {
if ( $first_child_type == 'grade_item' ) {
$child -> categoryid = $this -> id ;
if ( ! $child -> update ()) {
debugging ( " Could not set this category as a parent for one of its child grade_items, DB operation failed. " );
return false ;
}
} elseif ( $first_child_type == 'grade_category' ) {
$child -> parent = $this -> id ;
if ( ! $child -> update ()) {
debugging ( " Could not set this category as a parent for one of its child categories, DB operation failed. " );
return false ;
}
}
}
// TODO Assign correct sortorders to the newly assigned children and parent. Simply add 1 to all of them!
$this -> load_grade_item ();
$this -> grade_item -> sortorder = $first_child -> get_sortorder ();
2007-05-28 08:00:19 +00:00
// If this->courseid is not set, set it to the first child's courseid
if ( empty ( $this -> courseid )) {
$this -> courseid = $first_child_courseid ;
}
2007-05-18 08:05:53 +00:00
if ( ! $this -> update ()) {
debugging ( " Could not update this category's sortorder in DB. " );
return false ;
}
2007-05-21 02:22:12 +00:00
$query = " UPDATE { $CFG -> prefix } grade_items SET sortorder = sortorder + 1 WHERE sortorder >= { $this -> grade_item -> sortorder } " ;
2007-05-18 08:05:53 +00:00
if ( ! execute_sql ( $query )) {
debugging ( " Could not update the sortorder of grade_items listed after this category. " );
2007-05-21 02:22:12 +00:00
return false ;
2007-05-18 08:05:53 +00:00
} else {
return true ;
}
}
2007-05-24 08:50:01 +00:00
/**
* Returns the most descriptive field for this object . This is a standard method used
* when we do not know the exact type of an object .
* @ return string name
*/
function get_name () {
return $this -> fullname ;
}
2007-05-28 08:00:19 +00:00
/**
* Returns this category 's grade_item' s id . This is specified for cases where we do not
* know an object 's type, and want to get either an item' s id or a category 's item' s id .
*
* @ return int
*/
function get_item_id () {
$this -> load_grade_item ();
return $this -> grade_item -> id ;
}
2007-05-31 03:15:43 +00:00
/**
* Returns this category ' s parent id . A generic method shared by objects that have a parent id of some kind .
* @ return id $parentid
*/
function get_parent_id () {
return $this -> parent ;
}
/**
* Sets this category ' s parent id . A generic method shared by objects that have a parent id of some kind .
* @ param id $parentid
*/
function set_parent_id ( $parentid ) {
2007-05-31 08:57:05 +00:00
if ( $this -> parent != $parentid ) {
$this -> old_parent = $this -> get_parent_category ();
}
2007-05-31 03:15:43 +00:00
$this -> parent = $parentid ;
2007-05-31 08:57:05 +00:00
$this -> path = grade_category :: build_path ( $this );
$this -> depth = $this -> get_depth_from_path ();
2007-05-31 03:15:43 +00:00
}
/**
* Returns the sortorder of the associated grade_item . This method is also available in
* grade_item , for cases where the object type is not know . It will act as a virtual
* variable for a grade_category .
* @ return int Sort order
*/
function get_sortorder () {
if ( empty ( $this -> sortorder )) {
$this -> load_grade_item ();
if ( ! empty ( $this -> grade_item )) {
return $this -> grade_item -> sortorder ;
}
} else {
return $this -> sortorder ;
}
}
/**
* Sets a temporary sortorder variable for this category . It is used in the update () method to update the grade_item .
* This method is also available in grade_item , for cases where the object type is not know .
* @ param int $sortorder
* @ return void
*/
function set_sortorder ( $sortorder ) {
$this -> sortorder = $sortorder ;
}
2007-05-31 08:57:05 +00:00
/**
* If the old parent is set ( after an update ), this checks and returns whether it has any children . Important for
* deleting childless categories .
* @ return boolean
*/
function is_old_parent_childless () {
if ( ! empty ( $this -> old_parent )) {
return ! $this -> old_parent -> has_children ();
} else {
return false ;
}
}
2007-05-17 02:22:32 +00:00
}
2007-04-27 01:17:02 +00:00
?>