Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/grade/grade_grade.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00026 defined('MOODLE_INTERNAL') || die();
00027 
00028 require_once('grade_object.php');
00029 
00030 class grade_grade extends grade_object {
00031 
00036     public $table = 'grade_grades';
00037 
00042     public $required_fields = array('id', 'itemid', 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
00043                                  'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked',
00044                                  'locktime', 'exported', 'overridden', 'excluded', 'timecreated', 'timemodified');
00045 
00050     public $optional_fields = array('feedback'=>null, 'feedbackformat'=>0, 'information'=>null, 'informationformat'=>0);
00051 
00056     public $itemid;
00057 
00062     public $grade_item;
00063 
00068     public $userid;
00069 
00074     public $rawgrade;
00075 
00080     public $rawgrademax = 100;
00081 
00086     public $rawgrademin = 0;
00087 
00092     public $rawscaleid;
00093 
00098     public $usermodified;
00099 
00104     public $finalgrade;
00105 
00110     public $hidden = 0;
00111 
00116     public $locked = 0;
00117 
00122     public $locktime = 0;
00123 
00128     public $exported = 0;
00129 
00134     public $overridden = 0;
00135 
00140     public $excluded = 0;
00141 
00146     public $timecreated = null;
00147 
00152     public $timemodified = null;
00153 
00154 
00162     public static function fetch_users_grades($grade_item, $userids, $include_missing=true) {
00163         global $DB;
00164 
00165         // hmm, there might be a problem with length of sql query
00166         // if there are too many users requested - we might run out of memory anyway
00167         $limit = 2000;
00168         $count = count($userids);
00169         if ($count > $limit) {
00170             $half = (int)($count/2);
00171             $first  = array_slice($userids, 0, $half);
00172             $second = array_slice($userids, $half);
00173             return grade_grade::fetch_users_grades($grade_item, $first, $include_missing) + grade_grade::fetch_users_grades($grade_item, $second, $include_missing);
00174         }
00175 
00176         list($user_ids_cvs, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uid0');
00177         $params['giid'] = $grade_item->id;
00178         $result = array();
00179         if ($grade_records = $DB->get_records_select('grade_grades', "itemid=:giid AND userid $user_ids_cvs", $params)) {
00180             foreach ($grade_records as $record) {
00181                 $result[$record->userid] = new grade_grade($record, false);
00182             }
00183         }
00184         if ($include_missing) {
00185             foreach ($userids as $userid) {
00186                 if (!array_key_exists($userid, $result)) {
00187                     $grade_grade = new grade_grade();
00188                     $grade_grade->userid = $userid;
00189                     $grade_grade->itemid = $grade_item->id;
00190                     $result[$userid] = $grade_grade;
00191                 }
00192             }
00193         }
00194 
00195         return $result;
00196     }
00197 
00202     public function load_grade_item() {
00203         if (empty($this->itemid)) {
00204             debugging('Missing itemid');
00205             $this->grade_item = null;
00206             return null;
00207         }
00208 
00209         if (empty($this->grade_item)) {
00210             $this->grade_item = grade_item::fetch(array('id'=>$this->itemid));
00211 
00212         } else if ($this->grade_item->id != $this->itemid) {
00213             debugging('Itemid mismatch');
00214             $this->grade_item = grade_item::fetch(array('id'=>$this->itemid));
00215         }
00216 
00217         return $this->grade_item;
00218     }
00219 
00224     public function is_editable() {
00225         if ($this->is_locked()) {
00226             return false;
00227         }
00228 
00229         $grade_item = $this->load_grade_item();
00230 
00231         if ($grade_item->gradetype == GRADE_TYPE_NONE) {
00232             return false;
00233         }
00234 
00235         return true;
00236     }
00237 
00245     public function is_locked() {
00246         $this->load_grade_item();
00247         if (empty($this->grade_item)) {
00248             return !empty($this->locked);
00249         } else {
00250             return !empty($this->locked) or $this->grade_item->is_locked();
00251         }
00252     }
00253 
00258     public function is_overridden() {
00259         return !empty($this->overridden);
00260     }
00261 
00267     public function get_datesubmitted() {
00268         //TODO: HACK - create new fields in 2.0
00269         return $this->timecreated;
00270     }
00271 
00277     public function get_dategraded() {
00278         //TODO: HACK - create new fields in 2.0
00279         if (is_null($this->finalgrade) and is_null($this->feedback)) {
00280             return null; // no grade == no date
00281         } else if ($this->overridden) {
00282             return $this->overridden;
00283         } else {
00284             return $this->timemodified;
00285         }
00286     }
00287 
00294     public function set_overridden($state, $refresh = true) {
00295         if (empty($this->overridden) and $state) {
00296             $this->overridden = time();
00297             $this->update();
00298             return true;
00299 
00300         } else if (!empty($this->overridden) and !$state) {
00301             $this->overridden = 0;
00302             $this->update();
00303 
00304             if ($refresh) {
00305                 //refresh when unlocking
00306                 $this->grade_item->refresh_grades($this->userid);
00307             }
00308 
00309             return true;
00310         }
00311         return false;
00312     }
00313 
00318     public function is_excluded() {
00319         return !empty($this->excluded);
00320     }
00321 
00327     public function set_excluded($state) {
00328         if (empty($this->excluded) and $state) {
00329             $this->excluded = time();
00330             $this->update();
00331             return true;
00332 
00333         } else if (!empty($this->excluded) and !$state) {
00334             $this->excluded = 0;
00335             $this->update();
00336             return true;
00337         }
00338         return false;
00339     }
00340 
00349     public function set_locked($lockedstate, $cascade=false, $refresh=true) {
00350         $this->load_grade_item();
00351 
00352         if ($lockedstate) {
00353             if ($this->grade_item->needsupdate) {
00354                 //can not lock grade if final not calculated!
00355                 return false;
00356             }
00357 
00358             $this->locked = time();
00359             $this->update();
00360 
00361             return true;
00362 
00363         } else {
00364             if (!empty($this->locked) and $this->locktime < time()) {
00365                 //we have to reset locktime or else it would lock up again
00366                 $this->locktime = 0;
00367             }
00368 
00369             // remove the locked flag
00370             $this->locked = 0;
00371             $this->update();
00372 
00373             if ($refresh and !$this->is_overridden()) {
00374                 //refresh when unlocking and not overridden
00375                 $this->grade_item->refresh_grades($this->userid);
00376             }
00377 
00378             return true;
00379         }
00380     }
00381 
00387     public function check_locktime_all($items) {
00388         global $CFG, $DB;
00389 
00390         $now = time(); // no rounding needed, this is not supposed to be called every 10 seconds
00391         list($usql, $params) = $DB->get_in_or_equal($items);
00392         $params[] = $now;
00393         $rs = $DB->get_recordset_select('grade_grades', "itemid $usql AND locked = 0 AND locktime > 0 AND locktime < ?", $params);
00394         foreach ($rs as $grade) {
00395             $grade_grade = new grade_grade($grade, false);
00396             $grade_grade->locked = time();
00397             $grade_grade->update('locktime');
00398         }
00399         $rs->close();
00400     }
00401 
00408     public function set_locktime($locktime) {
00409         $this->locktime = $locktime;
00410         $this->update();
00411     }
00412 
00418     public function get_locktime() {
00419         $this->load_grade_item();
00420 
00421         $item_locktime = $this->grade_item->get_locktime();
00422 
00423         if (empty($this->locktime) or ($item_locktime and $item_locktime < $this->locktime)) {
00424             return $item_locktime;
00425 
00426         } else {
00427             return $this->locktime;
00428         }
00429     }
00430 
00435     public function is_hidden() {
00436         $this->load_grade_item();
00437         if (empty($this->grade_item)) {
00438             return $this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time());
00439         } else {
00440             return $this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()) or $this->grade_item->is_hidden();
00441         }
00442     }
00443 
00448     public function is_hiddenuntil() {
00449         $this->load_grade_item();
00450 
00451         if ($this->hidden == 1 or $this->grade_item->hidden == 1) {
00452             return false; //always hidden
00453         }
00454 
00455         if ($this->hidden > 1 or $this->grade_item->hidden > 1) {
00456             return true;
00457         }
00458 
00459         return false;
00460     }
00461 
00466     public function get_hidden() {
00467         $this->load_grade_item();
00468 
00469         $item_hidden = $this->grade_item->get_hidden();
00470 
00471         if ($item_hidden == 1) {
00472             return 1;
00473 
00474         } else if ($item_hidden == 0) {
00475             return $this->hidden;
00476 
00477         } else {
00478             if ($this->hidden == 0) {
00479                 return $item_hidden;
00480             } else if ($this->hidden == 1) {
00481                 return 1;
00482             } else if ($this->hidden > $item_hidden) {
00483                 return $this->hidden;
00484             } else {
00485                 return $item_hidden;
00486             }
00487         }
00488     }
00489 
00495     public function set_hidden($hidden, $cascade=false) {
00496        $this->hidden = $hidden;
00497        $this->update();
00498     }
00499 
00507     public static function fetch($params) {
00508         return grade_object::fetch_helper('grade_grades', 'grade_grade', $params);
00509     }
00510 
00518     public static function fetch_all($params) {
00519         return grade_object::fetch_all_helper('grade_grades', 'grade_grade', $params);
00520     }
00521 
00535     public static function standardise_score($rawgrade, $source_min, $source_max, $target_min, $target_max) {
00536         if (is_null($rawgrade)) {
00537           return null;
00538         }
00539 
00540         if ($source_max == $source_min or $target_min == $target_max) {
00541             // prevent division by 0
00542             return $target_max;
00543         }
00544 
00545         $factor = ($rawgrade - $source_min) / ($source_max - $source_min);
00546         $diff = $target_max - $target_min;
00547         $standardised_value = $factor * $diff + $target_min;
00548         return $standardised_value;
00549     }
00550 
00561     public static function get_hiding_affected(&$grade_grades, &$grade_items) {
00562         global $CFG;
00563 
00564         if (count($grade_grades) !== count($grade_items)) {
00565             print_error('invalidarraysize', 'debug', '', 'grade_grade::get_hiding_affected()!');
00566         }
00567 
00568         $dependson = array();
00569         $todo = array();
00570         $unknown = array();  // can not find altered
00571         $altered = array();  // altered grades
00572 
00573         $hiddenfound = false;
00574         foreach($grade_grades as $itemid=>$unused) {
00575             $grade_grade =& $grade_grades[$itemid];
00576             if ($grade_grade->is_excluded()) {
00577                 //nothing to do, aggregation is ok
00578             } else if ($grade_grade->is_hidden()) {
00579                 $hiddenfound = true;
00580                 $altered[$grade_grade->itemid] = null;
00581             } else if ($grade_grade->is_locked() or $grade_grade->is_overridden()) {
00582                 // no need to recalculate locked or overridden grades
00583             } else {
00584                 $dependson[$grade_grade->itemid] = $grade_items[$grade_grade->itemid]->depends_on();
00585                 if (!empty($dependson[$grade_grade->itemid])) {
00586                     $todo[] = $grade_grade->itemid;
00587                 }
00588             }
00589         }
00590         if (!$hiddenfound) {
00591             return array('unknown'=>array(), 'altered'=>array());
00592         }
00593 
00594         $max = count($todo);
00595         $hidden_precursors = null;
00596         for($i=0; $i<$max; $i++) {
00597             $found = false;
00598             foreach($todo as $key=>$do) {
00599                 $hidden_precursors = array_intersect($dependson[$do], $unknown);
00600                 if ($hidden_precursors) {
00601                     // this item depends on hidden grade indirectly
00602                     $unknown[$do] = $do;
00603                     unset($todo[$key]);
00604                     $found = true;
00605                     continue;
00606 
00607                 } else if (!array_intersect($dependson[$do], $todo)) {
00608                     $hidden_precursors = array_intersect($dependson[$do], array_keys($altered));
00609                     if (!$hidden_precursors) {
00610                         // hiding does not affect this grade
00611                         unset($todo[$key]);
00612                         $found = true;
00613                         continue;
00614 
00615                     } else {
00616                         // depends on altered grades - we should try to recalculate if possible
00617                         if ($grade_items[$do]->is_calculated() or
00618                             (!$grade_items[$do]->is_category_item() and !$grade_items[$do]->is_course_item())
00619                         ) {
00620                             $unknown[$do] = $do;
00621                             unset($todo[$key]);
00622                             $found = true;
00623                             continue;
00624 
00625                         } else {
00626                             $grade_category = $grade_items[$do]->load_item_category();
00627 
00628                             $values = array();
00629                             foreach ($dependson[$do] as $itemid) {
00630                                 if (array_key_exists($itemid, $altered)) {
00631                                     //nulling an altered precursor
00632                                     $values[$itemid] = $altered[$itemid];
00633                                 } elseif (empty($values[$itemid])) {
00634                                     $values[$itemid] = $grade_grades[$itemid]->finalgrade;
00635                                 }
00636                             }
00637 
00638                             foreach ($values as $itemid=>$value) {
00639                                 if ($grade_grades[$itemid]->is_excluded()) {
00640                                     unset($values[$itemid]);
00641                                     continue;
00642                                 }
00643                                 $values[$itemid] = grade_grade::standardise_score($value, $grade_items[$itemid]->grademin, $grade_items[$itemid]->grademax, 0, 1);
00644                             }
00645 
00646                             if ($grade_category->aggregateonlygraded) {
00647                                 foreach ($values as $itemid=>$value) {
00648                                     if (is_null($value)) {
00649                                         unset($values[$itemid]);
00650                                     }
00651                                 }
00652                             } else {
00653                                 foreach ($values as $itemid=>$value) {
00654                                     if (is_null($value)) {
00655                                         $values[$itemid] = 0;
00656                                     }
00657                                 }
00658                             }
00659 
00660                             // limit and sort
00661                             $grade_category->apply_limit_rules($values, $grade_items);
00662                             asort($values, SORT_NUMERIC);
00663 
00664                             // let's see we have still enough grades to do any statistics
00665                             if (count($values) == 0) {
00666                                 // not enough attempts yet
00667                                 $altered[$do] = null;
00668                                 unset($todo[$key]);
00669                                 $found = true;
00670                                 continue;
00671                             }
00672 
00673                             $agg_grade = $grade_category->aggregate_values($values, $grade_items);
00674 
00675                             // recalculate the rawgrade back to requested range
00676                             $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $grade_items[$do]->grademin, $grade_items[$do]->grademax);
00677 
00678                             $finalgrade = $grade_items[$do]->bounded_grade($finalgrade);
00679 
00680                             $altered[$do] = $finalgrade;
00681                             unset($todo[$key]);
00682                             $found = true;
00683                             continue;
00684                         }
00685                     }
00686                 }
00687             }
00688             if (!$found) {
00689                 break;
00690             }
00691         }
00692 
00693         return array('unknown'=>$unknown, 'altered'=>$altered);
00694     }
00695 
00701     public function is_passed($grade_item = null) {
00702         if (empty($grade_item)) {
00703             if (!isset($this->grade_item)) {
00704                 $this->load_grade_item();
00705             }
00706         } else {
00707             $this->grade_item = $grade_item;
00708             $this->itemid = $grade_item->id;
00709         }
00710 
00711         // Return null if finalgrade is null
00712         if (is_null($this->finalgrade)) {
00713             return null;
00714         }
00715 
00716         // Return null if gradepass == grademin or gradepass is null
00717         if (is_null($this->grade_item->gradepass) || $this->grade_item->gradepass == $this->grade_item->grademin) {
00718             return null;
00719         }
00720 
00721         return $this->finalgrade >= $this->grade_item->gradepass;
00722     }
00723 
00724     public function insert($source=null) {
00725         // TODO: dategraded hack - do not update times, they are used for submission and grading
00726         //$this->timecreated = $this->timemodified = time();
00727         return parent::insert($source);
00728     }
00729 
00736     public function update($source=null) {
00737         $this->rawgrade    = grade_floatval($this->rawgrade);
00738         $this->finalgrade  = grade_floatval($this->finalgrade);
00739         $this->rawgrademin = grade_floatval($this->rawgrademin);
00740         $this->rawgrademax = grade_floatval($this->rawgrademax);
00741         return parent::update($source);
00742     }
00743 
00749     function notify_changed($deleted) {
00750         global $USER, $SESSION, $CFG,$COURSE, $DB;
00751 
00752         // Grades may be cached in user session
00753         if ($USER->id == $this->userid) {
00754             unset($SESSION->gradescorecache[$this->itemid]);
00755         }
00756 
00757         // Ignore during restore
00758         // TODO There should be a proper way to determine when we are in restore
00759         // so that this hack looking for a $restore global is not needed.
00760         global $restore;
00761         if (!empty($restore->backup_unique_code)) {
00762             return;
00763         }
00764 
00765         require_once($CFG->libdir.'/completionlib.php');
00766 
00767         // Bail out immediately if completion is not enabled for site (saves loading
00768         // grade item below)
00769         if (!completion_info::is_enabled_for_site()) {
00770             return;
00771         }
00772 
00773         // Load information about grade item
00774         $this->load_grade_item();
00775 
00776         // Only course-modules have completion data
00777         if ($this->grade_item->itemtype!='mod') {
00778             return;
00779         }
00780 
00781         // Use $COURSE if available otherwise get it via item fields
00782         if(!empty($COURSE) && $COURSE->id == $this->grade_item->courseid) {
00783             $course = $COURSE;
00784         } else {
00785             $course = $DB->get_record('course', array('id'=>$this->grade_item->courseid));
00786         }
00787 
00788         // Bail out if completion is not enabled for course
00789         $completion = new completion_info($course);
00790         if (!$completion->is_enabled()) {
00791             return;
00792         }
00793 
00794         // Get course-module
00795         $cm = get_coursemodule_from_instance($this->grade_item->itemmodule,
00796               $this->grade_item->iteminstance, $this->grade_item->courseid);
00797         // If the course-module doesn't exist, display a warning...
00798         if (!$cm) {
00799             // ...unless the grade is being deleted in which case it's likely
00800             // that the course-module was just deleted too, so that's okay.
00801             if (!$deleted) {
00802                 debugging("Couldn't find course-module for module '" .
00803                         $this->grade_item->itemmodule . "', instance '" .
00804                         $this->grade_item->iteminstance . "', course '" .
00805                         $this->grade_item->courseid . "'");
00806             }
00807             return;
00808         }
00809 
00810         // Pass information on to completion system
00811         $completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted);
00812      }
00813 }
 All Data Structures Namespaces Files Functions Variables Enumerations