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   * Web services relating to fetching of a rubric for the grading panel.
  19   *
  20   * @package    gradingform_rubric
  21   * @copyright  2019 Mathew May <mathew.solutions>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  declare(strict_types = 1);
  26  
  27  namespace gradingform_rubric\grades\grader\gradingpanel\external;
  28  
  29  global $CFG;
  30  
  31  use coding_exception;
  32  use context;
  33  use core_grades\component_gradeitem as gradeitem;
  34  use core_grades\component_gradeitems;
  35  use core_external\external_api;
  36  use core_external\external_function_parameters;
  37  use core_external\external_multiple_structure;
  38  use core_external\external_single_structure;
  39  use core_external\external_value;
  40  use core_external\external_warnings;
  41  use stdClass;
  42  use moodle_exception;
  43  require_once($CFG->dirroot.'/grade/grading/form/rubric/lib.php');
  44  
  45  /**
  46   * Web services relating to fetching of a rubric for the grading panel.
  47   *
  48   * @package    gradingform_rubric
  49   * @copyright  2019 Mathew May <mathew.solutions>
  50   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  51   */
  52  class fetch extends external_api {
  53  
  54      /**
  55       * Describes the parameters for fetching the grading panel for a simple grade.
  56       *
  57       * @return external_function_parameters
  58       * @since Moodle 3.8
  59       */
  60      public static function execute_parameters(): external_function_parameters {
  61          return new external_function_parameters ([
  62              'component' => new external_value(
  63                  PARAM_ALPHANUMEXT,
  64                  'The name of the component',
  65                  VALUE_REQUIRED
  66              ),
  67              'contextid' => new external_value(
  68                  PARAM_INT,
  69                  'The ID of the context being graded',
  70                  VALUE_REQUIRED
  71              ),
  72              'itemname' => new external_value(
  73                  PARAM_ALPHANUM,
  74                  'The grade item itemname being graded',
  75                  VALUE_REQUIRED
  76              ),
  77              'gradeduserid' => new external_value(
  78                  PARAM_INT,
  79                  'The ID of the user show',
  80                  VALUE_REQUIRED
  81              ),
  82          ]);
  83      }
  84  
  85      /**
  86       * Fetch the data required to build a grading panel for a simple grade.
  87       *
  88       * @param string $component
  89       * @param int $contextid
  90       * @param string $itemname
  91       * @param int $gradeduserid
  92       * @return array
  93       * @since Moodle 3.8
  94       */
  95      public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array {
  96          global $CFG, $USER;
  97          require_once("{$CFG->libdir}/gradelib.php");
  98          [
  99              'component' => $component,
 100              'contextid' => $contextid,
 101              'itemname' => $itemname,
 102              'gradeduserid' => $gradeduserid,
 103          ] = self::validate_parameters(self::execute_parameters(), [
 104              'component' => $component,
 105              'contextid' => $contextid,
 106              'itemname' => $itemname,
 107              'gradeduserid' => $gradeduserid,
 108          ]);
 109  
 110          // Validate the context.
 111          $context = context::instance_by_id($contextid);
 112          self::validate_context($context);
 113  
 114          // Validate that the supplied itemname is a gradable item.
 115          if (!component_gradeitems::is_valid_itemname($component, $itemname)) {
 116              throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component");
 117          }
 118  
 119          // Fetch the gradeitem instance.
 120          $gradeitem = gradeitem::instance($component, $context, $itemname);
 121  
 122          if (RUBRIC !== $gradeitem->get_advanced_grading_method()) {
 123              throw new moodle_exception(
 124                  "The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a rubric"
 125              );
 126          }
 127  
 128          // Fetch the actual data.
 129          $gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST);
 130  
 131          // One can access its own grades. Others just if they're graders.
 132          if ($gradeduserid != $USER->id) {
 133              $gradeitem->require_user_can_grade($gradeduser, $USER);
 134          }
 135  
 136          return self::get_fetch_data($gradeitem, $gradeduser);
 137      }
 138  
 139      /**
 140       * Get the data to be fetched and create the structure ready for Mustache.
 141       *
 142       * @param gradeitem $gradeitem
 143       * @param stdClass $gradeduser
 144       * @return array
 145       */
 146      public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser): array {
 147          global $USER;
 148          // Set up all the controllers etc that we'll be needing.
 149          $hasgrade = $gradeitem->user_has_grade($gradeduser);
 150          $grade = $gradeitem->get_formatted_grade_for_user($gradeduser, $USER);
 151          $instance = $gradeitem->get_advanced_grading_instance($USER, $grade);
 152          if (!$instance) {
 153              throw new moodle_exception('error:gradingunavailable', 'grading');
 154          }
 155  
 156          $controller = $instance->get_controller();
 157          $definition = $controller->get_definition();
 158          $fillings = $instance->get_rubric_filling();
 159          $context = $controller->get_context();
 160          $definitionid = (int) $definition->id;
 161  
 162          // Set up some items we need to return on other interfaces.
 163          $gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]);
 164          $gradername = $gradegrade ? fullname(\core_user::get_user($gradegrade->usermodified)) : null;
 165          $maxgrade = max(array_keys($controller->get_grade_range()));
 166  
 167          $teacherdescription = self::get_formatted_text(
 168              $context,
 169              $definitionid,
 170              'description',
 171              $definition->description,
 172              (int) $definition->descriptionformat
 173          );
 174  
 175          $criterion = [];
 176          if ($definition->rubric_criteria) {
 177              // Iterate over the defined criterion in the rubric and map out what we need to render each item.
 178              $criterion = array_map(function($criterion) use ($definitionid, $fillings, $context, $hasgrade) {
 179                  // The general structure we'll be returning, we still need to get the remark (if any) and the levels associated.
 180                  $result = [
 181                      'id' => $criterion['id'],
 182                      'description' => self::get_formatted_text(
 183                          $context,
 184                          $definitionid,
 185                          'description',
 186                          $criterion['description'],
 187                          (int) $criterion['descriptionformat']
 188                      ),
 189                  ];
 190  
 191                  // Do we have an existing grade filling? if so lets get the remark associated to this criteria.
 192                  $filling = [];
 193                  if (array_key_exists($criterion['id'], $fillings['criteria'])) {
 194                      $filling = $fillings['criteria'][$criterion['id']];
 195                      $result['remark'] = self::get_formatted_text($context,
 196                          $definitionid,
 197                          'remark',
 198                          $filling['remark'],
 199                          (int) $filling['remarkformat']
 200                      );
 201                  }
 202  
 203                  // Lets build the levels within a criteria and figure out what needs to go where.
 204                  $result['levels'] = array_map(function($level) use ($criterion, $filling, $context, $definitionid) {
 205                      // The bulk of what'll be returned can be defined easily we'll add to this further down.
 206                      $result = [
 207                          'id' => $level['id'],
 208                          'criterionid' => $criterion['id'],
 209                          'score' => $level['score'],
 210                          'definition' => self::get_formatted_text(
 211                              $context,
 212                              $definitionid,
 213                              'definition',
 214                              $level['definition'],
 215                              (int) $level['definitionformat']
 216                          ),
 217                          'checked' => null,
 218                      ];
 219  
 220                      // Consult the grade filling to see if a level has been selected and if it is the current level.
 221                      if (array_key_exists('levelid', $filling) && $filling['levelid'] == $level['id']) {
 222                          $result['checked'] = true;
 223                      }
 224  
 225                      return $result;
 226                  }, $criterion['levels']);
 227  
 228                  $nulllevel = [
 229                      'id' => null,
 230                      'criterionid' => $criterion['id'],
 231                      'score' => '-',
 232                      'definition' => get_string('notset', 'gradingform_rubric'),
 233                      'checked' => !$hasgrade,
 234                  ];
 235                  // Consult the grade filling to see if a level has been selected and if it is the current level.
 236                  if (array_key_exists('levelid', $filling) && $filling['levelid'] == 0) {
 237                      $nulllevel['checked'] = true;
 238                  }
 239  
 240                  array_unshift($result['levels'], $nulllevel);
 241  
 242                  return $result;
 243              }, $definition->rubric_criteria);
 244          }
 245  
 246          return [
 247              'templatename' => 'gradingform_rubric/grades/grader/gradingpanel',
 248              'hasgrade' => $hasgrade,
 249              'grade' => [
 250                  'instanceid' => $instance->get_id(),
 251                  'criteria' => $criterion,
 252                  'rubricmode' => 'evaluate editable',
 253                  'teacherdescription' => $teacherdescription,
 254                  'canedit' => false,
 255                  'usergrade' => $grade->usergrade,
 256                  'maxgrade' => $maxgrade,
 257                  'gradedby' => $gradername,
 258                  'timecreated' => $grade->timecreated,
 259                  'timemodified' => $grade->timemodified,
 260              ],
 261              'warnings' => [],
 262          ];
 263      }
 264  
 265      /**
 266       * Describes the data returned from the external function.
 267       *
 268       * @return external_single_structure
 269       * @since Moodle 3.8
 270       */
 271      public static function execute_returns(): external_single_structure {
 272          return new external_single_structure([
 273              'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'),
 274              'hasgrade' => new external_value(PARAM_BOOL, 'Does the user have a grade?'),
 275              'grade' => new external_single_structure([
 276                  'instanceid' => new external_value(PARAM_INT, 'The id of the current grading instance'),
 277                  'rubricmode' => new external_value(PARAM_RAW, 'The mode i.e. evaluate editable'),
 278                  'canedit' => new external_value(PARAM_BOOL, 'Can the user edit this'),
 279                  'criteria' => new external_multiple_structure(
 280                      new external_single_structure([
 281                          'id' => new external_value(PARAM_INT, 'ID of the Criteria'),
 282                          'description' => new external_value(PARAM_RAW, 'Description of the Criteria'),
 283                          'remark' => new external_value(PARAM_RAW, 'Any remarks for this criterion for the user being assessed', VALUE_OPTIONAL),
 284                          'levels' => new external_multiple_structure(new external_single_structure([
 285                              'id' => new external_value(PARAM_INT, 'ID of level'),
 286                              'criterionid' => new external_value(PARAM_INT, 'ID of the criterion this matches to'),
 287                              'score' => new external_value(PARAM_RAW, 'What this level is worth'),
 288                              'definition' => new external_value(PARAM_RAW, 'Definition of the level'),
 289                              'checked' => new external_value(PARAM_BOOL, 'Selected flag'),
 290                          ])),
 291                      ])
 292                  ),
 293                  'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'),
 294                  'usergrade' => new external_value(PARAM_RAW, 'Current user grade'),
 295                  'maxgrade' => new external_value(PARAM_RAW, 'Max possible grade'),
 296                  'gradedby' => new external_value(PARAM_RAW, 'The assumed grader of this grading instance'),
 297                  'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'),
 298              ]),
 299              'warnings' => new external_warnings(),
 300          ]);
 301      }
 302  
 303      /**
 304       * Get a formatted version of the remark/description/etc.
 305       *
 306       * @param context $context
 307       * @param int $definitionid
 308       * @param string $filearea The file area of the field
 309       * @param string $text The text to be formatted
 310       * @param int $format The input format of the string
 311       * @return string
 312       */
 313      protected static function get_formatted_text(context $context, int $definitionid, string $filearea, string $text, int $format): string {
 314          $formatoptions = [
 315              'noclean' => false,
 316              'trusted' => false,
 317              'filter' => true,
 318          ];
 319          [$newtext] = \core_external\util::format_text(
 320              $text,
 321              $format,
 322              $context,
 323              'grading',
 324              $filearea,
 325              $definitionid,
 326              $formatoptions
 327          );
 328          return $newtext;
 329      }
 330  }