|
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/>. 00017 00027 defined('MOODLE_INTERNAL') || die(); 00028 00030 require_once($CFG->libdir . '/grade/constants.php'); 00031 00032 require_once($CFG->libdir . '/grade/grade_category.php'); 00033 require_once($CFG->libdir . '/grade/grade_item.php'); 00034 require_once($CFG->libdir . '/grade/grade_grade.php'); 00035 require_once($CFG->libdir . '/grade/grade_scale.php'); 00036 require_once($CFG->libdir . '/grade/grade_outcome.php'); 00037 00041 00064 function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades=NULL, $itemdetails=NULL) { 00065 global $USER, $CFG, $DB; 00066 00067 // only following grade_item properties can be changed in this function 00068 $allowed = array('itemname', 'idnumber', 'gradetype', 'grademax', 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted', 'hidden'); 00069 // list of 10,5 numeric fields 00070 $floats = array('grademin', 'grademax', 'multfactor', 'plusfactor'); 00071 00072 // grade item identification 00073 $params = compact('courseid', 'itemtype', 'itemmodule', 'iteminstance', 'itemnumber'); 00074 00075 if (is_null($courseid) or is_null($itemtype)) { 00076 debugging('Missing courseid or itemtype'); 00077 return GRADE_UPDATE_FAILED; 00078 } 00079 00080 if (!$grade_items = grade_item::fetch_all($params)) { 00081 // create a new one 00082 $grade_item = false; 00083 00084 } else if (count($grade_items) == 1){ 00085 $grade_item = reset($grade_items); 00086 unset($grade_items); //release memory 00087 00088 } else { 00089 debugging('Found more than one grade item'); 00090 return GRADE_UPDATE_MULTIPLE; 00091 } 00092 00093 if (!empty($itemdetails['deleted'])) { 00094 if ($grade_item) { 00095 if ($grade_item->delete($source)) { 00096 return GRADE_UPDATE_OK; 00097 } else { 00098 return GRADE_UPDATE_FAILED; 00099 } 00100 } 00101 return GRADE_UPDATE_OK; 00102 } 00103 00105 00106 if (!$grade_item) { 00107 if ($itemdetails) { 00108 $itemdetails = (array)$itemdetails; 00109 00110 // grademin and grademax ignored when scale specified 00111 if (array_key_exists('scaleid', $itemdetails)) { 00112 if ($itemdetails['scaleid']) { 00113 unset($itemdetails['grademin']); 00114 unset($itemdetails['grademax']); 00115 } 00116 } 00117 00118 foreach ($itemdetails as $k=>$v) { 00119 if (!in_array($k, $allowed)) { 00120 // ignore it 00121 continue; 00122 } 00123 if ($k == 'gradetype' and $v == GRADE_TYPE_NONE) { 00124 // no grade item needed! 00125 return GRADE_UPDATE_OK; 00126 } 00127 $params[$k] = $v; 00128 } 00129 } 00130 $grade_item = new grade_item($params); 00131 $grade_item->insert(); 00132 00133 } else { 00134 if ($grade_item->is_locked()) { 00135 // no notice() here, test returned value instead! 00136 return GRADE_UPDATE_ITEM_LOCKED; 00137 } 00138 00139 if ($itemdetails) { 00140 $itemdetails = (array)$itemdetails; 00141 $update = false; 00142 foreach ($itemdetails as $k=>$v) { 00143 if (!in_array($k, $allowed)) { 00144 // ignore it 00145 continue; 00146 } 00147 if (in_array($k, $floats)) { 00148 if (grade_floats_different($grade_item->{$k}, $v)) { 00149 $grade_item->{$k} = $v; 00150 $update = true; 00151 } 00152 00153 } else { 00154 if ($grade_item->{$k} != $v) { 00155 $grade_item->{$k} = $v; 00156 $update = true; 00157 } 00158 } 00159 } 00160 if ($update) { 00161 $grade_item->update(); 00162 } 00163 } 00164 } 00165 00167 if (!empty($itemdetails['reset'])) { 00168 $grade_item->delete_all_grades('reset'); 00169 return GRADE_UPDATE_OK; 00170 } 00171 00173 // do we use grading? 00174 if ($grade_item->gradetype == GRADE_TYPE_NONE) { 00175 return GRADE_UPDATE_OK; 00176 } 00177 00178 // no grade submitted 00179 if (empty($grades)) { 00180 return GRADE_UPDATE_OK; 00181 } 00182 00184 if (is_object($grades)) { 00185 $grades = array($grades->userid=>$grades); 00186 } else { 00187 if (array_key_exists('userid', $grades)) { 00188 $grades = array($grades['userid']=>$grades); 00189 } 00190 } 00191 00193 foreach($grades as $k=>$g) { 00194 if (!is_array($g)) { 00195 $g = (array)$g; 00196 $grades[$k] = $g; 00197 } 00198 00199 if (empty($g['userid']) or $k != $g['userid']) { 00200 debugging('Incorrect grade array index, must be user id! Grade ignored.'); 00201 unset($grades[$k]); 00202 } 00203 } 00204 00205 if (empty($grades)) { 00206 return GRADE_UPDATE_FAILED; 00207 } 00208 00209 $count = count($grades); 00210 if ($count > 0 and $count < 200) { 00211 list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start='uid'); 00212 $params['gid'] = $grade_item->id; 00213 $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid AND userid $uids"; 00214 00215 } else { 00216 $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid"; 00217 $params = array('gid'=>$grade_item->id); 00218 } 00219 00220 $rs = $DB->get_recordset_sql($sql, $params); 00221 00222 $failed = false; 00223 00224 while (count($grades) > 0) { 00225 $grade_grade = null; 00226 $grade = null; 00227 00228 foreach ($rs as $gd) { 00229 00230 $userid = $gd->userid; 00231 if (!isset($grades[$userid])) { 00232 // this grade not requested, continue 00233 continue; 00234 } 00235 // existing grade requested 00236 $grade = $grades[$userid]; 00237 $grade_grade = new grade_grade($gd, false); 00238 unset($grades[$userid]); 00239 break; 00240 } 00241 00242 if (is_null($grade_grade)) { 00243 if (count($grades) == 0) { 00244 // no more grades to process 00245 break; 00246 } 00247 00248 $grade = reset($grades); 00249 $userid = $grade['userid']; 00250 $grade_grade = new grade_grade(array('itemid'=>$grade_item->id, 'userid'=>$userid), false); 00251 $grade_grade->load_optional_fields(); // add feedback and info too 00252 unset($grades[$userid]); 00253 } 00254 00255 $rawgrade = false; 00256 $feedback = false; 00257 $feedbackformat = FORMAT_MOODLE; 00258 $usermodified = $USER->id; 00259 $datesubmitted = null; 00260 $dategraded = null; 00261 00262 if (array_key_exists('rawgrade', $grade)) { 00263 $rawgrade = $grade['rawgrade']; 00264 } 00265 00266 if (array_key_exists('feedback', $grade)) { 00267 $feedback = $grade['feedback']; 00268 } 00269 00270 if (array_key_exists('feedbackformat', $grade)) { 00271 $feedbackformat = $grade['feedbackformat']; 00272 } 00273 00274 if (array_key_exists('usermodified', $grade)) { 00275 $usermodified = $grade['usermodified']; 00276 } 00277 00278 if (array_key_exists('datesubmitted', $grade)) { 00279 $datesubmitted = $grade['datesubmitted']; 00280 } 00281 00282 if (array_key_exists('dategraded', $grade)) { 00283 $dategraded = $grade['dategraded']; 00284 } 00285 00286 // update or insert the grade 00287 if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified, $dategraded, $datesubmitted, $grade_grade)) { 00288 $failed = true; 00289 } 00290 } 00291 00292 if ($rs) { 00293 $rs->close(); 00294 } 00295 00296 if (!$failed) { 00297 return GRADE_UPDATE_OK; 00298 } else { 00299 return GRADE_UPDATE_FAILED; 00300 } 00301 } 00302 00317 function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $iteminstance, $userid, $data) { 00318 if ($items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) { 00319 $result = true; 00320 foreach ($items as $item) { 00321 if (!array_key_exists($item->itemnumber, $data)) { 00322 continue; 00323 } 00324 $grade = $data[$item->itemnumber] < 1 ? null : $data[$item->itemnumber]; 00325 $result = ($item->update_final_grade($userid, $grade, $source) && $result); 00326 } 00327 return $result; 00328 } 00329 return false; //grade items not found 00330 } 00331 00345 function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) { 00346 global $CFG; 00347 00348 $return = new stdClass(); 00349 $return->items = array(); 00350 $return->outcomes = array(); 00351 00352 $course_item = grade_item::fetch_course_item($courseid); 00353 $needsupdate = array(); 00354 if ($course_item->needsupdate) { 00355 $result = grade_regrade_final_grades($courseid); 00356 if ($result !== true) { 00357 $needsupdate = array_keys($result); 00358 } 00359 } 00360 00361 if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) { 00362 foreach ($grade_items as $grade_item) { 00363 $decimalpoints = null; 00364 00365 if (empty($grade_item->outcomeid)) { 00366 // prepare information about grade item 00367 $item = new stdClass(); 00368 $item->itemnumber = $grade_item->itemnumber; 00369 $item->scaleid = $grade_item->scaleid; 00370 $item->name = $grade_item->get_name(); 00371 $item->grademin = $grade_item->grademin; 00372 $item->grademax = $grade_item->grademax; 00373 $item->gradepass = $grade_item->gradepass; 00374 $item->locked = $grade_item->is_locked(); 00375 $item->hidden = $grade_item->is_hidden(); 00376 $item->grades = array(); 00377 00378 switch ($grade_item->gradetype) { 00379 case GRADE_TYPE_NONE: 00380 continue; 00381 00382 case GRADE_TYPE_VALUE: 00383 $item->scaleid = 0; 00384 break; 00385 00386 case GRADE_TYPE_TEXT: 00387 $item->scaleid = 0; 00388 $item->grademin = 0; 00389 $item->grademax = 0; 00390 $item->gradepass = 0; 00391 break; 00392 } 00393 00394 if (empty($userid_or_ids)) { 00395 $userids = array(); 00396 00397 } else if (is_array($userid_or_ids)) { 00398 $userids = $userid_or_ids; 00399 00400 } else { 00401 $userids = array($userid_or_ids); 00402 } 00403 00404 if ($userids) { 00405 $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true); 00406 foreach ($userids as $userid) { 00407 $grade_grades[$userid]->grade_item =& $grade_item; 00408 00409 $grade = new stdClass(); 00410 $grade->grade = $grade_grades[$userid]->finalgrade; 00411 $grade->locked = $grade_grades[$userid]->is_locked(); 00412 $grade->hidden = $grade_grades[$userid]->is_hidden(); 00413 $grade->overridden = $grade_grades[$userid]->overridden; 00414 $grade->feedback = $grade_grades[$userid]->feedback; 00415 $grade->feedbackformat = $grade_grades[$userid]->feedbackformat; 00416 $grade->usermodified = $grade_grades[$userid]->usermodified; 00417 $grade->datesubmitted = $grade_grades[$userid]->get_datesubmitted(); 00418 $grade->dategraded = $grade_grades[$userid]->get_dategraded(); 00419 00420 // create text representation of grade 00421 if ($grade_item->gradetype == GRADE_TYPE_TEXT or $grade_item->gradetype == GRADE_TYPE_NONE) { 00422 $grade->grade = null; 00423 $grade->str_grade = '-'; 00424 $grade->str_long_grade = $grade->str_grade; 00425 00426 } else if (in_array($grade_item->id, $needsupdate)) { 00427 $grade->grade = false; 00428 $grade->str_grade = get_string('error'); 00429 $grade->str_long_grade = $grade->str_grade; 00430 00431 } else if (is_null($grade->grade)) { 00432 $grade->str_grade = '-'; 00433 $grade->str_long_grade = $grade->str_grade; 00434 00435 } else { 00436 $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item); 00437 if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) { 00438 $grade->str_long_grade = $grade->str_grade; 00439 } else { 00440 $a = new stdClass(); 00441 $a->grade = $grade->str_grade; 00442 $a->max = grade_format_gradevalue($grade_item->grademax, $grade_item); 00443 $grade->str_long_grade = get_string('gradelong', 'grades', $a); 00444 } 00445 } 00446 00447 // create html representation of feedback 00448 if (is_null($grade->feedback)) { 00449 $grade->str_feedback = ''; 00450 } else { 00451 $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); 00452 } 00453 00454 $item->grades[$userid] = $grade; 00455 } 00456 } 00457 $return->items[$grade_item->itemnumber] = $item; 00458 00459 } else { 00460 if (!$grade_outcome = grade_outcome::fetch(array('id'=>$grade_item->outcomeid))) { 00461 debugging('Incorect outcomeid found'); 00462 continue; 00463 } 00464 00465 // outcome info 00466 $outcome = new stdClass(); 00467 $outcome->itemnumber = $grade_item->itemnumber; 00468 $outcome->scaleid = $grade_outcome->scaleid; 00469 $outcome->name = $grade_outcome->get_name(); 00470 $outcome->locked = $grade_item->is_locked(); 00471 $outcome->hidden = $grade_item->is_hidden(); 00472 00473 if (empty($userid_or_ids)) { 00474 $userids = array(); 00475 } else if (is_array($userid_or_ids)) { 00476 $userids = $userid_or_ids; 00477 } else { 00478 $userids = array($userid_or_ids); 00479 } 00480 00481 if ($userids) { 00482 $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true); 00483 foreach ($userids as $userid) { 00484 $grade_grades[$userid]->grade_item =& $grade_item; 00485 00486 $grade = new stdClass(); 00487 $grade->grade = $grade_grades[$userid]->finalgrade; 00488 $grade->locked = $grade_grades[$userid]->is_locked(); 00489 $grade->hidden = $grade_grades[$userid]->is_hidden(); 00490 $grade->feedback = $grade_grades[$userid]->feedback; 00491 $grade->feedbackformat = $grade_grades[$userid]->feedbackformat; 00492 $grade->usermodified = $grade_grades[$userid]->usermodified; 00493 00494 // create text representation of grade 00495 if (in_array($grade_item->id, $needsupdate)) { 00496 $grade->grade = false; 00497 $grade->str_grade = get_string('error'); 00498 00499 } else if (is_null($grade->grade)) { 00500 $grade->grade = 0; 00501 $grade->str_grade = get_string('nooutcome', 'grades'); 00502 00503 } else { 00504 $grade->grade = (int)$grade->grade; 00505 $scale = $grade_item->load_scale(); 00506 $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]); 00507 } 00508 00509 // create html representation of feedback 00510 if (is_null($grade->feedback)) { 00511 $grade->str_feedback = ''; 00512 } else { 00513 $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); 00514 } 00515 00516 $outcome->grades[$userid] = $grade; 00517 } 00518 } 00519 00520 if (isset($return->outcomes[$grade_item->itemnumber])) { 00521 // itemnumber duplicates - lets fix them! 00522 $newnumber = $grade_item->itemnumber + 1; 00523 while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) { 00524 $newnumber++; 00525 } 00526 $outcome->itemnumber = $newnumber; 00527 $grade_item->itemnumber = $newnumber; 00528 $grade_item->update('system'); 00529 } 00530 00531 $return->outcomes[$grade_item->itemnumber] = $outcome; 00532 00533 } 00534 } 00535 } 00536 00537 // sort results using itemnumbers 00538 ksort($return->items, SORT_NUMERIC); 00539 ksort($return->outcomes, SORT_NUMERIC); 00540 00541 return $return; 00542 } 00543 00547 00548 00549 00553 00564 function grade_get_setting($courseid, $name, $default=null, $resetcache=false) { 00565 global $DB; 00566 00567 static $cache = array(); 00568 00569 if ($resetcache or !array_key_exists($courseid, $cache)) { 00570 $cache[$courseid] = array(); 00571 00572 } else if (is_null($name)) { 00573 return null; 00574 00575 } else if (array_key_exists($name, $cache[$courseid])) { 00576 return $cache[$courseid][$name]; 00577 } 00578 00579 if (!$data = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) { 00580 $result = null; 00581 } else { 00582 $result = $data->value; 00583 } 00584 00585 if (is_null($result)) { 00586 $result = $default; 00587 } 00588 00589 $cache[$courseid][$name] = $result; 00590 return $result; 00591 } 00592 00600 function grade_get_settings($courseid) { 00601 global $DB; 00602 00603 $settings = new stdClass(); 00604 $settings->id = $courseid; 00605 00606 if ($records = $DB->get_records('grade_settings', array('courseid'=>$courseid))) { 00607 foreach ($records as $record) { 00608 $settings->{$record->name} = $record->value; 00609 } 00610 } 00611 00612 return $settings; 00613 } 00614 00624 function grade_set_setting($courseid, $name, $value) { 00625 global $DB; 00626 00627 if (is_null($value)) { 00628 $DB->delete_records('grade_settings', array('courseid'=>$courseid, 'name'=>$name)); 00629 00630 } else if (!$existing = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) { 00631 $data = new stdClass(); 00632 $data->courseid = $courseid; 00633 $data->name = $name; 00634 $data->value = $value; 00635 $DB->insert_record('grade_settings', $data); 00636 00637 } else { 00638 $data = new stdClass(); 00639 $data->id = $existing->id; 00640 $data->value = $value; 00641 $DB->update_record('grade_settings', $data); 00642 } 00643 00644 grade_get_setting($courseid, null, null, true); // reset the cache 00645 } 00646 00657 function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) { 00658 if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) { 00659 return ''; 00660 } 00661 00662 // no grade yet? 00663 if (is_null($value)) { 00664 return '-'; 00665 } 00666 00667 if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) { 00668 //unknown type?? 00669 return ''; 00670 } 00671 00672 if (is_null($displaytype)) { 00673 $displaytype = $grade_item->get_displaytype(); 00674 } 00675 00676 if (is_null($decimals)) { 00677 $decimals = $grade_item->get_decimals(); 00678 } 00679 00680 switch ($displaytype) { 00681 case GRADE_DISPLAY_TYPE_REAL: 00682 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized); 00683 00684 case GRADE_DISPLAY_TYPE_PERCENTAGE: 00685 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized); 00686 00687 case GRADE_DISPLAY_TYPE_LETTER: 00688 return grade_format_gradevalue_letter($value, $grade_item); 00689 00690 case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE: 00691 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' . 00692 grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')'; 00693 00694 case GRADE_DISPLAY_TYPE_REAL_LETTER: 00695 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' . 00696 grade_format_gradevalue_letter($value, $grade_item) . ')'; 00697 00698 case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL: 00699 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' . 00700 grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')'; 00701 00702 case GRADE_DISPLAY_TYPE_LETTER_REAL: 00703 return grade_format_gradevalue_letter($value, $grade_item) . ' (' . 00704 grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')'; 00705 00706 case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE: 00707 return grade_format_gradevalue_letter($value, $grade_item) . ' (' . 00708 grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')'; 00709 00710 case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER: 00711 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' . 00712 grade_format_gradevalue_letter($value, $grade_item) . ')'; 00713 default: 00714 return ''; 00715 } 00716 } 00717 00721 function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) { 00722 if ($grade_item->gradetype == GRADE_TYPE_SCALE) { 00723 if (!$scale = $grade_item->load_scale()) { 00724 return get_string('error'); 00725 } 00726 00727 $value = $grade_item->bounded_grade($value); 00728 return format_string($scale->scale_items[$value-1]); 00729 00730 } else { 00731 return format_float($value, $decimals, $localized); 00732 } 00733 } 00737 function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) { 00738 $min = $grade_item->grademin; 00739 $max = $grade_item->grademax; 00740 if ($min == $max) { 00741 return ''; 00742 } 00743 $value = $grade_item->bounded_grade($value); 00744 $percentage = (($value-$min)*100)/($max-$min); 00745 return format_float($percentage, $decimals, $localized).' %'; 00746 } 00750 function grade_format_gradevalue_letter($value, $grade_item) { 00751 $context = get_context_instance(CONTEXT_COURSE, $grade_item->courseid); 00752 if (!$letters = grade_get_letters($context)) { 00753 return ''; // no letters?? 00754 } 00755 00756 if (is_null($value)) { 00757 return '-'; 00758 } 00759 00760 $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100); 00761 $value = bounded_number(0, $value, 100); // just in case 00762 foreach ($letters as $boundary => $letter) { 00763 if ($value >= $boundary) { 00764 return format_string($letter); 00765 } 00766 } 00767 return '-'; // no match? maybe '' would be more correct 00768 } 00769 00770 00778 function grade_get_categories_menu($courseid, $includenew=false) { 00779 $result = array(); 00780 if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) { 00781 //make sure course category exists 00782 if (!grade_category::fetch_course_category($courseid)) { 00783 debugging('Can not create course grade category!'); 00784 return $result; 00785 } 00786 $categories = grade_category::fetch_all(array('courseid'=>$courseid)); 00787 } 00788 foreach ($categories as $key=>$category) { 00789 if ($category->is_course_category()) { 00790 $result[$category->id] = get_string('uncategorised', 'grades'); 00791 unset($categories[$key]); 00792 } 00793 } 00794 if ($includenew) { 00795 $result[-1] = get_string('newcategory', 'grades'); 00796 } 00797 $cats = array(); 00798 foreach ($categories as $category) { 00799 $cats[$category->id] = $category->get_name(); 00800 } 00801 collatorlib::asort($cats); 00802 00803 return ($result+$cats); 00804 } 00805 00812 function grade_get_letters($context=null) { 00813 global $DB; 00814 00815 if (empty($context)) { 00816 //default grading letters 00817 return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F'); 00818 } 00819 00820 static $cache = array(); 00821 00822 if (array_key_exists($context->id, $cache)) { 00823 return $cache[$context->id]; 00824 } 00825 00826 if (count($cache) > 100) { 00827 $cache = array(); // cache size limit 00828 } 00829 00830 $letters = array(); 00831 00832 $contexts = get_parent_contexts($context); 00833 array_unshift($contexts, $context->id); 00834 00835 foreach ($contexts as $ctxid) { 00836 if ($records = $DB->get_records('grade_letters', array('contextid'=>$ctxid), 'lowerboundary DESC')) { 00837 foreach ($records as $record) { 00838 $letters[$record->lowerboundary] = $record->letter; 00839 } 00840 } 00841 00842 if (!empty($letters)) { 00843 $cache[$context->id] = $letters; 00844 return $letters; 00845 } 00846 } 00847 00848 $letters = grade_get_letters(null); 00849 $cache[$context->id] = $letters; 00850 return $letters; 00851 } 00852 00853 00864 function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) { 00865 global $DB; 00866 00867 if ($idnumber == '') { 00868 //we allow empty idnumbers 00869 return true; 00870 } 00871 00872 // keep existing even when not unique 00873 if ($cm and $cm->idnumber == $idnumber) { 00874 if ($grade_item and $grade_item->itemnumber != 0) { 00875 // grade item with itemnumber > 0 can't have the same idnumber as the main 00876 // itemnumber 0 which is synced with course_modules 00877 return false; 00878 } 00879 return true; 00880 } else if ($grade_item and $grade_item->idnumber == $idnumber) { 00881 return true; 00882 } 00883 00884 if ($DB->record_exists('course_modules', array('course'=>$courseid, 'idnumber'=>$idnumber))) { 00885 return false; 00886 } 00887 00888 if ($DB->record_exists('grade_items', array('courseid'=>$courseid, 'idnumber'=>$idnumber))) { 00889 return false; 00890 } 00891 00892 return true; 00893 } 00894 00901 function grade_force_full_regrading($courseid) { 00902 global $DB; 00903 $DB->set_field('grade_items', 'needsupdate', 1, array('courseid'=>$courseid)); 00904 } 00905 00911 function grade_force_site_regrading() { 00912 global $CFG, $DB; 00913 $DB->set_field('grade_items', 'needsupdate', 1); 00914 } 00915 00922 function grade_recover_history_grades($userid, $courseid) { 00923 global $CFG, $DB; 00924 00925 if ($CFG->disablegradehistory) { 00926 debugging('Attempting to recover grades when grade history is disabled.'); 00927 return false; 00928 } 00929 00930 //Were grades recovered? Flag to return. 00931 $recoveredgrades = false; 00932 00933 //Check the user is enrolled in this course 00934 //Dont bother checking if they have a gradeable role. They may get one later so recover 00935 //whatever grades they have now just in case. 00936 $course_context = get_context_instance(CONTEXT_COURSE, $courseid); 00937 if (!is_enrolled($course_context, $userid)) { 00938 debugging('Attempting to recover the grades of a user who is deleted or not enrolled. Skipping recover.'); 00939 return false; 00940 } 00941 00942 //Check for existing grades for this user in this course 00943 //Recovering grades when the user already has grades can lead to duplicate indexes and bad data 00944 //In the future we could move the existing grades to the history table then recover the grades from before then 00945 $sql = "SELECT gg.id 00946 FROM {grade_grades} gg 00947 JOIN {grade_items} gi ON gi.id = gg.itemid 00948 WHERE gi.courseid = :courseid AND gg.userid = :userid"; 00949 $params = array('userid' => $userid, 'courseid' => $courseid); 00950 if ($DB->record_exists_sql($sql, $params)) { 00951 debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.'); 00952 return false; 00953 } else { 00954 //Retrieve the user's old grades 00955 //have history ID as first column to guarantee we a unique first column 00956 $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax, 00957 h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback, 00958 h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated 00959 FROM {grade_grades_history} h 00960 JOIN (SELECT itemid, MAX(id) AS id 00961 FROM {grade_grades_history} 00962 WHERE userid = :userid1 00963 GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid 00964 JOIN {grade_items} gi ON gi.id = h.itemid 00965 JOIN (SELECT itemid, MAX(timemodified) AS tm 00966 FROM {grade_grades_history} 00967 WHERE userid = :userid2 AND action = :insertaction 00968 GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid 00969 WHERE gi.courseid = :courseid"; 00970 $params = array('userid1' => $userid, 'userid2' => $userid , 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid); 00971 $oldgrades = $DB->get_records_sql($sql, $params); 00972 00973 //now move the old grades to the grade_grades table 00974 foreach ($oldgrades as $oldgrade) { 00975 unset($oldgrade->id); 00976 00977 $grade = new grade_grade($oldgrade, false);//2nd arg false as dont want to try and retrieve a record from the DB 00978 $grade->insert($oldgrade->source); 00979 00980 //dont include default empty grades created when activities are created 00981 if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) { 00982 $recoveredgrades = true; 00983 } 00984 } 00985 } 00986 00987 //Some activities require manual grade synching (moving grades from the activity into the gradebook) 00988 //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across 00989 grade_grab_course_grades($courseid, null, $userid); 00990 00991 return $recoveredgrades; 00992 } 00993 01002 function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) { 01003 01004 $course_item = grade_item::fetch_course_item($courseid); 01005 01006 if ($userid) { 01007 // one raw grade updated for one user 01008 if (empty($updated_item)) { 01009 print_error("cannotbenull", 'debug', '', "updated_item"); 01010 } 01011 if ($course_item->needsupdate) { 01012 $updated_item->force_regrading(); 01013 return array($course_item->id =>'Can not do fast regrading after updating of raw grades'); 01014 } 01015 01016 } else { 01017 if (!$course_item->needsupdate) { 01018 // nothing to do :-) 01019 return true; 01020 } 01021 } 01022 01023 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid)); 01024 $depends_on = array(); 01025 01026 // first mark all category and calculated items as needing regrading 01027 // this is slower, but 100% accurate 01028 foreach ($grade_items as $gid=>$gitem) { 01029 if (!empty($updated_item) and $updated_item->id == $gid) { 01030 $grade_items[$gid]->needsupdate = 1; 01031 01032 } else if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) { 01033 $grade_items[$gid]->needsupdate = 1; 01034 } 01035 01036 // construct depends_on lookup array 01037 $depends_on[$gid] = $grade_items[$gid]->depends_on(); 01038 } 01039 01040 $errors = array(); 01041 $finalids = array(); 01042 $gids = array_keys($grade_items); 01043 $failed = 0; 01044 01045 while (count($finalids) < count($gids)) { // work until all grades are final or error found 01046 $count = 0; 01047 foreach ($gids as $gid) { 01048 if (in_array($gid, $finalids)) { 01049 continue; // already final 01050 } 01051 01052 if (!$grade_items[$gid]->needsupdate) { 01053 $finalids[] = $gid; // we can make it final - does not need update 01054 continue; 01055 } 01056 01057 $doupdate = true; 01058 foreach ($depends_on[$gid] as $did) { 01059 if (!in_array($did, $finalids)) { 01060 $doupdate = false; 01061 continue; // this item depends on something that is not yet in finals array 01062 } 01063 } 01064 01065 //oki - let's update, calculate or aggregate :-) 01066 if ($doupdate) { 01067 $result = $grade_items[$gid]->regrade_final_grades($userid); 01068 01069 if ($result === true) { 01070 $grade_items[$gid]->regrading_finished(); 01071 $grade_items[$gid]->check_locktime(); // do the locktime item locking 01072 $count++; 01073 $finalids[] = $gid; 01074 01075 } else { 01076 $grade_items[$gid]->force_regrading(); 01077 $errors[$gid] = $result; 01078 } 01079 } 01080 } 01081 01082 if ($count == 0) { 01083 $failed++; 01084 } else { 01085 $failed = 0; 01086 } 01087 01088 if ($failed > 1) { 01089 foreach($gids as $gid) { 01090 if (in_array($gid, $finalids)) { 01091 continue; // this one is ok 01092 } 01093 $grade_items[$gid]->force_regrading(); 01094 $errors[$grade_items[$gid]->id] = 'Probably circular reference or broken calculation formula'; // TODO: localize 01095 } 01096 break; // oki, found error 01097 } 01098 } 01099 01100 if (count($errors) == 0) { 01101 if (empty($userid)) { 01102 // do the locktime locking of grades, but only when doing full regrading 01103 grade_grade::check_locktime_all($gids); 01104 } 01105 return true; 01106 } else { 01107 return $errors; 01108 } 01109 } 01110 01118 function grade_grab_course_grades($courseid, $modname=null, $userid=0) { 01119 global $CFG, $DB; 01120 01121 if ($modname) { 01122 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname 01123 FROM {".$modname."} a, {course_modules} cm, {modules} m 01124 WHERE m.name=:modname AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid"; 01125 $params = array('modname'=>$modname, 'courseid'=>$courseid); 01126 01127 if ($modinstances = $DB->get_records_sql($sql, $params)) { 01128 foreach ($modinstances as $modinstance) { 01129 grade_update_mod_grades($modinstance, $userid); 01130 } 01131 } 01132 return; 01133 } 01134 01135 if (!$mods = get_plugin_list('mod') ) { 01136 print_error('nomodules', 'debug'); 01137 } 01138 01139 foreach ($mods as $mod => $fullmod) { 01140 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it 01141 continue; 01142 } 01143 01144 // include the module lib once 01145 if (file_exists($fullmod.'/lib.php')) { 01146 // get all instance of the activity 01147 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname 01148 FROM {".$mod."} a, {course_modules} cm, {modules} m 01149 WHERE m.name=:mod AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid"; 01150 $params = array('mod'=>$mod, 'courseid'=>$courseid); 01151 01152 if ($modinstances = $DB->get_records_sql($sql, $params)) { 01153 foreach ($modinstances as $modinstance) { 01154 grade_update_mod_grades($modinstance, $userid); 01155 } 01156 } 01157 } 01158 } 01159 } 01160 01170 function grade_update_mod_grades($modinstance, $userid=0) { 01171 global $CFG, $DB; 01172 01173 $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname; 01174 if (!file_exists($fullmod.'/lib.php')) { 01175 debugging('missing lib.php file in module ' . $modinstance->modname); 01176 return false; 01177 } 01178 include_once($fullmod.'/lib.php'); 01179 01180 $updategradesfunc = $modinstance->modname.'_update_grades'; 01181 $updateitemfunc = $modinstance->modname.'_grade_item_update'; 01182 01183 if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) { 01184 //new grading supported, force updating of grades 01185 $updateitemfunc($modinstance); 01186 $updategradesfunc($modinstance, $userid); 01187 01188 } else { 01189 // mudule does not support grading?? 01190 } 01191 01192 return true; 01193 } 01194 01201 function remove_grade_letters($context, $showfeedback) { 01202 global $DB, $OUTPUT; 01203 01204 $strdeleted = get_string('deleted'); 01205 01206 $DB->delete_records('grade_letters', array('contextid'=>$context->id)); 01207 if ($showfeedback) { 01208 echo $OUTPUT->notification($strdeleted.' - '.get_string('letters', 'grades'), 'notifysuccess'); 01209 } 01210 } 01211 01218 function remove_course_grades($courseid, $showfeedback) { 01219 global $DB, $OUTPUT; 01220 01221 $fs = get_file_storage(); 01222 $strdeleted = get_string('deleted'); 01223 01224 $course_category = grade_category::fetch_course_category($courseid); 01225 $course_category->delete('coursedelete'); 01226 $fs->delete_area_files(get_context_instance(CONTEXT_COURSE, $courseid)->id, 'grade', 'feedback'); 01227 if ($showfeedback) { 01228 echo $OUTPUT->notification($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'), 'notifysuccess'); 01229 } 01230 01231 if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) { 01232 foreach ($outcomes as $outcome) { 01233 $outcome->delete('coursedelete'); 01234 } 01235 } 01236 $DB->delete_records('grade_outcomes_courses', array('courseid'=>$courseid)); 01237 if ($showfeedback) { 01238 echo $OUTPUT->notification($strdeleted.' - '.get_string('outcomes', 'grades'), 'notifysuccess'); 01239 } 01240 01241 if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) { 01242 foreach ($scales as $scale) { 01243 $scale->delete('coursedelete'); 01244 } 01245 } 01246 if ($showfeedback) { 01247 echo $OUTPUT->notification($strdeleted.' - '.get_string('scales'), 'notifysuccess'); 01248 } 01249 01250 $DB->delete_records('grade_settings', array('courseid'=>$courseid)); 01251 if ($showfeedback) { 01252 echo $OUTPUT->notification($strdeleted.' - '.get_string('settings', 'grades'), 'notifysuccess'); 01253 } 01254 } 01255 01264 function grade_course_category_delete($categoryid, $newparentid, $showfeedback) { 01265 global $DB; 01266 01267 $context = get_context_instance(CONTEXT_COURSECAT, $categoryid); 01268 $DB->delete_records('grade_letters', array('contextid'=>$context->id)); 01269 } 01270 01278 function grade_uninstalled_module($modname) { 01279 global $CFG, $DB; 01280 01281 $sql = "SELECT * 01282 FROM {grade_items} 01283 WHERE itemtype='mod' AND itemmodule=?"; 01284 01285 // go all items for this module and delete them including the grades 01286 $rs = $DB->get_recordset_sql($sql, array($modname)); 01287 foreach ($rs as $item) { 01288 $grade_item = new grade_item($item, false); 01289 $grade_item->delete('moduninstall'); 01290 } 01291 $rs->close(); 01292 } 01293 01298 function grade_user_delete($userid) { 01299 if ($grades = grade_grade::fetch_all(array('userid'=>$userid))) { 01300 foreach ($grades as $grade) { 01301 $grade->delete('userdelete'); 01302 } 01303 } 01304 } 01305 01310 function grade_user_unenrol($courseid, $userid) { 01311 if ($items = grade_item::fetch_all(array('courseid'=>$courseid))) { 01312 foreach ($items as $item) { 01313 if ($grades = grade_grade::fetch_all(array('userid'=>$userid, 'itemid'=>$item->id))) { 01314 foreach ($grades as $grade) { 01315 $grade->delete('userdelete'); 01316 } 01317 } 01318 } 01319 } 01320 } 01321 01328 function grade_cron() { 01329 global $CFG, $DB; 01330 01331 $now = time(); 01332 01333 $sql = "SELECT i.* 01334 FROM {grade_items} i 01335 WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < ? AND EXISTS ( 01336 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)"; 01337 01338 // go through all courses that have proper final grades and lock them if needed 01339 $rs = $DB->get_recordset_sql($sql, array($now)); 01340 foreach ($rs as $item) { 01341 $grade_item = new grade_item($item, false); 01342 $grade_item->locked = $now; 01343 $grade_item->update('locktime'); 01344 } 01345 $rs->close(); 01346 01347 $grade_inst = new grade_grade(); 01348 $fields = 'g.'.implode(',g.', $grade_inst->required_fields); 01349 01350 $sql = "SELECT $fields 01351 FROM {grade_grades} g, {grade_items} i 01352 WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < ? AND g.itemid=i.id AND EXISTS ( 01353 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)"; 01354 01355 // go through all courses that have proper final grades and lock them if needed 01356 $rs = $DB->get_recordset_sql($sql, array($now)); 01357 foreach ($rs as $grade) { 01358 $grade_grade = new grade_grade($grade, false); 01359 $grade_grade->locked = $now; 01360 $grade_grade->update('locktime'); 01361 } 01362 $rs->close(); 01363 01364 //TODO: do not run this cleanup every cron invocation 01365 // cleanup history tables 01366 if (!empty($CFG->gradehistorylifetime)) { // value in days 01367 $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24); 01368 $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_history', 'grade_grades_history', 'scale_history'); 01369 foreach ($tables as $table) { 01370 if ($DB->delete_records_select($table, "timemodified < ?", array($histlifetime))) { 01371 mtrace(" Deleted old grade history records from '$table'"); 01372 } 01373 } 01374 } 01375 } 01376 01383 function grade_course_reset($courseid) { 01384 01385 // no recalculations 01386 grade_force_full_regrading($courseid); 01387 01388 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid)); 01389 foreach ($grade_items as $gid=>$grade_item) { 01390 $grade_item->delete_all_grades('reset'); 01391 } 01392 01393 //refetch all grades 01394 grade_grab_course_grades($courseid); 01395 01396 // recalculate all grades 01397 grade_regrade_final_grades($courseid); 01398 return true; 01399 } 01400 01408 function grade_floatval($number) { 01409 if (is_null($number) or $number === '') { 01410 return null; 01411 } 01412 // we must round to 5 digits to get the same precision as in 10,5 db fields 01413 // note: db rounding for 10,5 is different from php round() function 01414 return round($number, 5); 01415 } 01416 01425 function grade_floats_different($f1, $f2) { 01426 // note: db rounding for 10,5 is different from php round() function 01427 return (grade_floatval($f1) !== grade_floatval($f2)); 01428 } 01429 01441 function grade_floats_equal($f1, $f2) { 01442 return (grade_floatval($f1) === grade_floatval($f2)); 01443 }