Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/gradelib.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/>.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations