|
Moodle
2.2.1
http://www.collinsharper.com
|
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 }