[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> gradelib.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Library of functions for gradebook - both public and internal
  19   *
  20   * @package   core_grades
  21   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  /** Include essential files */
  28  require_once($CFG->libdir . '/grade/constants.php');
  29  
  30  require_once($CFG->libdir . '/grade/grade_category.php');
  31  require_once($CFG->libdir . '/grade/grade_item.php');
  32  require_once($CFG->libdir . '/grade/grade_grade.php');
  33  require_once($CFG->libdir . '/grade/grade_scale.php');
  34  require_once($CFG->libdir . '/grade/grade_outcome.php');
  35  
  36  /////////////////////////////////////////////////////////////////////
  37  ///// Start of public API for communication with modules/blocks /////
  38  /////////////////////////////////////////////////////////////////////
  39  
  40  /**
  41   * Submit new or update grade; update/create grade_item definition. Grade must have userid specified,
  42   * rawgrade and feedback with format are optional. rawgrade NULL means 'Not graded'.
  43   * Missing property or key means does not change the existing value.
  44   *
  45   * Only following grade item properties can be changed 'itemname', 'idnumber', 'gradetype', 'grademax',
  46   * 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted' and 'hidden'. 'reset' means delete all current grades including locked ones.
  47   *
  48   * Manual, course or category items can not be updated by this function.
  49   *
  50   * @category grade
  51   * @param string $source Source of the grade such as 'mod/assignment'
  52   * @param int    $courseid ID of course
  53   * @param string $itemtype Type of grade item. For example, mod or block
  54   * @param string $itemmodule More specific then $itemtype. For example, assignment or forum. May be NULL for some item types
  55   * @param int    $iteminstance Instance ID of graded item
  56   * @param int    $itemnumber Most probably 0. Modules can use other numbers when having more than one grade for each user
  57   * @param mixed  $grades Grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only
  58   * @param mixed  $itemdetails Object or array describing the grading item, NULL if no change
  59   * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
  60   */
  61  function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades=NULL, $itemdetails=NULL) {
  62      global $USER, $CFG, $DB;
  63  
  64      // only following grade_item properties can be changed in this function
  65      $allowed = array('itemname', 'idnumber', 'gradetype', 'grademax', 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted', 'hidden');
  66      // list of 10,5 numeric fields
  67      $floats  = array('grademin', 'grademax', 'multfactor', 'plusfactor');
  68  
  69      // grade item identification
  70      $params = compact('courseid', 'itemtype', 'itemmodule', 'iteminstance', 'itemnumber');
  71  
  72      if (is_null($courseid) or is_null($itemtype)) {
  73          debugging('Missing courseid or itemtype');
  74          return GRADE_UPDATE_FAILED;
  75      }
  76  
  77      if (!$grade_items = grade_item::fetch_all($params)) {
  78          // create a new one
  79          $grade_item = false;
  80  
  81      } else if (count($grade_items) == 1){
  82          $grade_item = reset($grade_items);
  83          unset($grade_items); //release memory
  84  
  85      } else {
  86          debugging('Found more than one grade item');
  87          return GRADE_UPDATE_MULTIPLE;
  88      }
  89  
  90      if (!empty($itemdetails['deleted'])) {
  91          if ($grade_item) {
  92              if ($grade_item->delete($source)) {
  93                  return GRADE_UPDATE_OK;
  94              } else {
  95                  return GRADE_UPDATE_FAILED;
  96              }
  97          }
  98          return GRADE_UPDATE_OK;
  99      }
 100  
 101  /// Create or update the grade_item if needed
 102  
 103      if (!$grade_item) {
 104          if ($itemdetails) {
 105              $itemdetails = (array)$itemdetails;
 106  
 107              // grademin and grademax ignored when scale specified
 108              if (array_key_exists('scaleid', $itemdetails)) {
 109                  if ($itemdetails['scaleid']) {
 110                      unset($itemdetails['grademin']);
 111                      unset($itemdetails['grademax']);
 112                  }
 113              }
 114  
 115              foreach ($itemdetails as $k=>$v) {
 116                  if (!in_array($k, $allowed)) {
 117                      // ignore it
 118                      continue;
 119                  }
 120                  if ($k == 'gradetype' and $v == GRADE_TYPE_NONE) {
 121                      // no grade item needed!
 122                      return GRADE_UPDATE_OK;
 123                  }
 124                  $params[$k] = $v;
 125              }
 126          }
 127          $grade_item = new grade_item($params);
 128          $grade_item->insert();
 129  
 130      } else {
 131          if ($grade_item->is_locked()) {
 132              // no notice() here, test returned value instead!
 133              return GRADE_UPDATE_ITEM_LOCKED;
 134          }
 135  
 136          if ($itemdetails) {
 137              $itemdetails = (array)$itemdetails;
 138              $update = false;
 139              foreach ($itemdetails as $k=>$v) {
 140                  if (!in_array($k, $allowed)) {
 141                      // ignore it
 142                      continue;
 143                  }
 144                  if (in_array($k, $floats)) {
 145                      if (grade_floats_different($grade_item->{$k}, $v)) {
 146                          $grade_item->{$k} = $v;
 147                          $update = true;
 148                      }
 149  
 150                  } else {
 151                      if ($grade_item->{$k} != $v) {
 152                          $grade_item->{$k} = $v;
 153                          $update = true;
 154                      }
 155                  }
 156              }
 157              if ($update) {
 158                  $grade_item->update();
 159              }
 160          }
 161      }
 162  
 163  /// reset grades if requested
 164      if (!empty($itemdetails['reset'])) {
 165          $grade_item->delete_all_grades('reset');
 166          return GRADE_UPDATE_OK;
 167      }
 168  
 169  /// Some extra checks
 170      // do we use grading?
 171      if ($grade_item->gradetype == GRADE_TYPE_NONE) {
 172          return GRADE_UPDATE_OK;
 173      }
 174  
 175      // no grade submitted
 176      if (empty($grades)) {
 177          return GRADE_UPDATE_OK;
 178      }
 179  
 180  /// Finally start processing of grades
 181      if (is_object($grades)) {
 182          $grades = array($grades->userid=>$grades);
 183      } else {
 184          if (array_key_exists('userid', $grades)) {
 185              $grades = array($grades['userid']=>$grades);
 186          }
 187      }
 188  
 189  /// normalize and verify grade array
 190      foreach($grades as $k=>$g) {
 191          if (!is_array($g)) {
 192              $g = (array)$g;
 193              $grades[$k] = $g;
 194          }
 195  
 196          if (empty($g['userid']) or $k != $g['userid']) {
 197              debugging('Incorrect grade array index, must be user id! Grade ignored.');
 198              unset($grades[$k]);
 199          }
 200      }
 201  
 202      if (empty($grades)) {
 203          return GRADE_UPDATE_FAILED;
 204      }
 205  
 206      $count = count($grades);
 207      if ($count > 0 and $count < 200) {
 208          list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start='uid');
 209          $params['gid'] = $grade_item->id;
 210          $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid AND userid $uids";
 211  
 212      } else {
 213          $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid";
 214          $params = array('gid'=>$grade_item->id);
 215      }
 216  
 217      $rs = $DB->get_recordset_sql($sql, $params);
 218  
 219      $failed = false;
 220  
 221      while (count($grades) > 0) {
 222          $grade_grade = null;
 223          $grade       = null;
 224  
 225          foreach ($rs as $gd) {
 226  
 227              $userid = $gd->userid;
 228              if (!isset($grades[$userid])) {
 229                  // this grade not requested, continue
 230                  continue;
 231              }
 232              // existing grade requested
 233              $grade       = $grades[$userid];
 234              $grade_grade = new grade_grade($gd, false);
 235              unset($grades[$userid]);
 236              break;
 237          }
 238  
 239          if (is_null($grade_grade)) {
 240              if (count($grades) == 0) {
 241                  // no more grades to process
 242                  break;
 243              }
 244  
 245              $grade       = reset($grades);
 246              $userid      = $grade['userid'];
 247              $grade_grade = new grade_grade(array('itemid'=>$grade_item->id, 'userid'=>$userid), false);
 248              $grade_grade->load_optional_fields(); // add feedback and info too
 249              unset($grades[$userid]);
 250          }
 251  
 252          $rawgrade       = false;
 253          $feedback       = false;
 254          $feedbackformat = FORMAT_MOODLE;
 255          $usermodified   = $USER->id;
 256          $datesubmitted  = null;
 257          $dategraded     = null;
 258  
 259          if (array_key_exists('rawgrade', $grade)) {
 260              $rawgrade = $grade['rawgrade'];
 261          }
 262  
 263          if (array_key_exists('feedback', $grade)) {
 264              $feedback = $grade['feedback'];
 265          }
 266  
 267          if (array_key_exists('feedbackformat', $grade)) {
 268              $feedbackformat = $grade['feedbackformat'];
 269          }
 270  
 271          if (array_key_exists('usermodified', $grade)) {
 272              $usermodified = $grade['usermodified'];
 273          }
 274  
 275          if (array_key_exists('datesubmitted', $grade)) {
 276              $datesubmitted = $grade['datesubmitted'];
 277          }
 278  
 279          if (array_key_exists('dategraded', $grade)) {
 280              $dategraded = $grade['dategraded'];
 281          }
 282  
 283          // update or insert the grade
 284          if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified, $dategraded, $datesubmitted, $grade_grade)) {
 285              $failed = true;
 286          }
 287      }
 288  
 289      if ($rs) {
 290          $rs->close();
 291      }
 292  
 293      if (!$failed) {
 294          return GRADE_UPDATE_OK;
 295      } else {
 296          return GRADE_UPDATE_FAILED;
 297      }
 298  }
 299  
 300  /**
 301   * Updates a user's outcomes. Manual outcomes can not be updated.
 302   *
 303   * @category grade
 304   * @param string $source Source of the grade such as 'mod/assignment'
 305   * @param int    $courseid ID of course
 306   * @param string $itemtype Type of grade item. For example, 'mod' or 'block'
 307   * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types
 308   * @param int    $iteminstance Instance ID of graded item. For example the forum ID.
 309   * @param int    $userid ID of the graded user
 310   * @param array  $data Array consisting of grade item itemnumber ({@link grade_update()}) => outcomegrade
 311   * @return bool returns true if grade items were found and updated successfully
 312   */
 313  function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $iteminstance, $userid, $data) {
 314      if ($items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
 315          $result = true;
 316          foreach ($items as $item) {
 317              if (!array_key_exists($item->itemnumber, $data)) {
 318                  continue;
 319              }
 320              $grade = $data[$item->itemnumber] < 1 ? null : $data[$item->itemnumber];
 321              $result = ($item->update_final_grade($userid, $grade, $source) && $result);
 322          }
 323          return $result;
 324      }
 325      return false; //grade items not found
 326  }
 327  
 328  /**
 329   * Returns grading information for given activity, optionally with user grades
 330   * Manual, course or category items can not be queried.
 331   *
 332   * @category grade
 333   * @param int    $courseid ID of course
 334   * @param string $itemtype Type of grade item. For example, 'mod' or 'block'
 335   * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types
 336   * @param int    $iteminstance ID of the item module
 337   * @param mixed  $userid_or_ids Either a single user ID, an array of user IDs or null. If user ID or IDs are not supplied returns information about grade_item
 338   * @return array Array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers
 339   */
 340  function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) {
 341      global $CFG;
 342  
 343      $return = new stdClass();
 344      $return->items    = array();
 345      $return->outcomes = array();
 346  
 347      $course_item = grade_item::fetch_course_item($courseid);
 348      $needsupdate = array();
 349      if ($course_item->needsupdate) {
 350          $result = grade_regrade_final_grades($courseid);
 351          if ($result !== true) {
 352              $needsupdate = array_keys($result);
 353          }
 354      }
 355  
 356      if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
 357          foreach ($grade_items as $grade_item) {
 358              $decimalpoints = null;
 359  
 360              if (empty($grade_item->outcomeid)) {
 361                  // prepare information about grade item
 362                  $item = new stdClass();
 363                  $item->id = $grade_item->id;
 364                  $item->itemnumber = $grade_item->itemnumber;
 365                  $item->itemtype  = $grade_item->itemtype;
 366                  $item->itemmodule = $grade_item->itemmodule;
 367                  $item->iteminstance = $grade_item->iteminstance;
 368                  $item->scaleid    = $grade_item->scaleid;
 369                  $item->name       = $grade_item->get_name();
 370                  $item->grademin   = $grade_item->grademin;
 371                  $item->grademax   = $grade_item->grademax;
 372                  $item->gradepass  = $grade_item->gradepass;
 373                  $item->locked     = $grade_item->is_locked();
 374                  $item->hidden     = $grade_item->is_hidden();
 375                  $item->grades     = array();
 376  
 377                  switch ($grade_item->gradetype) {
 378                      case GRADE_TYPE_NONE:
 379                          continue;
 380  
 381                      case GRADE_TYPE_VALUE:
 382                          $item->scaleid = 0;
 383                          break;
 384  
 385                      case GRADE_TYPE_TEXT:
 386                          $item->scaleid   = 0;
 387                          $item->grademin   = 0;
 388                          $item->grademax   = 0;
 389                          $item->gradepass  = 0;
 390                          break;
 391                  }
 392  
 393                  if (empty($userid_or_ids)) {
 394                      $userids = array();
 395  
 396                  } else if (is_array($userid_or_ids)) {
 397                      $userids = $userid_or_ids;
 398  
 399                  } else {
 400                      $userids = array($userid_or_ids);
 401                  }
 402  
 403                  if ($userids) {
 404                      $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
 405                      foreach ($userids as $userid) {
 406                          $grade_grades[$userid]->grade_item =& $grade_item;
 407  
 408                          $grade = new stdClass();
 409                          $grade->grade          = $grade_grades[$userid]->finalgrade;
 410                          $grade->locked         = $grade_grades[$userid]->is_locked();
 411                          $grade->hidden         = $grade_grades[$userid]->is_hidden();
 412                          $grade->overridden     = $grade_grades[$userid]->overridden;
 413                          $grade->feedback       = $grade_grades[$userid]->feedback;
 414                          $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
 415                          $grade->usermodified   = $grade_grades[$userid]->usermodified;
 416                          $grade->datesubmitted  = $grade_grades[$userid]->get_datesubmitted();
 417                          $grade->dategraded     = $grade_grades[$userid]->get_dategraded();
 418  
 419                          // create text representation of grade
 420                          if ($grade_item->gradetype == GRADE_TYPE_TEXT or $grade_item->gradetype == GRADE_TYPE_NONE) {
 421                              $grade->grade          = null;
 422                              $grade->str_grade      = '-';
 423                              $grade->str_long_grade = $grade->str_grade;
 424  
 425                          } else if (in_array($grade_item->id, $needsupdate)) {
 426                              $grade->grade          = false;
 427                              $grade->str_grade      = get_string('error');
 428                              $grade->str_long_grade = $grade->str_grade;
 429  
 430                          } else if (is_null($grade->grade)) {
 431                              $grade->str_grade      = '-';
 432                              $grade->str_long_grade = $grade->str_grade;
 433  
 434                          } else {
 435                              $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item);
 436                              if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) {
 437                                  $grade->str_long_grade = $grade->str_grade;
 438                              } else {
 439                                  $a = new stdClass();
 440                                  $a->grade = $grade->str_grade;
 441                                  $a->max   = grade_format_gradevalue($grade_item->grademax, $grade_item);
 442                                  $grade->str_long_grade = get_string('gradelong', 'grades', $a);
 443                              }
 444                          }
 445  
 446                          // create html representation of feedback
 447                          if (is_null($grade->feedback)) {
 448                              $grade->str_feedback = '';
 449                          } else {
 450                              $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
 451                          }
 452  
 453                          $item->grades[$userid] = $grade;
 454                      }
 455                  }
 456                  $return->items[$grade_item->itemnumber] = $item;
 457  
 458              } else {
 459                  if (!$grade_outcome = grade_outcome::fetch(array('id'=>$grade_item->outcomeid))) {
 460                      debugging('Incorect outcomeid found');
 461                      continue;
 462                  }
 463  
 464                  // outcome info
 465                  $outcome = new stdClass();
 466                  $outcome->id = $grade_item->id;
 467                  $outcome->itemnumber = $grade_item->itemnumber;
 468                  $outcome->itemtype   = $grade_item->itemtype;
 469                  $outcome->itemmodule = $grade_item->itemmodule;
 470                  $outcome->iteminstance = $grade_item->iteminstance;
 471                  $outcome->scaleid    = $grade_outcome->scaleid;
 472                  $outcome->name       = $grade_outcome->get_name();
 473                  $outcome->locked     = $grade_item->is_locked();
 474                  $outcome->hidden     = $grade_item->is_hidden();
 475  
 476                  if (empty($userid_or_ids)) {
 477                      $userids = array();
 478                  } else if (is_array($userid_or_ids)) {
 479                      $userids = $userid_or_ids;
 480                  } else {
 481                      $userids = array($userid_or_ids);
 482                  }
 483  
 484                  if ($userids) {
 485                      $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
 486                      foreach ($userids as $userid) {
 487                          $grade_grades[$userid]->grade_item =& $grade_item;
 488  
 489                          $grade = new stdClass();
 490                          $grade->grade          = $grade_grades[$userid]->finalgrade;
 491                          $grade->locked         = $grade_grades[$userid]->is_locked();
 492                          $grade->hidden         = $grade_grades[$userid]->is_hidden();
 493                          $grade->feedback       = $grade_grades[$userid]->feedback;
 494                          $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
 495                          $grade->usermodified   = $grade_grades[$userid]->usermodified;
 496  
 497                          // create text representation of grade
 498                          if (in_array($grade_item->id, $needsupdate)) {
 499                              $grade->grade     = false;
 500                              $grade->str_grade = get_string('error');
 501  
 502                          } else if (is_null($grade->grade)) {
 503                              $grade->grade = 0;
 504                              $grade->str_grade = get_string('nooutcome', 'grades');
 505  
 506                          } else {
 507                              $grade->grade = (int)$grade->grade;
 508                              $scale = $grade_item->load_scale();
 509                              $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]);
 510                          }
 511  
 512                          // create html representation of feedback
 513                          if (is_null($grade->feedback)) {
 514                              $grade->str_feedback = '';
 515                          } else {
 516                              $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
 517                          }
 518  
 519                          $outcome->grades[$userid] = $grade;
 520                      }
 521                  }
 522  
 523                  if (isset($return->outcomes[$grade_item->itemnumber])) {
 524                      // itemnumber duplicates - lets fix them!
 525                      $newnumber = $grade_item->itemnumber + 1;
 526                      while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) {
 527                          $newnumber++;
 528                      }
 529                      $outcome->itemnumber    = $newnumber;
 530                      $grade_item->itemnumber = $newnumber;
 531                      $grade_item->update('system');
 532                  }
 533  
 534                  $return->outcomes[$grade_item->itemnumber] = $outcome;
 535  
 536              }
 537          }
 538      }
 539  
 540      // sort results using itemnumbers
 541      ksort($return->items, SORT_NUMERIC);
 542      ksort($return->outcomes, SORT_NUMERIC);
 543  
 544      return $return;
 545  }
 546  
 547  ///////////////////////////////////////////////////////////////////
 548  ///// End of public API for communication with modules/blocks /////
 549  ///////////////////////////////////////////////////////////////////
 550  
 551  
 552  
 553  ///////////////////////////////////////////////////////////////////
 554  ///// Internal API: used by gradebook plugins and Moodle core /////
 555  ///////////////////////////////////////////////////////////////////
 556  
 557  /**
 558   * Returns a  course gradebook setting
 559   *
 560   * @param int $courseid
 561   * @param string $name of setting, maybe null if reset only
 562   * @param string $default value to return if setting is not found
 563   * @param bool $resetcache force reset of internal static cache
 564   * @return string value of the setting, $default if setting not found, NULL if supplied $name is null
 565   */
 566  function grade_get_setting($courseid, $name, $default=null, $resetcache=false) {
 567      global $DB;
 568  
 569      static $cache = array();
 570  
 571      if ($resetcache or !array_key_exists($courseid, $cache)) {
 572          $cache[$courseid] = array();
 573  
 574      } else if (is_null($name)) {
 575          return null;
 576  
 577      } else if (array_key_exists($name, $cache[$courseid])) {
 578          return $cache[$courseid][$name];
 579      }
 580  
 581      if (!$data = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) {
 582          $result = null;
 583      } else {
 584          $result = $data->value;
 585      }
 586  
 587      if (is_null($result)) {
 588          $result = $default;
 589      }
 590  
 591      $cache[$courseid][$name] = $result;
 592      return $result;
 593  }
 594  
 595  /**
 596   * Returns all course gradebook settings as object properties
 597   *
 598   * @param int $courseid
 599   * @return object
 600   */
 601  function grade_get_settings($courseid) {
 602      global $DB;
 603  
 604       $settings = new stdClass();
 605       $settings->id = $courseid;
 606  
 607      if ($records = $DB->get_records('grade_settings', array('courseid'=>$courseid))) {
 608          foreach ($records as $record) {
 609              $settings->{$record->name} = $record->value;
 610          }
 611      }
 612  
 613      return $settings;
 614  }
 615  
 616  /**
 617   * Add, update or delete a course gradebook setting
 618   *
 619   * @param int $courseid The course ID
 620   * @param string $name Name of the setting
 621   * @param string $value Value of the setting. NULL means delete the setting.
 622   */
 623  function grade_set_setting($courseid, $name, $value) {
 624      global $DB;
 625  
 626      if (is_null($value)) {
 627          $DB->delete_records('grade_settings', array('courseid'=>$courseid, 'name'=>$name));
 628  
 629      } else if (!$existing = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) {
 630          $data = new stdClass();
 631          $data->courseid = $courseid;
 632          $data->name     = $name;
 633          $data->value    = $value;
 634          $DB->insert_record('grade_settings', $data);
 635  
 636      } else {
 637          $data = new stdClass();
 638          $data->id       = $existing->id;
 639          $data->value    = $value;
 640          $DB->update_record('grade_settings', $data);
 641      }
 642  
 643      grade_get_setting($courseid, null, null, true); // reset the cache
 644  }
 645  
 646  /**
 647   * Returns string representation of grade value
 648   *
 649   * @param float $value The grade value
 650   * @param object $grade_item Grade item object passed by reference to prevent scale reloading
 651   * @param bool $localized use localised decimal separator
 652   * @param int $displaytype type of display. For example GRADE_DISPLAY_TYPE_REAL, GRADE_DISPLAY_TYPE_PERCENTAGE, GRADE_DISPLAY_TYPE_LETTER
 653   * @param int $decimals The number of decimal places when displaying float values
 654   * @return string
 655   */
 656  function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) {
 657      if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) {
 658          return '';
 659      }
 660  
 661      // no grade yet?
 662      if (is_null($value)) {
 663          return '-';
 664      }
 665  
 666      if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) {
 667          //unknown type??
 668          return '';
 669      }
 670  
 671      if (is_null($displaytype)) {
 672          $displaytype = $grade_item->get_displaytype();
 673      }
 674  
 675      if (is_null($decimals)) {
 676          $decimals = $grade_item->get_decimals();
 677      }
 678  
 679      switch ($displaytype) {
 680          case GRADE_DISPLAY_TYPE_REAL:
 681              return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized);
 682  
 683          case GRADE_DISPLAY_TYPE_PERCENTAGE:
 684              return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized);
 685  
 686          case GRADE_DISPLAY_TYPE_LETTER:
 687              return grade_format_gradevalue_letter($value, $grade_item);
 688  
 689          case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE:
 690              return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
 691                      grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
 692  
 693          case GRADE_DISPLAY_TYPE_REAL_LETTER:
 694              return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
 695                      grade_format_gradevalue_letter($value, $grade_item) . ')';
 696  
 697          case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL:
 698              return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
 699                      grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
 700  
 701          case GRADE_DISPLAY_TYPE_LETTER_REAL:
 702              return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
 703                      grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
 704  
 705          case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE:
 706              return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
 707                      grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
 708  
 709          case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER:
 710              return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
 711                      grade_format_gradevalue_letter($value, $grade_item) . ')';
 712          default:
 713              return '';
 714      }
 715  }
 716  
 717  /**
 718   * Returns a float representation of a grade value
 719   *
 720   * @param float $value The grade value
 721   * @param object $grade_item Grade item object
 722   * @param int $decimals The number of decimal places
 723   * @param bool $localized use localised decimal separator
 724   * @return string
 725   */
 726  function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) {
 727      if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
 728          if (!$scale = $grade_item->load_scale()) {
 729              return get_string('error');
 730          }
 731  
 732          $value = $grade_item->bounded_grade($value);
 733          return format_string($scale->scale_items[$value-1]);
 734  
 735      } else {
 736          return format_float($value, $decimals, $localized);
 737      }
 738  }
 739  
 740  /**
 741   * Returns a percentage representation of a grade value
 742   *
 743   * @param float $value The grade value
 744   * @param object $grade_item Grade item object
 745   * @param int $decimals The number of decimal places
 746   * @param bool $localized use localised decimal separator
 747   * @return string
 748   */
 749  function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) {
 750      $min = $grade_item->grademin;
 751      $max = $grade_item->grademax;
 752      if ($min == $max) {
 753          return '';
 754      }
 755      $value = $grade_item->bounded_grade($value);
 756      $percentage = (($value-$min)*100)/($max-$min);
 757      return format_float($percentage, $decimals, $localized).' %';
 758  }
 759  
 760  /**
 761   * Returns a letter grade representation of a grade value
 762   * The array of grade letters used is produced by {@link grade_get_letters()} using the course context
 763   *
 764   * @param float $value The grade value
 765   * @param object $grade_item Grade item object
 766   * @return string
 767   */
 768  function grade_format_gradevalue_letter($value, $grade_item) {
 769      $context = context_course::instance($grade_item->courseid, IGNORE_MISSING);
 770      if (!$letters = grade_get_letters($context)) {
 771          return ''; // no letters??
 772      }
 773  
 774      if (is_null($value)) {
 775          return '-';
 776      }
 777  
 778      $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
 779      $value = bounded_number(0, $value, 100); // just in case
 780      foreach ($letters as $boundary => $letter) {
 781          if ($value >= $boundary) {
 782              return format_string($letter);
 783          }
 784      }
 785      return '-'; // no match? maybe '' would be more correct
 786  }
 787  
 788  
 789  /**
 790   * Returns grade options for gradebook grade category menu
 791   *
 792   * @param int $courseid The course ID
 793   * @param bool $includenew Include option for new category at array index -1
 794   * @return array of grade categories in course
 795   */
 796  function grade_get_categories_menu($courseid, $includenew=false) {
 797      $result = array();
 798      if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) {
 799          //make sure course category exists
 800          if (!grade_category::fetch_course_category($courseid)) {
 801              debugging('Can not create course grade category!');
 802              return $result;
 803          }
 804          $categories = grade_category::fetch_all(array('courseid'=>$courseid));
 805      }
 806      foreach ($categories as $key=>$category) {
 807          if ($category->is_course_category()) {
 808              $result[$category->id] = get_string('uncategorised', 'grades');
 809              unset($categories[$key]);
 810          }
 811      }
 812      if ($includenew) {
 813          $result[-1] = get_string('newcategory', 'grades');
 814      }
 815      $cats = array();
 816      foreach ($categories as $category) {
 817          $cats[$category->id] = $category->get_name();
 818      }
 819      core_collator::asort($cats);
 820  
 821      return ($result+$cats);
 822  }
 823  
 824  /**
 825   * Returns the array of grade letters to be used in the supplied context
 826   *
 827   * @param object $context Context object or null for defaults
 828   * @return array of grade_boundary (minimum) => letter_string
 829   */
 830  function grade_get_letters($context=null) {
 831      global $DB;
 832  
 833      if (empty($context)) {
 834          //default grading letters
 835          return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F');
 836      }
 837  
 838      static $cache = array();
 839  
 840      if (array_key_exists($context->id, $cache)) {
 841          return $cache[$context->id];
 842      }
 843  
 844      if (count($cache) > 100) {
 845          $cache = array(); // cache size limit
 846      }
 847  
 848      $letters = array();
 849  
 850      $contexts = $context->get_parent_context_ids();
 851      array_unshift($contexts, $context->id);
 852  
 853      foreach ($contexts as $ctxid) {
 854          if ($records = $DB->get_records('grade_letters', array('contextid'=>$ctxid), 'lowerboundary DESC')) {
 855              foreach ($records as $record) {
 856                  $letters[$record->lowerboundary] = $record->letter;
 857              }
 858          }
 859  
 860          if (!empty($letters)) {
 861              $cache[$context->id] = $letters;
 862              return $letters;
 863          }
 864      }
 865  
 866      $letters = grade_get_letters(null);
 867      $cache[$context->id] = $letters;
 868      return $letters;
 869  }
 870  
 871  
 872  /**
 873   * Verify new value of grade item idnumber. Checks for uniqueness of new ID numbers. Old ID numbers are kept intact.
 874   *
 875   * @param string $idnumber string (with magic quotes)
 876   * @param int $courseid ID numbers are course unique only
 877   * @param grade_item $grade_item The grade item this idnumber is associated with
 878   * @param stdClass $cm used for course module idnumbers and items attached to modules
 879   * @return bool true means idnumber ok
 880   */
 881  function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) {
 882      global $DB;
 883  
 884      if ($idnumber == '') {
 885          //we allow empty idnumbers
 886          return true;
 887      }
 888  
 889      // keep existing even when not unique
 890      if ($cm and $cm->idnumber == $idnumber) {
 891          if ($grade_item and $grade_item->itemnumber != 0) {
 892              // grade item with itemnumber > 0 can't have the same idnumber as the main
 893              // itemnumber 0 which is synced with course_modules
 894              return false;
 895          }
 896          return true;
 897      } else if ($grade_item and $grade_item->idnumber == $idnumber) {
 898          return true;
 899      }
 900  
 901      if ($DB->record_exists('course_modules', array('course'=>$courseid, 'idnumber'=>$idnumber))) {
 902          return false;
 903      }
 904  
 905      if ($DB->record_exists('grade_items', array('courseid'=>$courseid, 'idnumber'=>$idnumber))) {
 906          return false;
 907      }
 908  
 909      return true;
 910  }
 911  
 912  /**
 913   * Force final grade recalculation in all course items
 914   *
 915   * @param int $courseid The course ID to recalculate
 916   */
 917  function grade_force_full_regrading($courseid) {
 918      global $DB;
 919      $DB->set_field('grade_items', 'needsupdate', 1, array('courseid'=>$courseid));
 920  }
 921  
 922  /**
 923   * Forces regrading of all site grades. Used when changing site setings
 924   */
 925  function grade_force_site_regrading() {
 926      global $CFG, $DB;
 927      $DB->set_field('grade_items', 'needsupdate', 1);
 928  }
 929  
 930  /**
 931   * Recover a user's grades from grade_grades_history
 932   * @param int $userid the user ID whose grades we want to recover
 933   * @param int $courseid the relevant course
 934   * @return bool true if successful or false if there was an error or no grades could be recovered
 935   */
 936  function grade_recover_history_grades($userid, $courseid) {
 937      global $CFG, $DB;
 938  
 939      if ($CFG->disablegradehistory) {
 940          debugging('Attempting to recover grades when grade history is disabled.');
 941          return false;
 942      }
 943  
 944      //Were grades recovered? Flag to return.
 945      $recoveredgrades = false;
 946  
 947      //Check the user is enrolled in this course
 948      //Dont bother checking if they have a gradeable role. They may get one later so recover
 949      //whatever grades they have now just in case.
 950      $course_context = context_course::instance($courseid);
 951      if (!is_enrolled($course_context, $userid)) {
 952          debugging('Attempting to recover the grades of a user who is deleted or not enrolled. Skipping recover.');
 953          return false;
 954      }
 955  
 956      //Check for existing grades for this user in this course
 957      //Recovering grades when the user already has grades can lead to duplicate indexes and bad data
 958      //In the future we could move the existing grades to the history table then recover the grades from before then
 959      $sql = "SELECT gg.id
 960                FROM {grade_grades} gg
 961                JOIN {grade_items} gi ON gi.id = gg.itemid
 962               WHERE gi.courseid = :courseid AND gg.userid = :userid";
 963      $params = array('userid' => $userid, 'courseid' => $courseid);
 964      if ($DB->record_exists_sql($sql, $params)) {
 965          debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.');
 966          return false;
 967      } else {
 968          //Retrieve the user's old grades
 969          //have history ID as first column to guarantee we a unique first column
 970          $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax,
 971                         h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback,
 972                         h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated
 973                    FROM {grade_grades_history} h
 974                    JOIN (SELECT itemid, MAX(id) AS id
 975                            FROM {grade_grades_history}
 976                           WHERE userid = :userid1
 977                        GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid
 978                    JOIN {grade_items} gi ON gi.id = h.itemid
 979                    JOIN (SELECT itemid, MAX(timemodified) AS tm
 980                            FROM {grade_grades_history}
 981                           WHERE userid = :userid2 AND action = :insertaction
 982                        GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid
 983                   WHERE gi.courseid = :courseid";
 984          $params = array('userid1' => $userid, 'userid2' => $userid , 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid);
 985          $oldgrades = $DB->get_records_sql($sql, $params);
 986  
 987          //now move the old grades to the grade_grades table
 988          foreach ($oldgrades as $oldgrade) {
 989              unset($oldgrade->id);
 990  
 991              $grade = new grade_grade($oldgrade, false);//2nd arg false as dont want to try and retrieve a record from the DB
 992              $grade->insert($oldgrade->source);
 993  
 994              //dont include default empty grades created when activities are created
 995              if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) {
 996                  $recoveredgrades = true;
 997              }
 998          }
 999      }
1000  
1001      //Some activities require manual grade synching (moving grades from the activity into the gradebook)
1002      //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across
1003      grade_grab_course_grades($courseid, null, $userid);
1004  
1005      return $recoveredgrades;
1006  }
1007  
1008  /**
1009   * Updates all final grades in course.
1010   *
1011   * @param int $courseid The course ID
1012   * @param int $userid If specified try to do a quick regrading of the grades of this user only
1013   * @param object $updated_item Optional grade item to be marked for regrading
1014   * @return bool true if ok, array of errors if problems found. Grade item id => error message
1015   */
1016  function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) {
1017  
1018      $course_item = grade_item::fetch_course_item($courseid);
1019  
1020      if ($userid) {
1021          // one raw grade updated for one user
1022          if (empty($updated_item)) {
1023              print_error("cannotbenull", 'debug', '', "updated_item");
1024          }
1025          if ($course_item->needsupdate) {
1026              $updated_item->force_regrading();
1027              return array($course_item->id =>'Can not do fast regrading after updating of raw grades');
1028          }
1029  
1030      } else {
1031          if (!$course_item->needsupdate) {
1032              // nothing to do :-)
1033              return true;
1034          }
1035      }
1036  
1037      // Categories might have to run some processing before we fetch the grade items.
1038      // This gives them a final opportunity to update and mark their children to be updated.
1039      // We need to work on the children categories up to the parent ones, so that, for instance,
1040      // if a category total is updated it will be reflected in the parent category.
1041      $cats = grade_category::fetch_all(array('courseid' => $courseid));
1042      $flatcattree = array();
1043      foreach ($cats as $cat) {
1044          if (!isset($flatcattree[$cat->depth])) {
1045              $flatcattree[$cat->depth] = array();
1046          }
1047          $flatcattree[$cat->depth][] = $cat;
1048      }
1049      krsort($flatcattree);
1050      foreach ($flatcattree as $depth => $cats) {
1051          foreach ($cats as $cat) {
1052              $cat->pre_regrade_final_grades();
1053          }
1054      }
1055  
1056      $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
1057      $depends_on = array();
1058  
1059      // first mark all category and calculated items as needing regrading
1060      // this is slower, but 100% accurate
1061      foreach ($grade_items as $gid=>$gitem) {
1062          if (!empty($updated_item) and $updated_item->id == $gid) {
1063              $grade_items[$gid]->needsupdate = 1;
1064  
1065          } else if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) {
1066              $grade_items[$gid]->needsupdate = 1;
1067          }
1068  
1069          // construct depends_on lookup array
1070          $depends_on[$gid] = $grade_items[$gid]->depends_on();
1071      }
1072  
1073      $errors = array();
1074      $finalids = array();
1075      $gids     = array_keys($grade_items);
1076      $failed = 0;
1077  
1078      while (count($finalids) < count($gids)) { // work until all grades are final or error found
1079          $count = 0;
1080          foreach ($gids as $gid) {
1081              if (in_array($gid, $finalids)) {
1082                  continue; // already final
1083              }
1084  
1085              if (!$grade_items[$gid]->needsupdate) {
1086                  $finalids[] = $gid; // we can make it final - does not need update
1087                  continue;
1088              }
1089  
1090              $doupdate = true;
1091              foreach ($depends_on[$gid] as $did) {
1092                  if (!in_array($did, $finalids)) {
1093                      $doupdate = false;
1094                      continue; // this item depends on something that is not yet in finals array
1095                  }
1096              }
1097  
1098              //oki - let's update, calculate or aggregate :-)
1099              if ($doupdate) {
1100                  $result = $grade_items[$gid]->regrade_final_grades($userid);
1101  
1102                  if ($result === true) {
1103                      $grade_items[$gid]->regrading_finished();
1104                      $grade_items[$gid]->check_locktime(); // do the locktime item locking
1105                      $count++;
1106                      $finalids[] = $gid;
1107  
1108                  } else {
1109                      $grade_items[$gid]->force_regrading();
1110                      $errors[$gid] = $result;
1111                  }
1112              }
1113          }
1114  
1115          if ($count == 0) {
1116              $failed++;
1117          } else {
1118              $failed = 0;
1119          }
1120  
1121          if ($failed > 1) {
1122              foreach($gids as $gid) {
1123                  if (in_array($gid, $finalids)) {
1124                      continue; // this one is ok
1125                  }
1126                  $grade_items[$gid]->force_regrading();
1127                  $errors[$grade_items[$gid]->id] = get_string('errorcalculationbroken', 'grades');
1128              }
1129              break; // Found error.
1130          }
1131      }
1132  
1133      if (count($errors) == 0) {
1134          if (empty($userid)) {
1135              // do the locktime locking of grades, but only when doing full regrading
1136              grade_grade::check_locktime_all($gids);
1137          }
1138          return true;
1139      } else {
1140          return $errors;
1141      }
1142  }
1143  
1144  /**
1145   * Refetches grade data from course activities
1146   *
1147   * @param int $courseid The course ID
1148   * @param string $modname Limit the grade fetch to a single module type. For example 'forum'
1149   * @param int $userid limit the grade fetch to a single user
1150   */
1151  function grade_grab_course_grades($courseid, $modname=null, $userid=0) {
1152      global $CFG, $DB;
1153  
1154      if ($modname) {
1155          $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
1156                    FROM {".$modname."} a, {course_modules} cm, {modules} m
1157                   WHERE m.name=:modname AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid";
1158          $params = array('modname'=>$modname, 'courseid'=>$courseid);
1159  
1160          if ($modinstances = $DB->get_records_sql($sql, $params)) {
1161              foreach ($modinstances as $modinstance) {
1162                  grade_update_mod_grades($modinstance, $userid);
1163              }
1164          }
1165          return;
1166      }
1167  
1168      if (!$mods = core_component::get_plugin_list('mod') ) {
1169          print_error('nomodules', 'debug');
1170      }
1171  
1172      foreach ($mods as $mod => $fullmod) {
1173          if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
1174              continue;
1175          }
1176  
1177          // include the module lib once
1178          if (file_exists($fullmod.'/lib.php')) {
1179              // get all instance of the activity
1180              $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
1181                        FROM {".$mod."} a, {course_modules} cm, {modules} m
1182                       WHERE m.name=:mod AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid";
1183              $params = array('mod'=>$mod, 'courseid'=>$courseid);
1184  
1185              if ($modinstances = $DB->get_records_sql($sql, $params)) {
1186                  foreach ($modinstances as $modinstance) {
1187                      grade_update_mod_grades($modinstance, $userid);
1188                  }
1189              }
1190          }
1191      }
1192  }
1193  
1194  /**
1195   * Force full update of module grades in central gradebook
1196   *
1197   * @param object $modinstance Module object with extra cmidnumber and modname property
1198   * @param int $userid Optional user ID if limiting the update to a single user
1199   * @return bool True if success
1200   */
1201  function grade_update_mod_grades($modinstance, $userid=0) {
1202      global $CFG, $DB;
1203  
1204      $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname;
1205      if (!file_exists($fullmod.'/lib.php')) {
1206          debugging('missing lib.php file in module ' . $modinstance->modname);
1207          return false;
1208      }
1209      include_once($fullmod.'/lib.php');
1210  
1211      $updateitemfunc   = $modinstance->modname.'_grade_item_update';
1212      $updategradesfunc = $modinstance->modname.'_update_grades';
1213  
1214      if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) {
1215          //new grading supported, force updating of grades
1216          $updateitemfunc($modinstance);
1217          $updategradesfunc($modinstance, $userid);
1218  
1219      } else {
1220          // Module does not support grading?
1221      }
1222  
1223      return true;
1224  }
1225  
1226  /**
1227   * Remove grade letters for given context
1228   *
1229   * @param context $context The context
1230   * @param bool $showfeedback If true a success notification will be displayed
1231   */
1232  function remove_grade_letters($context, $showfeedback) {
1233      global $DB, $OUTPUT;
1234  
1235      $strdeleted = get_string('deleted');
1236  
1237      $DB->delete_records('grade_letters', array('contextid'=>$context->id));
1238      if ($showfeedback) {
1239          echo $OUTPUT->notification($strdeleted.' - '.get_string('letters', 'grades'), 'notifysuccess');
1240      }
1241  }
1242  
1243  /**
1244   * Remove all grade related course data
1245   * Grade history is kept
1246   *
1247   * @param int $courseid The course ID
1248   * @param bool $showfeedback If true success notifications will be displayed
1249   */
1250  function remove_course_grades($courseid, $showfeedback) {
1251      global $DB, $OUTPUT;
1252  
1253      $fs = get_file_storage();
1254      $strdeleted = get_string('deleted');
1255  
1256      $course_category = grade_category::fetch_course_category($courseid);
1257      $course_category->delete('coursedelete');
1258      $fs->delete_area_files(context_course::instance($courseid)->id, 'grade', 'feedback');
1259      if ($showfeedback) {
1260          echo $OUTPUT->notification($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'), 'notifysuccess');
1261      }
1262  
1263      if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) {
1264          foreach ($outcomes as $outcome) {
1265              $outcome->delete('coursedelete');
1266          }
1267      }
1268      $DB->delete_records('grade_outcomes_courses', array('courseid'=>$courseid));
1269      if ($showfeedback) {
1270          echo $OUTPUT->notification($strdeleted.' - '.get_string('outcomes', 'grades'), 'notifysuccess');
1271      }
1272  
1273      if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) {
1274          foreach ($scales as $scale) {
1275              $scale->delete('coursedelete');
1276          }
1277      }
1278      if ($showfeedback) {
1279          echo $OUTPUT->notification($strdeleted.' - '.get_string('scales'), 'notifysuccess');
1280      }
1281  
1282      $DB->delete_records('grade_settings', array('courseid'=>$courseid));
1283      if ($showfeedback) {
1284          echo $OUTPUT->notification($strdeleted.' - '.get_string('settings', 'grades'), 'notifysuccess');
1285      }
1286  }
1287  
1288  /**
1289   * Called when course category is deleted
1290   * Cleans the gradebook of associated data
1291   *
1292   * @param int $categoryid The course category id
1293   * @param int $newparentid If empty everything is deleted. Otherwise the ID of the category where content moved
1294   * @param bool $showfeedback print feedback
1295   */
1296  function grade_course_category_delete($categoryid, $newparentid, $showfeedback) {
1297      global $DB;
1298  
1299      $context = context_coursecat::instance($categoryid);
1300      $DB->delete_records('grade_letters', array('contextid'=>$context->id));
1301  }
1302  
1303  /**
1304   * Does gradebook cleanup when a module is uninstalled
1305   * Deletes all associated grade items
1306   *
1307   * @param string $modname The grade item module name to remove. For example 'forum'
1308   */
1309  function grade_uninstalled_module($modname) {
1310      global $CFG, $DB;
1311  
1312      $sql = "SELECT *
1313                FROM {grade_items}
1314               WHERE itemtype='mod' AND itemmodule=?";
1315  
1316      // go all items for this module and delete them including the grades
1317      $rs = $DB->get_recordset_sql($sql, array($modname));
1318      foreach ($rs as $item) {
1319          $grade_item = new grade_item($item, false);
1320          $grade_item->delete('moduninstall');
1321      }
1322      $rs->close();
1323  }
1324  
1325  /**
1326   * Deletes all of a user's grade data from gradebook
1327   *
1328   * @param int $userid The user whose grade data should be deleted
1329   */
1330  function grade_user_delete($userid) {
1331      if ($grades = grade_grade::fetch_all(array('userid'=>$userid))) {
1332          foreach ($grades as $grade) {
1333              $grade->delete('userdelete');
1334          }
1335      }
1336  }
1337  
1338  /**
1339   * Purge course data when user unenrolls from a course
1340   *
1341   * @param int $courseid The ID of the course the user has unenrolled from
1342   * @param int $userid The ID of the user unenrolling
1343   */
1344  function grade_user_unenrol($courseid, $userid) {
1345      if ($items = grade_item::fetch_all(array('courseid'=>$courseid))) {
1346          foreach ($items as $item) {
1347              if ($grades = grade_grade::fetch_all(array('userid'=>$userid, 'itemid'=>$item->id))) {
1348                  foreach ($grades as $grade) {
1349                      $grade->delete('userdelete');
1350                  }
1351              }
1352          }
1353      }
1354  }
1355  
1356  /**
1357   * Grading cron job. Performs background clean up on the gradebook
1358   */
1359  function grade_cron() {
1360      global $CFG, $DB;
1361  
1362      $now = time();
1363  
1364      $sql = "SELECT i.*
1365                FROM {grade_items} i
1366               WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < ? AND EXISTS (
1367                  SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
1368  
1369      // go through all courses that have proper final grades and lock them if needed
1370      $rs = $DB->get_recordset_sql($sql, array($now));
1371      foreach ($rs as $item) {
1372          $grade_item = new grade_item($item, false);
1373          $grade_item->locked = $now;
1374          $grade_item->update('locktime');
1375      }
1376      $rs->close();
1377  
1378      $grade_inst = new grade_grade();
1379      $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
1380  
1381      $sql = "SELECT $fields
1382                FROM {grade_grades} g, {grade_items} i
1383               WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < ? AND g.itemid=i.id AND EXISTS (
1384                  SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
1385  
1386      // go through all courses that have proper final grades and lock them if needed
1387      $rs = $DB->get_recordset_sql($sql, array($now));
1388      foreach ($rs as $grade) {
1389          $grade_grade = new grade_grade($grade, false);
1390          $grade_grade->locked = $now;
1391          $grade_grade->update('locktime');
1392      }
1393      $rs->close();
1394  
1395      //TODO: do not run this cleanup every cron invocation
1396      // cleanup history tables
1397      if (!empty($CFG->gradehistorylifetime)) {  // value in days
1398          $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24);
1399          $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_history', 'grade_grades_history', 'scale_history');
1400          foreach ($tables as $table) {
1401              if ($DB->delete_records_select($table, "timemodified < ?", array($histlifetime))) {
1402                  mtrace("    Deleted old grade history records from '$table'");
1403              }
1404          }
1405      }
1406  }
1407  
1408  /**
1409   * Reset all course grades, refetch from the activities and recalculate
1410   *
1411   * @param int $courseid The course to reset
1412   * @return bool success
1413   */
1414  function grade_course_reset($courseid) {
1415  
1416      // no recalculations
1417      grade_force_full_regrading($courseid);
1418  
1419      $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
1420      foreach ($grade_items as $gid=>$grade_item) {
1421          $grade_item->delete_all_grades('reset');
1422      }
1423  
1424      //refetch all grades
1425      grade_grab_course_grades($courseid);
1426  
1427      // recalculate all grades
1428      grade_regrade_final_grades($courseid);
1429      return true;
1430  }
1431  
1432  /**
1433   * Convert a number to 5 decimal point float, an empty string or a null db compatible format
1434   * (we need this to decide if db value changed)
1435   *
1436   * @param mixed $number The number to convert
1437   * @return mixed float or null
1438   */
1439  function grade_floatval($number) {
1440      if (is_null($number) or $number === '') {
1441          return null;
1442      }
1443      // we must round to 5 digits to get the same precision as in 10,5 db fields
1444      // note: db rounding for 10,5 is different from php round() function
1445      return round($number, 5);
1446  }
1447  
1448  /**
1449   * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()}. Nulls accepted too.
1450   * Used for determining if a database update is required
1451   *
1452   * @param float $f1 Float one to compare
1453   * @param float $f2 Float two to compare
1454   * @return bool True if the supplied values are different
1455   */
1456  function grade_floats_different($f1, $f2) {
1457      // note: db rounding for 10,5 is different from php round() function
1458      return (grade_floatval($f1) !== grade_floatval($f2));
1459  }
1460  
1461  /**
1462   * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()}
1463   *
1464   * Do not use rounding for 10,5 at the database level as the results may be
1465   * different from php round() function.
1466   *
1467   * @since Moodle 2.0
1468   * @param float $f1 Float one to compare
1469   * @param float $f2 Float two to compare
1470   * @return bool True if the values should be considered as the same grades
1471   */
1472  function grade_floats_equal($f1, $f2) {
1473      return (grade_floatval($f1) === grade_floatval($f2));
1474  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1