Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

   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   * External grading API
  19   *
  20   * @package    core_grading
  21   * @since      Moodle 2.5
  22   * @copyright  2013 Paul Charsley
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  use core_external\external_api;
  27  use core_external\external_format_value;
  28  use core_external\external_function_parameters;
  29  use core_external\external_multiple_structure;
  30  use core_external\external_single_structure;
  31  use core_external\external_value;
  32  use core_external\external_warnings;
  33  
  34  defined('MOODLE_INTERNAL') || die;
  35  
  36  require_once("$CFG->dirroot/grade/grading/lib.php");
  37  
  38  /**
  39   * core grading functions
  40   */
  41  class core_grading_external extends external_api {
  42  
  43      /**
  44       * Describes the parameters for get_definitions
  45       * @return external_function_parameters
  46       * @since Moodle 2.5
  47       */
  48      public static function get_definitions_parameters() {
  49          return new external_function_parameters(
  50              array(
  51                  'cmids' => new external_multiple_structure(
  52                          new external_value(PARAM_INT, 'course module id'), '1 or more course module ids'),
  53                  'areaname' => new external_value(PARAM_AREA, 'area name'),
  54                  'activeonly' => new external_value(PARAM_BOOL, 'Only the active method', VALUE_DEFAULT, 0)
  55              )
  56          );
  57      }
  58  
  59      /**
  60       * Returns the definitions for the requested course module ids
  61       * @param array of ints $cmids
  62       * @param string $areaname
  63       * @param boolean $activeonly default is false, if true, only the active method is returned
  64       * @return array of areas with definitions for each requested course module id
  65       * @since Moodle 2.5
  66       */
  67      public static function get_definitions($cmids, $areaname, $activeonly = false) {
  68          global $DB, $CFG;
  69          require_once("$CFG->dirroot/grade/grading/form/lib.php");
  70          $params = self::validate_parameters(self::get_definitions_parameters(),
  71              array('cmids' => $cmids,
  72                  'areaname' => $areaname,
  73                  'activeonly' => $activeonly));
  74          $warnings = array();
  75          $areas = array();
  76          foreach ($params['cmids'] as $cmid) {
  77              $context = context_module::instance($cmid);
  78              try {
  79                  self::validate_context($context);
  80              } catch (Exception $e) {
  81                  $warnings[] = array(
  82                      'item' => 'module',
  83                      'itemid' => $cmid,
  84                      'message' => 'No access rights in module context',
  85                      'warningcode' => '1'
  86                  );
  87                  continue;
  88              }
  89              // Check if the user has managegradingforms capability.
  90              $isgradingmethodmanager = false;
  91              if (has_capability('moodle/grade:managegradingforms', $context)) {
  92                  $isgradingmethodmanager = true;
  93              }
  94              $module = get_coursemodule_from_id('', $cmid, 0, false, MUST_EXIST);
  95              $componentname = "mod_".$module->modname;
  96  
  97              // Get the grading manager.
  98              $gradingmanager = get_grading_manager($context, $componentname, $params['areaname']);
  99              // Get the controller for each grading method.
 100              $methods = array();
 101              if ($params['activeonly'] == true) {
 102                  $methods[] = $gradingmanager->get_active_method();
 103              } else {
 104                  $methods = array_keys($gradingmanager->get_available_methods(false));
 105              }
 106  
 107              $area = array();
 108              $area['cmid'] = $cmid;
 109              $area['contextid'] = $context->id;
 110              $area['component'] = $componentname;
 111              $area['areaname'] = $params['areaname'];
 112              $area['activemethod'] = $gradingmanager->get_active_method();
 113              $area['definitions'] = array();
 114  
 115              foreach ($methods as $method) {
 116                  $controller = $gradingmanager->get_controller($method);
 117                  $def = $controller->get_definition(true);
 118                  if ($def == false) {
 119                      continue;
 120                  }
 121                  if ($isgradingmethodmanager == false) {
 122                      $isviewable = true;
 123                      if ($def->status != gradingform_controller::DEFINITION_STATUS_READY) {
 124                          $warnings[] = array(
 125                              'item' => 'module',
 126                              'itemid' => $cmid,
 127                              'message' => 'Capability moodle/grade:managegradingforms required to view draft definitions',
 128                              'warningcode' => '1'
 129                          );
 130                          $isviewable = false;
 131                      }
 132                      if (!empty($def->options)) {
 133                          $options = json_decode($def->options);
 134                          if (isset($options->alwaysshowdefinition) &&
 135                              $options->alwaysshowdefinition == 0) {
 136                              $warnings[] = array(
 137                                  'item' => 'module',
 138                                  'itemid' => $cmid,
 139                                  'message' => 'Capability moodle/grade:managegradingforms required to preview definition',
 140                                  'warningcode' => '1'
 141                              );
 142                              $isviewable = false;
 143                          }
 144                      }
 145                      if ($isviewable == false) {
 146                          continue;
 147                      }
 148                  }
 149                  $definition = array();
 150                  $definition['id'] = $def->id;
 151                  $definition['method'] = $method;
 152                  $definition['name'] = $def->name;
 153                  $definition['description'] = $def->description;
 154                  $definition['descriptionformat'] = $def->descriptionformat;
 155                  $definition['status'] = $def->status;
 156                  $definition['copiedfromid'] = $def->copiedfromid;
 157                  $definition['timecreated'] = $def->timecreated;
 158                  $definition['usercreated'] = $def->usercreated;
 159                  $definition['timemodified'] = $def->timemodified;
 160                  $definition['usermodified'] = $def->usermodified;
 161                  $definition['timecopied'] = $def->timecopied;
 162                  // Format the description text field.
 163                  $formattedtext = \core_external\util::format_text($definition['description'],
 164                      $definition['descriptionformat'],
 165                      $context->id,
 166                      $componentname,
 167                      'description',
 168                      $def->id);
 169                  $definition['description'] = $formattedtext[0];
 170                  $definition['descriptionformat'] = $formattedtext[1];
 171  
 172                  $details = $controller->get_external_definition_details();
 173                  $items = array();
 174                  foreach ($details as $key => $value) {
 175                      $items[$key] = self::format_text($def->{$key}, $context->id, $componentname, $def->id);
 176                  }
 177                  $definition[$method] = $items;
 178                  $area['definitions'][] = $definition;
 179              }
 180              $areas[] = $area;
 181          }
 182          $result = array(
 183              'areas' => $areas,
 184              'warnings' => $warnings
 185          );
 186          return $result;
 187      }
 188  
 189      /**
 190       * Recursively processes all elements in an array and runs \core_external\util::format_text()on
 191       * all elements which have a text field and associated format field with a key name
 192       * that ends with the text 'format'. The modified array is returned.
 193       * @param array $items the array to be processed
 194       * @param int $contextid
 195       * @param string $componentname
 196       * @param int $itemid
 197       * @see \core_external\util::format_text()
 198       * @return array the input array with all fields formatted
 199       */
 200      private static function format_text($items, $contextid, $componentname, $itemid) {
 201          $formatkeys = array();
 202          foreach ($items as $key => $value) {
 203              if (!is_array($value) && substr_compare($key, 'format', -6, 6) === 0) {
 204                  $formatkeys[] = $key;
 205              }
 206          }
 207          foreach ($formatkeys as $formatkey) {
 208              $descriptionkey = substr($formatkey, 0, -6);
 209              $formattedtext = \core_external\util::format_text($items[$descriptionkey],
 210                  $items[$formatkey],
 211                  context::instance_by_id($contextid),
 212                  $componentname,
 213                  'description',
 214                  $itemid);
 215              $items[$descriptionkey] = $formattedtext[0];
 216              $items[$formatkey] = $formattedtext[1];
 217          }
 218          foreach ($items as $key => $value) {
 219              if (is_array($value)) {
 220                  $items[$key] = self::format_text($value, $contextid, $componentname, $itemid);
 221              }
 222          }
 223          return $items;
 224      }
 225  
 226      /**
 227       * Creates a grading area
 228       * @return external_single_structure
 229       * @since  Moodle 2.5
 230       */
 231      private static function grading_area() {
 232          return new external_single_structure(
 233              array (
 234                  'cmid'    => new external_value(PARAM_INT, 'course module id'),
 235                  'contextid'  => new external_value(PARAM_INT, 'context id'),
 236                  'component' => new external_value(PARAM_TEXT, 'component name'),
 237                  'areaname' => new external_value(PARAM_TEXT, 'area name'),
 238                  'activemethod' => new external_value(PARAM_TEXT, 'active method', VALUE_OPTIONAL),
 239                  'definitions'  => new external_multiple_structure(self::definition(), 'definitions')
 240              )
 241          );
 242      }
 243  
 244      /**
 245       * creates a grading form definition
 246       * @return external_single_structure
 247       * @since  Moodle 2.5
 248       */
 249      private static function definition() {
 250          global $CFG;
 251          $definition = array();
 252          $definition['id']                = new external_value(PARAM_INT, 'definition id', VALUE_OPTIONAL);
 253          $definition['method']            = new external_value(PARAM_TEXT, 'method');
 254          $definition['name']              = new external_value(PARAM_TEXT, 'name');
 255          $definition['description']       = new external_value(PARAM_RAW, 'description', VALUE_OPTIONAL);
 256          $definition['descriptionformat'] = new external_format_value('description', VALUE_OPTIONAL);
 257          $definition['status']            = new external_value(PARAM_INT, 'status');
 258          $definition['copiedfromid']      = new external_value(PARAM_INT, 'copied from id', VALUE_OPTIONAL);
 259          $definition['timecreated']       = new external_value(PARAM_INT, 'creation time');
 260          $definition['usercreated']       = new external_value(PARAM_INT, 'user who created definition');
 261          $definition['timemodified']      = new external_value(PARAM_INT, 'last modified time');
 262          $definition['usermodified']      = new external_value(PARAM_INT, 'user who modified definition');
 263          $definition['timecopied']        = new external_value(PARAM_INT, 'time copied', VALUE_OPTIONAL);
 264          foreach (self::get_grading_methods() as $method) {
 265              require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
 266              $details  = call_user_func('gradingform_'.$method.'_controller::get_external_definition_details');
 267              if ($details != null) {
 268                  $items = array();
 269                  foreach ($details as $key => $value) {
 270                      $details[$key]->required = VALUE_OPTIONAL;
 271                      $items[$key] = $value;
 272                  }
 273                  $definition[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
 274              }
 275          }
 276          return new external_single_structure($definition);
 277      }
 278  
 279      /**
 280       * Describes the get_definitions return value
 281       * @return external_single_structure
 282       * @since Moodle 2.5
 283       */
 284      public static function get_definitions_returns() {
 285          return new external_single_structure(
 286              array(
 287                  'areas' => new external_multiple_structure(self::grading_area(), 'list of grading areas'),
 288                  'warnings' => new external_warnings()
 289              )
 290          );
 291      }
 292  
 293      /**
 294       * @return array of available grading methods
 295       * @since Moodle 2.5
 296       */
 297      private static function get_grading_methods() {
 298          $methods = array_keys(grading_manager::available_methods(false));
 299          return $methods;
 300      }
 301  
 302      /**
 303       * Describes the parameters for get_gradingform_instances
 304       *
 305       * @return external_function_parameters
 306       * @since Moodle 2.6
 307       */
 308      public static function get_gradingform_instances_parameters() {
 309          return new external_function_parameters(
 310              array(
 311                  'definitionid' => new external_value(PARAM_INT, 'definition id'),
 312                  'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0)
 313              )
 314          );
 315      }
 316  
 317      /**
 318       * Returns the instances and fillings for the requested definition id
 319       *
 320       * @param int $definitionid
 321       * @param int $since only return instances with timemodified >= since
 322       * @return array of grading instances with fillings for the definition id
 323       * @since Moodle 2.6
 324       */
 325      public static function get_gradingform_instances($definitionid, $since = 0) {
 326          global $DB, $CFG;
 327          require_once("$CFG->dirroot/grade/grading/form/lib.php");
 328          $params = self::validate_parameters(self::get_gradingform_instances_parameters(),
 329              array('definitionid' => $definitionid,
 330                  'since' => $since));
 331          $instances = array();
 332          $warnings = array();
 333  
 334          $definition = $DB->get_record('grading_definitions',
 335              array('id' => $params['definitionid']),
 336              'areaid,method', MUST_EXIST);
 337          $area = $DB->get_record('grading_areas',
 338              array('id' => $definition->areaid),
 339              'contextid,component', MUST_EXIST);
 340  
 341          $context = context::instance_by_id($area->contextid);
 342          require_capability('moodle/grade:managegradingforms', $context);
 343  
 344          $gradingmanager = get_grading_manager($definition->areaid);
 345          $controller = $gradingmanager->get_controller($definition->method);
 346          $activeinstances = $controller->get_all_active_instances ($params['since']);
 347          $details = $controller->get_external_instance_filling_details();
 348          if ($details == null) {
 349              $warnings[] = array(
 350                  'item' => 'definition',
 351                  'itemid' => $params['definitionid'],
 352                  'message' => 'Fillings unavailable because get_external_instance_filling_details is not defined',
 353                  'warningcode' => '1'
 354              );
 355          }
 356          $getfilling = null;
 357          if (method_exists('gradingform_'.$definition->method.'_instance', 'get_'.$definition->method.'_filling')) {
 358              $getfilling = 'get_'.$definition->method.'_filling';
 359          } else {
 360              $warnings[] = array(
 361                  'item' => 'definition',
 362                  'itemid' => $params['definitionid'],
 363                  'message' => 'Fillings unavailable because get_'.$definition->method.'_filling is not defined',
 364                  'warningcode' => '1'
 365              );
 366          }
 367          foreach ($activeinstances as $activeinstance) {
 368              $instance = array();
 369              $instance['id'] = $activeinstance->get_id();
 370              $instance['raterid'] = $activeinstance->get_data('raterid');
 371              $instance['itemid'] = $activeinstance->get_data('itemid');
 372              $instance['rawgrade'] = $activeinstance->get_data('rawgrade');
 373              $instance['status'] = $activeinstance->get_data('status');
 374              $instance['feedback'] = $activeinstance->get_data('feedback');
 375              $instance['feedbackformat'] = $activeinstance->get_data('feedbackformat');
 376              // Format the feedback text field.
 377              $formattedtext = \core_external\util::format_text($activeinstance->get_data('feedback'),
 378                  $activeinstance->get_data('feedbackformat'),
 379                  $context->id,
 380                  $area->component,
 381                  'feedback',
 382                  $params['definitionid']);
 383              $instance['feedback'] = $formattedtext[0];
 384              $instance['feedbackformat'] = $formattedtext[1];
 385              $instance['timemodified'] = $activeinstance->get_data('timemodified');
 386  
 387              if ($details != null && $getfilling != null) {
 388                  $fillingdata = $activeinstance->$getfilling();
 389                  $filling = array();
 390                  foreach ($details as $key => $value) {
 391                      $filling[$key] = self::format_text($fillingdata[$key],
 392                          $context->id,
 393                          $area->component,
 394                          $params['definitionid']);
 395                  }
 396                  $instance[$definition->method] = $filling;
 397              }
 398              $instances[] = $instance;
 399          }
 400          $result = array(
 401              'instances' => $instances,
 402              'warnings' => $warnings
 403          );
 404          return $result;
 405      }
 406  
 407      /**
 408       * Creates a grading instance
 409       *
 410       * @return external_single_structure
 411       * @since  Moodle 2.6
 412       */
 413      private static function grading_instance() {
 414          global $CFG;
 415          $instance = array();
 416          $instance['id']                = new external_value(PARAM_INT, 'instance id');
 417          $instance['raterid']           = new external_value(PARAM_INT, 'rater id');
 418          $instance['itemid']            = new external_value(PARAM_INT, 'item id');
 419          $instance['rawgrade']          = new external_value(PARAM_TEXT, 'raw grade', VALUE_OPTIONAL);
 420          $instance['status']            = new external_value(PARAM_INT, 'status');
 421          $instance['feedback']          = new external_value(PARAM_RAW, 'feedback', VALUE_OPTIONAL);
 422          $instance['feedbackformat']    = new external_format_value('feedback', VALUE_OPTIONAL);
 423          $instance['timemodified']      = new external_value(PARAM_INT, 'modified time');
 424          foreach (self::get_grading_methods() as $method) {
 425              require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
 426              $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
 427              if ($details != null) {
 428                  $items = array();
 429                  foreach ($details as $key => $value) {
 430                      $details[$key]->required = VALUE_OPTIONAL;
 431                      $items[$key] = $value;
 432                  }
 433                  $instance[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
 434              }
 435          }
 436          return new external_single_structure($instance);
 437      }
 438  
 439      /**
 440       * Describes the get_gradingform_instances return value
 441       *
 442       * @return external_single_structure
 443       * @since Moodle 2.6
 444       */
 445      public static function get_gradingform_instances_returns() {
 446          return new external_single_structure(
 447              array(
 448                  'instances' => new external_multiple_structure(self::grading_instance(), 'list of grading instances'),
 449                  'warnings' => new external_warnings()
 450              )
 451          );
 452      }
 453  
 454      /**
 455       * Describes the parameters for save_definitions
 456       *
 457       * @return external_function_parameters
 458       * @since Moodle 2.8
 459       */
 460      public static function save_definitions_parameters() {
 461          return new external_function_parameters(
 462              array(
 463                  'areas' => new external_multiple_structure(self::grading_area(), 'areas with definitions to save')
 464              )
 465          );
 466      }
 467  
 468      /**
 469       * Saves the areas and definitions
 470       * @param array $areas array of areas containing definitions to be saved
 471       * @return null
 472       * @throws invalid_parameter_exception
 473       * @since Moodle 2.8
 474       */
 475      public static function save_definitions($areas) {
 476          $params = self::validate_parameters(self::save_definitions_parameters(),
 477                                              array('areas' => $areas));
 478  
 479          foreach ($params['areas'] as $area) {
 480  
 481              $context = context::instance_by_id($area['contextid']);
 482              require_capability('moodle/grade:managegradingforms', $context);
 483              $gradingmanager = get_grading_manager($context, $area['component'], $area['areaname']);
 484              $gradingmanager->set_active_method($area['activemethod']);
 485              $availablemethods = $gradingmanager->get_available_methods();
 486  
 487              foreach ($area['definitions'] as $definition) {
 488                  if (array_key_exists($definition['method'], $availablemethods)) {
 489                      $controller = $gradingmanager->get_controller($definition['method']);
 490                      $controller->update_definition(self::create_definition_object($definition));
 491                  } else {
 492                      throw new invalid_parameter_exception('Unknown Grading method: '. $definition['method']);
 493                  }
 494              }
 495          }
 496      }
 497  
 498      /**
 499       * Describes the return value for save_definitions
 500       *
 501       * @return external_single_structure
 502       * @since Moodle 2.8
 503       */
 504      public static function save_definitions_returns() {
 505          return null;
 506      }
 507  
 508      /**
 509       * Creates a definition stdClass object using the values from the definition
 510       * array that is passed in as a parameter
 511       *
 512       * @param array $definition
 513       * @return stdClass definition object
 514       * @since Moodle 2.8
 515       */
 516      private static function create_definition_object($definition) {
 517          global $CFG;
 518  
 519          $method = $definition['method'];
 520          $definitionobject = new stdClass();
 521          foreach ($definition as $key => $value) {
 522              if (!is_array($value)) {
 523                  $definitionobject->$key = $value;
 524              }
 525          }
 526          $text = '';
 527          $format = FORMAT_MOODLE;
 528          if (isset($definition['description'])) {
 529              $text = $definition['description'];
 530              if (isset($definition['descriptionformat'])) {
 531                  $format = $definition['descriptionformat'];
 532              }
 533          }
 534          $definitionobject->description_editor = array('text' => $text, 'format' => $format);
 535  
 536          require_once("$CFG->libdir/filelib.php");
 537          require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
 538          $details  = call_user_func('gradingform_'.$method.'_controller::get_external_definition_details');
 539          $methodarray = array();
 540          foreach (array_keys($details) as $definitionkey) {
 541              $items = array();
 542              $idnumber = 1;
 543              foreach ($definition[$method][$definitionkey] as $item) {
 544                  $processeditem = self::set_new_ids($item, $idnumber);
 545                  $items[$processeditem['id']] = $processeditem;
 546                  $idnumber++;
 547              }
 548              $definitionobjectkey = substr($definitionkey, strlen($method.'_'));
 549              $methodarray[$definitionobjectkey] = $items;
 550              $definitionobject->$method = $methodarray;
 551          }
 552  
 553          return $definitionobject;
 554      }
 555  
 556      /**
 557       * Recursively iterates through arrays. Any array without an id key-value combination
 558       * is assumed to be an array of values to be inserted and an id key-value is added with
 559       * the value matching the regex '/^NEWID\d+$/' that is expected by each grading form implementation.
 560       *
 561       * @param array $arraytoset the array to be processed
 562       * @param int $startnumber the starting number for the new id numbers
 563       * @return array with missing id keys added for all arrays
 564       * @since Moodle 2.8
 565       */
 566      private static function set_new_ids($arraytoset, $startnumber) {
 567          $result = array();
 568          $foundid = false;
 569          $number = $startnumber;
 570          foreach ($arraytoset as $key1 => $value1) {
 571              if (is_array($value1)) {
 572                  foreach ($value1 as $key2 => $value2) {
 573                      $processedvalue = self::set_new_ids($value2, $number);
 574                      $result[$key1][$processedvalue['id']] = $processedvalue;
 575                      $number++;
 576                  }
 577              } else {
 578                  $result[$key1] = $value1;
 579              }
 580              if ($key1 === 'id') {
 581                  $foundid = true;
 582              }
 583          }
 584          if (!$foundid) {
 585              $result['id'] = 'NEWID'.$number;
 586          }
 587          return $result;
 588      }
 589  
 590  }