Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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   * Core grades external functions
  19   *
  20   * @package    core_grades
  21   * @copyright  2012 Andrew Davis
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   * @since Moodle 2.7
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die;
  27  
  28  require_once("$CFG->libdir/externallib.php");
  29  require_once("$CFG->libdir/gradelib.php");
  30  require_once("$CFG->dirroot/grade/edit/tree/lib.php");
  31  require_once("$CFG->dirroot/grade/querylib.php");
  32  
  33  /**
  34   * core grades functions
  35   */
  36  class core_grades_external extends external_api {
  37      /**
  38       * Returns description of method parameters
  39       *
  40       * @return external_function_parameters
  41       * @since Moodle 2.7
  42       * @deprecated Moodle 3.2 MDL-51373 - Please do not call this function any more.
  43       * @see gradereport_user_external::get_grade_items for a similar function
  44       */
  45      public static function get_grades_parameters() {
  46          return new external_function_parameters(
  47              array(
  48                  'courseid' => new external_value(PARAM_INT, 'id of course'),
  49                  'component' => new external_value(
  50                      PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''),
  51                  'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null),
  52                  'userids' => new external_multiple_structure(
  53                      new external_value(PARAM_INT, 'user ID'),
  54                      'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array()
  55                  )
  56              )
  57          );
  58      }
  59  
  60      /**
  61       * Returns student course total grade and grades for activities.
  62       * This function does not return category or manual items.
  63       * This function is suitable for managers or teachers not students.
  64       *
  65       * @param  int $courseid        Course id
  66       * @param  string $component    Component name
  67       * @param  int $activityid      Activity id
  68       * @param  array  $userids      Array of user ids
  69       * @return array                Array of grades
  70       * @since Moodle 2.7
  71       * @deprecated Moodle 3.2 MDL-51373 - Please do not call this function any more.
  72       * @see gradereport_user_external::get_grade_items for a similar function
  73       */
  74      public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) {
  75          global $CFG, $USER, $DB;
  76  
  77          $params = self::validate_parameters(self::get_grades_parameters(),
  78              array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids));
  79  
  80          $gradesarray = array(
  81              'items'     => array(),
  82              'outcomes'  => array()
  83          );
  84  
  85          $coursecontext = context_course::instance($params['courseid']);
  86  
  87          try {
  88              self::validate_context($coursecontext);
  89          } catch (Exception $e) {
  90              $exceptionparam = new stdClass();
  91              $exceptionparam->message = $e->getMessage();
  92              $exceptionparam->courseid = $params['courseid'];
  93              throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
  94          }
  95  
  96          require_capability('moodle/grade:viewhidden', $coursecontext);
  97  
  98          $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
  99  
 100          $access = false;
 101          if (has_capability('moodle/grade:viewall', $coursecontext)) {
 102              // Can view all user's grades in this course.
 103              $access = true;
 104  
 105          } else if ($course->showgrades && count($params['userids']) == 1) {
 106              // Course showgrades == students/parents can access grades.
 107  
 108              if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) {
 109                  // Student can view their own grades in this course.
 110                  $access = true;
 111  
 112              } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) {
 113                  // User can view the grades of this user. Parent most probably.
 114                  $access = true;
 115              }
 116          }
 117  
 118          if (!$access) {
 119              throw new moodle_exception('nopermissiontoviewgrades', 'error');
 120          }
 121  
 122          $itemtype = null;
 123          $itemmodule = null;
 124          $iteminstance = null;
 125  
 126          if (!empty($params['component'])) {
 127              list($itemtype, $itemmodule) = normalize_component($params['component']);
 128          }
 129  
 130          $cm = null;
 131          if (!empty($itemmodule) && !empty($params['activityid'])) {
 132              if (!$cm = get_coursemodule_from_id($itemmodule, $params['activityid'])) {
 133                  throw new moodle_exception('invalidcoursemodule');
 134              }
 135              $iteminstance = $cm->instance;
 136          }
 137  
 138          // Load all the module info.
 139          $modinfo = get_fast_modinfo($params['courseid']);
 140          $activityinstances = $modinfo->get_instances();
 141  
 142          $gradeparams = array('courseid' => $params['courseid']);
 143          if (!empty($itemtype)) {
 144              $gradeparams['itemtype'] = $itemtype;
 145          }
 146          if (!empty($itemmodule)) {
 147              $gradeparams['itemmodule'] = $itemmodule;
 148          }
 149          if (!empty($iteminstance)) {
 150              $gradeparams['iteminstance'] = $iteminstance;
 151          }
 152  
 153          if ($activitygrades = grade_item::fetch_all($gradeparams)) {
 154              $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid']));
 155  
 156              foreach ($activitygrades as $activitygrade) {
 157  
 158                  if ($activitygrade->itemtype != 'course' and $activitygrade->itemtype != 'mod') {
 159                      // This function currently only supports course and mod grade items. Manual and category not supported.
 160                      continue;
 161                  }
 162  
 163                  $context = $coursecontext;
 164  
 165                  if ($activitygrade->itemtype == 'course') {
 166                      $item = grade_get_course_grades($course->id, $params['userids']);
 167                      $item->itemnumber = 0;
 168  
 169                      $grades = new stdClass;
 170                      $grades->items = array($item);
 171                      $grades->outcomes = array();
 172  
 173                  } else {
 174                      $cm = $activityinstances[$activitygrade->itemmodule][$activitygrade->iteminstance];
 175                      $instance = $cm->instance;
 176                      $context = context_module::instance($cm->id, IGNORE_MISSING);
 177  
 178                      $grades = grade_get_grades($params['courseid'], $activitygrade->itemtype,
 179                                                  $activitygrade->itemmodule, $instance, $params['userids']);
 180                  }
 181  
 182                  // Convert from objects to arrays so all web service clients are supported.
 183                  // While we're doing that we also remove grades the current user can't see due to hiding.
 184                  foreach ($grades->items as $gradeitem) {
 185                      // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID.
 186                      $gradeiteminstance = self::get_grade_item(
 187                          $course->id, $activitygrade->itemtype, $activitygrade->itemmodule, $activitygrade->iteminstance, 0);
 188                      if (!$canviewhidden && $gradeiteminstance->is_hidden()) {
 189                          continue;
 190                      }
 191  
 192                      // Format mixed bool/integer parameters.
 193                      $gradeitem->hidden = (empty($gradeitem->hidden)) ? 0 : $gradeitem->hidden;
 194                      $gradeitem->locked = (empty($gradeitem->locked)) ? 0 : $gradeitem->locked;
 195  
 196                      $gradeitemarray = (array)$gradeitem;
 197                      $gradeitemarray['grades'] = array();
 198  
 199                      if (!empty($gradeitem->grades)) {
 200                          foreach ($gradeitem->grades as $studentid => $studentgrade) {
 201                              if (!$canviewhidden) {
 202                                  // Need to load the grade_grade object to check visibility.
 203                                  $gradegradeinstance = grade_grade::fetch(
 204                                      array(
 205                                          'userid' => $studentid,
 206                                          'itemid' => $gradeiteminstance->id
 207                                      )
 208                                  );
 209                                  // The grade grade may be legitimately missing if the student has no grade.
 210                                  if (!empty($gradegradeinstance) && $gradegradeinstance->is_hidden()) {
 211                                      continue;
 212                                  }
 213                              }
 214  
 215                              // Format mixed bool/integer parameters.
 216                              $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
 217                              $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
 218                              $studentgrade->overridden = (empty($studentgrade->overridden)) ? 0 : $studentgrade->overridden;
 219  
 220                              if ($gradeiteminstance->itemtype != 'course' and !empty($studentgrade->feedback)) {
 221                                  list($studentgrade->feedback, $studentgrade->feedbackformat) =
 222                                      external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
 223                                      $context->id, $params['component'], 'feedback', null);
 224                              }
 225  
 226                              $gradeitemarray['grades'][$studentid] = (array)$studentgrade;
 227                              // Add the student ID as some WS clients can't access the array key.
 228                              $gradeitemarray['grades'][$studentid]['userid'] = $studentid;
 229                          }
 230                      }
 231  
 232                      if ($gradeiteminstance->itemtype == 'course') {
 233                          $gradesarray['items']['course'] = $gradeitemarray;
 234                          $gradesarray['items']['course']['activityid'] = 'course';
 235                      } else {
 236                          $gradesarray['items'][$cm->id] = $gradeitemarray;
 237                          // Add the activity ID as some WS clients can't access the array key.
 238                          $gradesarray['items'][$cm->id]['activityid'] = $cm->id;
 239                      }
 240                  }
 241  
 242                  foreach ($grades->outcomes as $outcome) {
 243                      // Format mixed bool/integer parameters.
 244                      $outcome->hidden = (empty($outcome->hidden)) ? 0 : $outcome->hidden;
 245                      $outcome->locked = (empty($outcome->locked)) ? 0 : $outcome->locked;
 246  
 247                      $gradesarray['outcomes'][$cm->id] = (array)$outcome;
 248                      $gradesarray['outcomes'][$cm->id]['activityid'] = $cm->id;
 249  
 250                      $gradesarray['outcomes'][$cm->id]['grades'] = array();
 251                      if (!empty($outcome->grades)) {
 252                          foreach ($outcome->grades as $studentid => $studentgrade) {
 253                              if (!$canviewhidden) {
 254                                  // Need to load the grade_grade object to check visibility.
 255                                  $gradeiteminstance = self::get_grade_item($course->id, $activitygrade->itemtype,
 256                                                                             $activitygrade->itemmodule, $activitygrade->iteminstance,
 257                                                                             $activitygrade->itemnumber);
 258                                  $gradegradeinstance = grade_grade::fetch(
 259                                      array(
 260                                          'userid' => $studentid,
 261                                          'itemid' => $gradeiteminstance->id
 262                                      )
 263                                  );
 264                                  // The grade grade may be legitimately missing if the student has no grade.
 265                                  if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) {
 266                                      continue;
 267                                  }
 268                              }
 269  
 270                              // Format mixed bool/integer parameters.
 271                              $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
 272                              $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
 273  
 274                              if (!empty($studentgrade->feedback)) {
 275                                  list($studentgrade->feedback, $studentgrade->feedbackformat) =
 276                                      external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
 277                                      $context->id, $params['component'], 'feedback', null);
 278                              }
 279  
 280                              $gradesarray['outcomes'][$cm->id]['grades'][$studentid] = (array)$studentgrade;
 281  
 282                              // Add the student ID into the grade structure as some WS clients can't access the key.
 283                              $gradesarray['outcomes'][$cm->id]['grades'][$studentid]['userid'] = $studentid;
 284                          }
 285                      }
 286                  }
 287              }
 288          }
 289  
 290          return $gradesarray;
 291      }
 292  
 293      /**
 294       * Get a grade item
 295       * @param  int $courseid        Course id
 296       * @param  string $itemtype     Item type
 297       * @param  string $itemmodule   Item module
 298       * @param  int $iteminstance    Item instance
 299       * @param  int $itemnumber      Item number
 300       * @return grade_item           A grade_item instance
 301       * @deprecated Moodle 3.2 MDL-51373 - Please do not call this function any more.
 302       * @see gradereport_user_external::get_grade_items for a similar function
 303       */
 304      private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) {
 305          $gradeiteminstance = null;
 306          if ($itemtype == 'course') {
 307              $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype));
 308          } else {
 309              $gradeiteminstance = grade_item::fetch(
 310                  array('courseid' => $courseid, 'itemtype' => $itemtype,
 311                      'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber));
 312          }
 313          return $gradeiteminstance;
 314      }
 315  
 316      /**
 317       * Returns description of method result value
 318       *
 319       * @return external_description
 320       * @since Moodle 2.7
 321       * @deprecated Moodle 3.2 MDL-51373 - Please do not call this function any more.
 322       * @see gradereport_user_external::get_grade_items for a similar function
 323       */
 324      public static function get_grades_returns() {
 325          return new external_single_structure(
 326              array(
 327                  'items'  => new external_multiple_structure(
 328                      new external_single_structure(
 329                          array(
 330                              'activityid' => new external_value(
 331                                  PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
 332                              'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
 333                              'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
 334                              'name' => new external_value(PARAM_RAW, 'The module name'),
 335                              'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'),
 336                              'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'),
 337                              'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'),
 338                              'locked' => new external_value(PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
 339                              'hidden' => new external_value(PARAM_INT, '0 means not hidden, > 1 is a date to hide until'),
 340                              'grades' => new external_multiple_structure(
 341                                  new external_single_structure(
 342                                      array(
 343                                          'userid' => new external_value(
 344                                              PARAM_INT, 'Student ID'),
 345                                          'grade' => new external_value(
 346                                              PARAM_FLOAT, 'Student grade'),
 347                                          'locked' => new external_value(
 348                                              PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
 349                                          'hidden' => new external_value(
 350                                              PARAM_INT, '0 means not hidden, 1 hidden, > 1 is a date to hide until'),
 351                                          'overridden' => new external_value(
 352                                              PARAM_INT, '0 means not overridden, > 1 means overridden'),
 353                                          'feedback' => new external_value(
 354                                              PARAM_RAW, 'Feedback from the grader'),
 355                                          'feedbackformat' => new external_value(
 356                                              PARAM_INT, 'The format of the feedback'),
 357                                          'usermodified' => new external_value(
 358                                              PARAM_INT, 'The ID of the last user to modify this student grade'),
 359                                          'datesubmitted' => new external_value(
 360                                              PARAM_INT, 'A timestamp indicating when the student submitted the activity'),
 361                                          'dategraded' => new external_value(
 362                                              PARAM_INT, 'A timestamp indicating when the assignment was grades'),
 363                                          'str_grade' => new external_value(
 364                                              PARAM_RAW, 'A string representation of the grade'),
 365                                          'str_long_grade' => new external_value(
 366                                              PARAM_RAW, 'A nicely formatted string representation of the grade'),
 367                                          'str_feedback' => new external_value(
 368                                              PARAM_RAW, 'A formatted string representation of the feedback from the grader'),
 369                                      )
 370                                  )
 371                              ),
 372                          )
 373                      )
 374                  ),
 375                  'outcomes'  => new external_multiple_structure(
 376                      new external_single_structure(
 377                          array(
 378                              'activityid' => new external_value(
 379                                  PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'),
 380                              'itemnumber'  => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'),
 381                              'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'),
 382                              'name' => new external_value(PARAM_RAW, 'The module name'),
 383                              'locked' => new external_value(PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
 384                              'hidden' => new external_value(PARAM_INT, '0 means not hidden, > 1 is a date to hide until'),
 385                              'grades' => new external_multiple_structure(
 386                                  new external_single_structure(
 387                                      array(
 388                                          'userid' => new external_value(
 389                                              PARAM_INT, 'Student ID'),
 390                                          'grade' => new external_value(
 391                                              PARAM_FLOAT, 'Student grade'),
 392                                          'locked' => new external_value(
 393                                              PARAM_INT, '0 means not locked, > 1 is a date to lock until'),
 394                                          'hidden' => new external_value(
 395                                              PARAM_INT, '0 means not hidden, 1 hidden, > 1 is a date to hide until'),
 396                                          'feedback' => new external_value(
 397                                              PARAM_RAW, 'Feedback from the grader'),
 398                                          'feedbackformat' => new external_value(
 399                                              PARAM_INT, 'The feedback format'),
 400                                          'usermodified' => new external_value(
 401                                              PARAM_INT, 'The ID of the last user to modify this student grade'),
 402                                          'str_grade' => new external_value(
 403                                              PARAM_RAW, 'A string representation of the grade'),
 404                                          'str_feedback' => new external_value(
 405                                              PARAM_RAW, 'A formatted string representation of the feedback from the grader'),
 406                                      )
 407                                  )
 408                              ),
 409                          )
 410                      ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL
 411                  )
 412              )
 413          );
 414  
 415      }
 416  
 417      /**
 418       * Marking the method as deprecated.
 419       *
 420       * @return bool
 421       */
 422      public static function get_grades_is_deprecated() {
 423          return true;
 424      }
 425  
 426      /**
 427       * Returns description of method parameters
 428       *
 429       * @return external_function_parameters
 430       * @since Moodle 2.7
 431       */
 432      public static function update_grades_parameters() {
 433          return new external_function_parameters(
 434              array(
 435                  'source' => new external_value(PARAM_TEXT, 'The source of the grade update'),
 436                  'courseid' => new external_value(PARAM_INT, 'id of course'),
 437                  'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'),
 438                  'activityid' => new external_value(PARAM_INT, 'The activity ID'),
 439                  'itemnumber' => new external_value(
 440                      PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'),
 441                  'grades' => new external_multiple_structure(
 442                      new external_single_structure(
 443                          array(
 444                              'studentid' => new external_value(PARAM_INT, 'Student ID'),
 445                              'grade' => new external_value(PARAM_FLOAT, 'Student grade'),
 446                              'str_feedback' => new external_value(
 447                                  PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL),
 448                          )
 449                  ), 'Any student grades to alter', VALUE_DEFAULT, array()),
 450                  'itemdetails' => new external_single_structure(
 451                      array(
 452                          'itemname' => new external_value(
 453                              PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL),
 454                          'idnumber' => new external_value(
 455                              PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL),
 456                          'gradetype' => new external_value(
 457                              PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL),
 458                          'grademax' => new external_value(
 459                              PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL),
 460                          'grademin' => new external_value(
 461                              PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL),
 462                          'scaleid' => new external_value(
 463                              PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL),
 464                          'multfactor' => new external_value(
 465                              PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL),
 466                          'plusfactor' => new external_value(
 467                              PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL),
 468                          'deleted' => new external_value(
 469                              PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL),
 470                          'hidden' => new external_value(
 471                              PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL),
 472                      ), 'Any grade item settings to alter', VALUE_DEFAULT, array()
 473                  )
 474              )
 475          );
 476      }
 477  
 478      /**
 479       * Update a grade item and, optionally, student grades
 480       *
 481       * @param  string $source       The source of the grade update
 482       * @param  int $courseid        The course id
 483       * @param  string $component    Component name
 484       * @param  int $activityid      The activity id
 485       * @param  int $itemnumber      The item number
 486       * @param  array  $grades      Array of grades
 487       * @param  array  $itemdetails Array of item details
 488       * @return int                  A status flag
 489       * @since Moodle 2.7
 490       */
 491      public static function update_grades($source, $courseid, $component, $activityid,
 492          $itemnumber, $grades = array(), $itemdetails = array()) {
 493          global $CFG;
 494  
 495          $params = self::validate_parameters(
 496              self::update_grades_parameters(),
 497              array(
 498                  'source' => $source,
 499                  'courseid' => $courseid,
 500                  'component' => $component,
 501                  'activityid' => $activityid,
 502                  'itemnumber' => $itemnumber,
 503                  'grades' => $grades,
 504                  'itemdetails' => $itemdetails
 505              )
 506          );
 507  
 508          list($itemtype, $itemmodule) = normalize_component($params['component']);
 509  
 510          if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
 511              throw new moodle_exception('invalidcoursemodule');
 512          }
 513          $iteminstance = $cm->instance;
 514  
 515          $coursecontext = context_course::instance($params['courseid']);
 516  
 517          try {
 518              self::validate_context($coursecontext);
 519          } catch (Exception $e) {
 520              $exceptionparam = new stdClass();
 521              $exceptionparam->message = $e->getMessage();
 522              $exceptionparam->courseid = $params['courseid'];
 523              throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
 524          }
 525  
 526          $hidinggrades = false;
 527          $editinggradeitem = false;
 528          $editinggrades = false;
 529  
 530          $gradestructure = array();
 531          foreach ($grades as $grade) {
 532              $editinggrades = true;
 533              $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']);
 534          }
 535          if (!empty($params['itemdetails'])) {
 536              if (isset($params['itemdetails']['hidden'])) {
 537                  $hidinggrades = true;
 538              } else {
 539                  $editinggradeitem = true;
 540              }
 541          }
 542  
 543          if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) {
 544              throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
 545                  'moodle/grade:manage required to edit grade information');
 546          }
 547          if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) &&
 548              !has_capability('moodle/grade:hide', $coursecontext)) {
 549              throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
 550                  'moodle/grade:hide required to hide grade items');
 551          }
 552          if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) {
 553              throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null,
 554                  'moodle/grade:edit required to edit grades');
 555          }
 556  
 557          return grade_update($params['source'], $params['courseid'], $itemtype,
 558              $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']);
 559      }
 560  
 561      /**
 562       * Returns description of method result value
 563       *
 564       * @return external_description
 565       * @since Moodle 2.7
 566       */
 567      public static function update_grades_returns() {
 568          return new external_value(
 569              PARAM_INT,
 570              'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED
 571              as defined in lib/grade/constants.php'
 572          );
 573      }
 574  
 575      /**
 576       * Returns description of method parameters
 577       *
 578       * @return external_function_parameters
 579       * @since Moodle 3.10
 580       */
 581      public static function create_gradecategory_parameters() {
 582          return new external_function_parameters(
 583              [
 584                  'courseid' => new external_value(PARAM_INT, 'id of course', VALUE_REQUIRED),
 585                  'fullname' => new external_value(PARAM_TEXT, 'fullname of category', VALUE_REQUIRED),
 586                  'options' => new external_single_structure([
 587                      'aggregation' => new external_value(PARAM_INT, 'aggregation method', VALUE_OPTIONAL),
 588                      'aggregateonlygraded' => new external_value(PARAM_BOOL, 'exclude empty grades', VALUE_OPTIONAL),
 589                      'aggregateoutcomes' => new external_value(PARAM_BOOL, 'aggregate outcomes', VALUE_OPTIONAL),
 590                      'droplow' => new external_value(PARAM_INT, 'drop low grades', VALUE_OPTIONAL),
 591                      'itemname' => new external_value(PARAM_TEXT, 'the category total name', VALUE_OPTIONAL),
 592                      'iteminfo' => new external_value(PARAM_TEXT, 'the category iteminfo', VALUE_OPTIONAL),
 593                      'idnumber' => new external_value(PARAM_TEXT, 'the category idnumber', VALUE_OPTIONAL),
 594                      'gradetype' => new external_value(PARAM_INT, 'the grade type', VALUE_OPTIONAL),
 595                      'grademax' => new external_value(PARAM_INT, 'the grade max', VALUE_OPTIONAL),
 596                      'grademin' => new external_value(PARAM_INT, 'the grade min', VALUE_OPTIONAL),
 597                      'gradepass' => new external_value(PARAM_INT, 'the grade to pass', VALUE_OPTIONAL),
 598                      'display' => new external_value(PARAM_INT, 'the display type', VALUE_OPTIONAL),
 599                      'decimals' => new external_value(PARAM_INT, 'the decimal count', VALUE_OPTIONAL),
 600                      'hiddenuntil' => new external_value(PARAM_INT, 'grades hidden until', VALUE_OPTIONAL),
 601                      'locktime' => new external_value(PARAM_INT, 'lock grades after', VALUE_OPTIONAL),
 602                      'weightoverride' => new external_value(PARAM_BOOL, 'weight adjusted', VALUE_OPTIONAL),
 603                      'aggregationcoef2' => new external_value(PARAM_RAW, 'weight coefficient', VALUE_OPTIONAL),
 604                      'parentcategoryid' => new external_value(PARAM_INT, 'The parent category id', VALUE_OPTIONAL),
 605                      'parentcategoryidnumber' => new external_value(PARAM_TEXT, 'the parent category idnumber', VALUE_OPTIONAL),
 606                  ], 'optional category data', VALUE_DEFAULT, [])
 607              ]
 608          );
 609      }
 610  
 611      /**
 612       * Creates a gradecategory inside of the specified course.
 613       *
 614       * @param int $courseid the courseid to create the gradecategory in.
 615       * @param string $fullname the fullname of the grade category to create.
 616       * @param array $options array of options to set.
 617       *
 618       * @return array array of created categoryid and warnings.
 619       */
 620      public static function create_gradecategory(int $courseid, string $fullname, array $options) {
 621          global $CFG, $DB;
 622  
 623          $params = self::validate_parameters(self::create_gradecategory_parameters(),
 624              ['courseid' => $courseid, 'fullname' => $fullname, 'options' => $options]);
 625  
 626          // Now params are validated, update the references.
 627          $courseid = $params['courseid'];
 628          $fullname = $params['fullname'];
 629          $options = $params['options'];
 630  
 631          // Check that the context and permissions are OK.
 632          $context = context_course::instance($courseid);
 633          self::validate_context($context);
 634          require_capability('moodle/grade:manage', $context);
 635  
 636          $defaultparentcat = new grade_category(['courseid' => $courseid, 'depth' => 1], true);
 637  
 638          // Setup default data so WS call needs to contain only data to set.
 639          // This is not done in the Parameters, so that the array of options can be optional.
 640          $data = [
 641              'fullname' => $fullname,
 642              'aggregation' => grade_get_setting($courseid, 'displaytype', $CFG->grade_displaytype),
 643              'aggregateonlygraded' => 1,
 644              'aggregateoutcomes' => 0,
 645              'droplow' => 0,
 646              'grade_item_itemname' => '',
 647              'grade_item_iteminfo' => '',
 648              'grade_item_idnumber' => '',
 649              'grade_item_gradetype' => GRADE_TYPE_VALUE,
 650              'grade_item_grademax' => 100,
 651              'grade_item_grademin' => 1,
 652              'grade_item_gradepass' => 1,
 653              'grade_item_display' => GRADE_DISPLAY_TYPE_DEFAULT,
 654              // Hack. This must be -2 to use the default setting.
 655              'grade_item_decimals' => -2,
 656              'grade_item_hiddenuntil' => 0,
 657              'grade_item_locktime' => 0,
 658              'grade_item_weightoverride' => 0,
 659              'grade_item_aggregationcoef2' => 0,
 660              'parentcategory' => $defaultparentcat->id
 661          ];
 662  
 663          // Most of the data items need boilerplate prepended. These are the exceptions.
 664          $ignorekeys = ['aggregation', 'aggregateonlygraded', 'aggregateoutcomes', 'droplow', 'parentcategoryid', 'parentcategoryidnumber'];
 665          foreach ($options as $key => $value) {
 666              if (!in_array($key, $ignorekeys)) {
 667                  $fullkey = 'grade_item_' . $key;
 668                  $data[$fullkey] = $value;
 669              } else {
 670                  $data[$key] = $value;
 671              }
 672          }
 673  
 674          // Handle parent category special case.
 675          if (array_key_exists('parentcategoryid', $options) && $parentcat = $DB->get_record('grade_categories',
 676              ['id' => $options['parentcategoryid'], 'courseid' => $courseid])) {
 677              $data['parentcategory'] = $parentcat->id;
 678          } else if (array_key_exists('parentcategoryidnumber', $options) && $parentcatgradeitem = $DB->get_record('grade_items',
 679              ['itemtype' => 'category', 'idnumber' => $options['parentcategoryidnumber']], '*', IGNORE_MULTIPLE)) {
 680              if ($parentcat = $DB->get_record('grade_categories', ['courseid' => $courseid, 'id' => $parentcatgradeitem->iteminstance])) {
 681                  $data['parentcategory'] = $parentcat->id;
 682              }
 683          }
 684  
 685          // Create new gradecategory item.
 686          $gradecategory = new grade_category(['courseid' => $courseid], false);
 687          $gradecategory->apply_default_settings();
 688          $gradecategory->apply_forced_settings();
 689  
 690          // Data Validation.
 691          if (array_key_exists('grade_item_gradetype', $data) and $data['grade_item_gradetype'] == GRADE_TYPE_SCALE) {
 692              if (empty($data['grade_item_scaleid'])) {
 693                  $warnings[] = ['item' => 'scaleid', 'warningcode' => 'invalidscale',
 694                      'message' => get_string('missingscale', 'grades')];
 695              }
 696          }
 697          if (array_key_exists('grade_item_grademin', $data) and array_key_exists('grade_item_grademax', $data)) {
 698              if (($data['grade_item_grademax'] != 0 OR $data['grade_item_grademin'] != 0) AND
 699                  ($data['grade_item_grademax'] == $data['grade_item_grademin'] OR
 700                  $data['grade_item_grademax'] < $data['grade_item_grademin'])) {
 701                  $warnings[] = ['item' => 'grademax', 'warningcode' => 'invalidgrade',
 702                      'message' => get_string('incorrectminmax', 'grades')];
 703              }
 704          }
 705  
 706          if (!empty($warnings)) {
 707              return ['categoryid' => null, 'warnings' => $warnings];
 708          }
 709  
 710          // Now call the update function with data. Transactioned so the gradebook isn't broken on bad data.
 711          try {
 712              $transaction = $DB->start_delegated_transaction();
 713              grade_edit_tree::update_gradecategory($gradecategory, (object) $data);
 714              $transaction->allow_commit();
 715          } catch (Exception $e) {
 716              // If the submitted data was broken for any reason.
 717              $warnings['database'] = $e->getMessage();
 718              $transaction->rollback();
 719              return ['warnings' => $warnings];
 720          }
 721  
 722          return['categoryid' => $gradecategory->id, 'warnings' => []];
 723      }
 724  
 725      /**
 726       * Returns description of method result value
 727       *
 728       * @return external_description
 729       * @since Moodle 3.10
 730       */
 731      public static function create_gradecategory_returns() {
 732          return new external_single_structure([
 733              'categoryid' => new external_value(PARAM_INT, 'The ID of the created category', VALUE_OPTIONAL),
 734              'warnings' => new external_warnings(),
 735          ]);
 736      }
 737  }