Search moodle.org's
Developer Documentation

See Release Notes

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

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