Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [Versions 401 and 403]

   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  namespace gradereport_user\external;
  18  
  19  use external_api;
  20  use context_course;
  21  use core_user;
  22  use external_description;
  23  use external_format_value;
  24  use external_function_parameters;
  25  use external_multiple_structure;
  26  use external_single_structure;
  27  use external_value;
  28  use external_warnings;
  29  use grade_plugin_return;
  30  use graded_users_iterator;
  31  use moodle_exception;
  32  use stdClass;
  33  use gradereport_user\report\user as user_report;
  34  
  35  defined('MOODLE_INTERNAL') || die;
  36  
  37  require_once($CFG->libdir.'/externallib.php');
  38  require_once($CFG->dirroot.'/grade/lib.php');
  39  
  40  /**
  41   * External grade report API implementation
  42   *
  43   * @package    gradereport_user
  44   * @copyright  2015 Juan Leyva <juan@moodle.com>
  45   * @category   external
  46   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   */
  48  class user extends external_api {
  49  
  50      /**
  51       * Validate access permissions to the report
  52       *
  53       * @param  int  $courseid the courseid
  54       * @param  int  $userid   the user id to retrieve data from
  55       * @param  int $groupid   the group id
  56       * @return array with the parameters cleaned and other required information
  57       * @since  Moodle 3.2
  58       */
  59      protected static function check_report_access(int $courseid, int $userid, int $groupid = 0): array {
  60          global $USER;
  61  
  62          // Validate the parameter.
  63          $params = self::validate_parameters(self::get_grades_table_parameters(),
  64              [
  65                  'courseid' => $courseid,
  66                  'userid' => $userid,
  67                  'groupid' => $groupid,
  68              ]
  69          );
  70  
  71          // Compact/extract functions are not recommended.
  72          $courseid = $params['courseid'];
  73          $userid   = $params['userid'];
  74          $groupid  = $params['groupid'];
  75  
  76          // Function get_course internally throws an exception if the course doesn't exist.
  77          $course = get_course($courseid);
  78  
  79          $context = context_course::instance($courseid);
  80          self::validate_context($context);
  81  
  82          // Specific capabilities.
  83          require_capability('gradereport/user:view', $context);
  84  
  85          $user = null;
  86  
  87          if (empty($userid)) {
  88              require_capability('moodle/grade:viewall', $context);
  89          } else {
  90              $user = core_user::get_user($userid, '*', MUST_EXIST);
  91              core_user::require_active_user($user);
  92              // Check if we can view the user group (if any).
  93              // When userid == 0, we are retrieving all the users, we'll check then if a groupid is required.
  94              if (!groups_user_groups_visible($course, $user->id)) {
  95                  throw new moodle_exception('notingroup');
  96              }
  97          }
  98  
  99          $access = false;
 100  
 101          if (has_capability('moodle/grade:viewall', $context)) {
 102              // Can view all course grades.
 103              $access = true;
 104          } else if ($userid == $USER->id && has_capability('moodle/grade:view', $context) && $course->showgrades) {
 105              // View own grades.
 106              $access = true;
 107          }
 108  
 109          if (!$access) {
 110              throw new moodle_exception('nopermissiontoviewgrades', 'error');
 111          }
 112  
 113          if (!empty($groupid)) {
 114              // Determine is the group is visible to user.
 115              if (!groups_group_visible($groupid, $course)) {
 116                  throw new moodle_exception('notingroup');
 117              }
 118          } else {
 119              // Check to see if groups are being used here.
 120              if ($groupmode = groups_get_course_groupmode($course)) {
 121                  $groupid = groups_get_course_group($course);
 122                  // Determine is the group is visible to user (this is particullary for the group 0).
 123                  if (!groups_group_visible($groupid, $course)) {
 124                      throw new moodle_exception('notingroup');
 125                  }
 126              } else {
 127                  $groupid = 0;
 128              }
 129          }
 130  
 131          return [$params, $course, $context, $user, $groupid];
 132      }
 133  
 134      /**
 135       * Get the report data
 136       * @param  stdClass $course  course object
 137       * @param  stdClass $context context object
 138       * @param  null|stdClass $user    user object (it can be null for all the users)
 139       * @param  int $userid       the user to retrieve data from, 0 for all
 140       * @param  int $groupid      the group id to filter
 141       * @param  bool $tabledata   whether to get the table data (true) or the gradeitemdata
 142       * @return array data and possible warnings
 143       * @since  Moodle 3.2
 144       */
 145      protected static function get_report_data(
 146          stdClass $course,
 147          stdClass $context,
 148          ?stdClass $user,
 149          int $userid,
 150          int $groupid,
 151          bool $tabledata = true
 152      ): array {
 153          global $CFG;
 154  
 155          $warnings = [];
 156          // Require files here to save some memory in case validation fails.
 157          require_once($CFG->dirroot . '/group/lib.php');
 158          require_once($CFG->libdir  . '/gradelib.php');
 159          require_once($CFG->dirroot . '/grade/lib.php');
 160          require_once($CFG->dirroot . '/grade/report/user/lib.php');
 161  
 162          // Force regrade to update items marked as 'needupdate'.
 163          grade_regrade_final_grades($course->id);
 164  
 165          $gpr = new grade_plugin_return(
 166              [
 167                  'type'           => 'report',
 168                  'plugin'         => 'user',
 169                  'courseid'       => $course->id,
 170                  'courseidnumber' => $course->idnumber,
 171                  'userid'         => $userid
 172              ]
 173          );
 174  
 175          $reportdata = [];
 176  
 177          // Just one user.
 178          if ($user) {
 179              $report = new user_report($course->id, $gpr, $context, $userid);
 180              $report->fill_table();
 181  
 182              $gradeuserdata = [
 183                  'courseid'       => $course->id,
 184                  'courseidnumber' => $course->idnumber,
 185                  'userid'         => $user->id,
 186                  'userfullname'   => fullname($user),
 187                  'useridnumber'   => $user->idnumber,
 188                  'maxdepth'       => $report->maxdepth,
 189              ];
 190              if ($tabledata) {
 191                  $gradeuserdata['tabledata'] = $report->tabledata;
 192              } else {
 193                  $gradeuserdata['gradeitems'] = $report->gradeitemsdata;
 194              }
 195              $reportdata[] = $gradeuserdata;
 196          } else {
 197              $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
 198              $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
 199              $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context);
 200  
 201              $gui = new graded_users_iterator($course, null, $groupid);
 202              $gui->require_active_enrolment($showonlyactiveenrol);
 203              $gui->init();
 204  
 205              while ($userdata = $gui->next_user()) {
 206                  $currentuser = $userdata->user;
 207                  $report = new user_report($course->id, $gpr, $context, $currentuser->id);
 208                  $report->fill_table();
 209  
 210                  $gradeuserdata = [
 211                      'courseid'       => $course->id,
 212                      'courseidnumber' => $course->idnumber,
 213                      'userid'         => $currentuser->id,
 214                      'userfullname'   => fullname($currentuser),
 215                      'useridnumber'   => $currentuser->idnumber,
 216                      'maxdepth'       => $report->maxdepth,
 217                  ];
 218                  if ($tabledata) {
 219                      $gradeuserdata['tabledata'] = $report->tabledata;
 220                  } else {
 221                      $gradeuserdata['gradeitems'] = $report->gradeitemsdata;
 222                  }
 223                  $reportdata[] = $gradeuserdata;
 224              }
 225              $gui->close();
 226          }
 227          return [$reportdata, $warnings];
 228      }
 229  
 230      /**
 231       * Describes the parameters for get_grades_table.
 232       *
 233       * @return external_function_parameters
 234       * @since Moodle 2.9
 235       */
 236      public static function get_grades_table_parameters(): external_function_parameters {
 237          return new external_function_parameters (
 238              [
 239                  'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED),
 240                  'userid'   => new external_value(PARAM_INT, 'Return grades only for this user (optional)', VALUE_DEFAULT, 0),
 241                  'groupid'  => new external_value(PARAM_INT, 'Get users from this group only', VALUE_DEFAULT, 0)
 242              ]
 243          );
 244      }
 245  
 246      /**
 247       * Returns a list of grades tables for users in a course.
 248       *
 249       * @param int $courseid Course Id
 250       * @param int $userid   Only this user (optional)
 251       * @param int $groupid  Get users from this group only
 252       *
 253       * @return array the grades tables
 254       * @since Moodle 2.9
 255       */
 256      public static function get_grades_table(int $courseid, int $userid = 0, int $groupid = 0): array {
 257  
 258          list($params, $course, $context, $user, $groupid) = self::check_report_access($courseid, $userid, $groupid);
 259          $userid = $params['userid'];
 260  
 261          // We pass userid because it can be still 0.
 262          list($tables, $warnings) = self::get_report_data($course, $context, $user, $userid, $groupid);
 263  
 264          return [
 265              'tables' => $tables,
 266              'warnings' => $warnings
 267          ];
 268      }
 269  
 270      /**
 271       * Creates a table column structure
 272       *
 273       * @return array
 274       * @since  Moodle 2.9
 275       */
 276      private static function grades_table_column(): array {
 277          return [
 278              'class'   => new external_value(PARAM_RAW, 'class'),
 279              'content' => new external_value(PARAM_RAW, 'cell content'),
 280              'headers' => new external_value(PARAM_RAW, 'headers')
 281          ];
 282      }
 283  
 284      /**
 285       * Describes tget_grades_table return value.
 286       *
 287       * @return external_single_structure
 288       * @since Moodle 2.9
 289       */
 290      public static function get_grades_table_returns(): external_single_structure {
 291          return new external_single_structure(
 292              [
 293                  'tables' => new external_multiple_structure(
 294                      new external_single_structure(
 295                          [
 296                              'courseid' => new external_value(PARAM_INT, 'course id'),
 297                              'userid'   => new external_value(PARAM_INT, 'user id'),
 298                              'userfullname' => new external_value(PARAM_TEXT, 'user fullname'),
 299                              'maxdepth'   => new external_value(PARAM_INT, 'table max depth (needed for printing it)'),
 300                              'tabledata' => new external_multiple_structure(
 301                                  new external_single_structure(
 302                                      [
 303                                          'itemname' => new external_single_structure(
 304                                              [
 305                                                  'class' => new external_value(PARAM_RAW, 'class'),
 306                                                  'colspan' => new external_value(PARAM_INT, 'col span'),
 307                                                  'content'  => new external_value(PARAM_RAW, 'cell content'),
 308                                                  'id'  => new external_value(PARAM_ALPHANUMEXT, 'id')
 309                                              ], 'The item returned data', VALUE_OPTIONAL
 310                                          ),
 311                                          'leader' => new external_single_structure(
 312                                              [
 313                                                  'class' => new external_value(PARAM_RAW, 'class'),
 314                                                  'rowspan' => new external_value(PARAM_INT, 'row span')
 315                                              ], 'The item returned data', VALUE_OPTIONAL
 316                                          ),
 317                                          'weight' => new external_single_structure(
 318                                              self::grades_table_column(), 'weight column', VALUE_OPTIONAL
 319                                          ),
 320                                          'grade' => new external_single_structure(
 321                                              self::grades_table_column(), 'grade column', VALUE_OPTIONAL
 322                                          ),
 323                                          'range' => new external_single_structure(
 324                                              self::grades_table_column(), 'range column', VALUE_OPTIONAL
 325                                          ),
 326                                          'percentage' => new external_single_structure(
 327                                              self::grades_table_column(), 'percentage column', VALUE_OPTIONAL
 328                                          ),
 329                                          'lettergrade' => new external_single_structure(
 330                                              self::grades_table_column(), 'lettergrade column', VALUE_OPTIONAL
 331                                          ),
 332                                          'rank' => new external_single_structure(
 333                                              self::grades_table_column(), 'rank column', VALUE_OPTIONAL
 334                                          ),
 335                                          'average' => new external_single_structure(
 336                                              self::grades_table_column(), 'average column', VALUE_OPTIONAL
 337                                          ),
 338                                          'feedback' => new external_single_structure(
 339                                              self::grades_table_column(), 'feedback column', VALUE_OPTIONAL
 340                                          ),
 341                                          'contributiontocoursetotal' => new external_single_structure(
 342                                              self::grades_table_column(), 'contributiontocoursetotal column', VALUE_OPTIONAL
 343                                          ),
 344                                          'parentcategories' => new external_multiple_structure(
 345                                              new external_value(PARAM_INT, 'Parent grade category ID.')
 346                                          ),
 347                                      ], 'table'
 348                                  )
 349                              )
 350                          ]
 351                      )
 352                  ),
 353                  'warnings' => new external_warnings()
 354              ]
 355          );
 356      }
 357  
 358      /**
 359       * Returns description of method parameters
 360       *
 361       * @return external_function_parameters
 362       * @since Moodle 2.9
 363       */
 364      public static function view_grade_report_parameters(): external_function_parameters {
 365          return new external_function_parameters(
 366              [
 367                  'courseid' => new external_value(PARAM_INT, 'id of the course'),
 368                  'userid' => new external_value(PARAM_INT, 'id of the user, 0 means current user', VALUE_DEFAULT, 0),
 369              ]
 370          );
 371      }
 372  
 373      /**
 374       * Trigger the user report events, do the same that the web interface view of the report
 375       *
 376       * @param int $courseid id of course
 377       * @param int $userid id of the user the report belongs to
 378       * @return array of warnings and status result
 379       * @since Moodle 2.9
 380       * @throws moodle_exception
 381       */
 382      public static function view_grade_report(int $courseid, int $userid = 0): array {
 383          global $CFG, $USER;
 384          require_once($CFG->dirroot . "/grade/lib.php");
 385          require_once($CFG->dirroot . "/grade/report/user/lib.php");
 386  
 387          $params = self::validate_parameters(self::view_grade_report_parameters(),
 388              [
 389                  'courseid' => $courseid,
 390                  'userid' => $userid
 391              ]);
 392  
 393          $warnings = [];
 394  
 395          $course = get_course($params['courseid']);
 396  
 397          $context = context_course::instance($course->id);
 398          self::validate_context($context);
 399  
 400          $userid = $params['userid'];
 401          if (empty($userid)) {
 402              $userid = $USER->id;
 403          } else {
 404              $user = core_user::get_user($userid, '*', MUST_EXIST);
 405              core_user::require_active_user($user);
 406          }
 407  
 408          $access = false;
 409  
 410          if (has_capability('moodle/grade:viewall', $context)) {
 411              // Can view all course grades (any user).
 412              $access = true;
 413          } else if ($userid == $USER->id && has_capability('moodle/grade:view', $context) && $course->showgrades) {
 414              // View own grades.
 415              $access = true;
 416          }
 417  
 418          if (!$access) {
 419              throw new moodle_exception('nopermissiontoviewgrades', 'error');
 420          }
 421  
 422          // Create a report instance. We don't need the gpr second parameter.
 423          $report = new user_report($course->id, null, $context, $userid);
 424          $report->viewed();
 425  
 426          return [
 427              'status' => true,
 428              'warnings' => $warnings
 429          ];
 430      }
 431  
 432      /**
 433       * Returns description of method result value
 434       *
 435       * @return external_description
 436       * @since Moodle 2.9
 437       */
 438      public static function view_grade_report_returns(): external_description {
 439          return new external_single_structure(
 440              [
 441                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
 442                  'warnings' => new external_warnings()
 443              ]
 444          );
 445      }
 446  
 447      /**
 448       * Describes the parameters for get_grade_items.
 449       *
 450       * @return external_function_parameters
 451       * @since Moodle 3.2
 452       */
 453      public static function get_grade_items_parameters(): external_function_parameters {
 454          return self::get_grades_table_parameters();
 455      }
 456  
 457      /**
 458       * Returns the complete list of grade items for users in a course.
 459       *
 460       * @param int $courseid Course Id
 461       * @param int $userid   Only this user (optional)
 462       * @param int $groupid  Get users from this group only
 463       *
 464       * @return array the grades tables
 465       * @since Moodle 3.2
 466       */
 467      public static function get_grade_items(int $courseid, int $userid = 0, int $groupid = 0): array {
 468  
 469          list($params, $course, $context, $user, $groupid) = self::check_report_access($courseid, $userid, $groupid);
 470          $userid = $params['userid'];
 471  
 472          // We pass userid because it can be still 0.
 473          list($gradeitems, $warnings) = self::get_report_data($course, $context, $user, $userid, $groupid, false);
 474  
 475          foreach ($gradeitems as $gradeitem) {
 476              if (isset($gradeitem['feedback']) && isset($gradeitem['feedbackformat'])) {
 477                  list($gradeitem['feedback'], $gradeitem['feedbackformat']) =
 478                      external_format_text($gradeitem['feedback'], $gradeitem['feedbackformat'], $context->id);
 479              }
 480          }
 481  
 482          return [
 483              'usergrades' => $gradeitems,
 484              'warnings' => $warnings
 485          ];
 486      }
 487  
 488      /**
 489       * Describes tget_grade_items return value.
 490       *
 491       * @return external_single_structure
 492       * @since Moodle 3.2
 493       */
 494      public static function get_grade_items_returns(): external_single_structure {
 495          return new external_single_structure(
 496              [
 497                  'usergrades' => new external_multiple_structure(
 498                      new external_single_structure(
 499                          [
 500                              'courseid' => new external_value(PARAM_INT, 'course id'),
 501                              'courseidnumber' => new external_value(PARAM_TEXT, 'course idnumber'),
 502                              'userid'   => new external_value(PARAM_INT, 'user id'),
 503                              'userfullname' => new external_value(PARAM_TEXT, 'user fullname'),
 504                              'useridnumber' => new external_value(
 505                                  core_user::get_property_type('idnumber'), 'user idnumber'),
 506                              'maxdepth'   => new external_value(PARAM_INT, 'table max depth (needed for printing it)'),
 507                              'gradeitems' => new external_multiple_structure(
 508                                  new external_single_structure(
 509                                      [
 510                                          'id' => new external_value(PARAM_INT, 'Grade item id'),
 511                                          'itemname' => new external_value(PARAM_CLEANHTML, 'Grade item name'),
 512                                          'itemtype' => new external_value(PARAM_ALPHA, 'Grade item type'),
 513                                          'itemmodule' => new external_value(PARAM_PLUGIN, 'Grade item module'),
 514                                          'iteminstance' => new external_value(PARAM_INT, 'Grade item instance'),
 515                                          'itemnumber' => new external_value(PARAM_INT, 'Grade item item number'),
 516                                          'idnumber' => new external_value(PARAM_TEXT, 'Grade item idnumber'),
 517                                          'categoryid' => new external_value(PARAM_INT, 'Grade item category id'),
 518                                          'outcomeid' => new external_value(PARAM_INT, 'Outcome id'),
 519                                          'scaleid' => new external_value(PARAM_INT, 'Scale id'),
 520                                          'locked' => new external_value(PARAM_BOOL, 'Grade item for user locked?', VALUE_OPTIONAL),
 521                                          'cmid' => new external_value(PARAM_INT, 'Course module id (if type mod)', VALUE_OPTIONAL),
 522                                          'weightraw' => new external_value(PARAM_FLOAT, 'Weight raw', VALUE_OPTIONAL),
 523                                          'weightformatted' => new external_value(PARAM_NOTAGS, 'Weight', VALUE_OPTIONAL),
 524                                          'status' => new external_value(PARAM_ALPHA, 'Status', VALUE_OPTIONAL),
 525                                          'graderaw' => new external_value(PARAM_FLOAT, 'Grade raw', VALUE_OPTIONAL),
 526                                          'gradedatesubmitted' => new external_value(PARAM_INT, 'Grade submit date', VALUE_OPTIONAL),
 527                                          'gradedategraded' => new external_value(PARAM_INT, 'Grade graded date', VALUE_OPTIONAL),
 528                                          'gradehiddenbydate' => new external_value(PARAM_BOOL, 'Grade hidden by date?', VALUE_OPTIONAL),
 529                                          'gradeneedsupdate' => new external_value(PARAM_BOOL, 'Grade needs update?', VALUE_OPTIONAL),
 530                                          'gradeishidden' => new external_value(PARAM_BOOL, 'Grade is hidden?', VALUE_OPTIONAL),
 531                                          'gradeislocked' => new external_value(PARAM_BOOL, 'Grade is locked?', VALUE_OPTIONAL),
 532                                          'gradeisoverridden' => new external_value(PARAM_BOOL, 'Grade overridden?', VALUE_OPTIONAL),
 533                                          'gradeformatted' => new external_value(PARAM_RAW, 'The grade formatted', VALUE_OPTIONAL),
 534                                          'grademin' => new external_value(PARAM_FLOAT, 'Grade min', VALUE_OPTIONAL),
 535                                          'grademax' => new external_value(PARAM_FLOAT, 'Grade max', VALUE_OPTIONAL),
 536                                          'rangeformatted' => new external_value(PARAM_NOTAGS, 'Range formatted', VALUE_OPTIONAL),
 537                                          'percentageformatted' => new external_value(PARAM_NOTAGS, 'Percentage', VALUE_OPTIONAL),
 538                                          'lettergradeformatted' => new external_value(PARAM_NOTAGS, 'Letter grade', VALUE_OPTIONAL),
 539                                          'rank' => new external_value(PARAM_INT, 'Rank in the course', VALUE_OPTIONAL),
 540                                          'numusers' => new external_value(PARAM_INT, 'Num users in course', VALUE_OPTIONAL),
 541                                          'averageformatted' => new external_value(PARAM_NOTAGS, 'Grade average', VALUE_OPTIONAL),
 542                                          'feedback' => new external_value(PARAM_RAW, 'Grade feedback', VALUE_OPTIONAL),
 543                                          'feedbackformat' => new external_format_value('feedback', VALUE_OPTIONAL),
 544                                      ], 'Grade items'
 545                                  )
 546                              )
 547                          ]
 548                      )
 549                  ),
 550                  'warnings' => new external_warnings()
 551              ]
 552          );
 553      }
 554  }