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 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [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  /**
  18   * External assign API
  19   *
  20   * @package    mod_assign
  21   * @since      Moodle 2.4
  22   * @copyright  2012 Paul Charsley
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die;
  27  
  28  require_once("$CFG->libdir/externallib.php");
  29  require_once("$CFG->dirroot/user/externallib.php");
  30  require_once("$CFG->dirroot/mod/assign/locallib.php");
  31  
  32  /**
  33   * Assign functions
  34   * @copyright 2012 Paul Charsley
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class mod_assign_external extends \mod_assign\external\external_api {
  38  
  39      /**
  40       * Describes the parameters for get_grades
  41       * @return external_function_parameters
  42       * @since  Moodle 2.4
  43       */
  44      public static function get_grades_parameters() {
  45          return new external_function_parameters(
  46              array(
  47                  'assignmentids' => new external_multiple_structure(
  48                      new external_value(PARAM_INT, 'assignment id'),
  49                      '1 or more assignment ids',
  50                      VALUE_REQUIRED),
  51                  'since' => new external_value(PARAM_INT,
  52                            'timestamp, only return records where timemodified >= since',
  53                            VALUE_DEFAULT, 0)
  54              )
  55          );
  56      }
  57  
  58      /**
  59       * Returns grade information from assign_grades for the requested assignment ids
  60       * @param int[] $assignmentids
  61       * @param int $since only return records with timemodified >= since
  62       * @return array of grade records for each requested assignment
  63       * @since  Moodle 2.4
  64       */
  65      public static function get_grades($assignmentids, $since = 0) {
  66          global $DB;
  67          $params = self::validate_parameters(self::get_grades_parameters(),
  68                          array('assignmentids' => $assignmentids,
  69                                'since' => $since));
  70  
  71          $assignments = array();
  72          $warnings = array();
  73          $requestedassignmentids = $params['assignmentids'];
  74  
  75          // Check the user is allowed to get the grades for the assignments requested.
  76          $placeholders = array();
  77          list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
  78          $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
  79                 "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
  80          $placeholders['modname'] = 'assign';
  81          $cms = $DB->get_records_sql($sql, $placeholders);
  82          foreach ($cms as $cm) {
  83              try {
  84                  $context = context_module::instance($cm->id);
  85                  self::validate_context($context);
  86                  $assign = new assign($context, null, null);
  87                  $assign->require_view_grades();
  88              } catch (Exception $e) {
  89                  $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
  90                  $warning = array();
  91                  $warning['item'] = 'assignment';
  92                  $warning['itemid'] = $cm->instance;
  93                  $warning['warningcode'] = '1';
  94                  $warning['message'] = 'No access rights in module context';
  95                  $warnings[] = $warning;
  96              }
  97          }
  98  
  99          // Create the query and populate an array of grade records from the recordset results.
 100          if (count ($requestedassignmentids) > 0) {
 101              $placeholders = array();
 102              list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
 103  
 104              $sql = "SELECT ag.id,
 105                             ag.assignment,
 106                             ag.userid,
 107                             ag.timecreated,
 108                             ag.timemodified,
 109                             ag.grader,
 110                             ag.grade,
 111                             ag.attemptnumber
 112                        FROM {assign_grades} ag, {assign_submission} s
 113                       WHERE s.assignment $inorequalsql
 114                         AND s.userid = ag.userid
 115                         AND s.latest = 1
 116                         AND s.attemptnumber = ag.attemptnumber
 117                         AND ag.timemodified  >= :since
 118                         AND ag.assignment = s.assignment
 119                    ORDER BY ag.assignment, ag.id";
 120  
 121              $placeholders['since'] = $params['since'];
 122              $rs = $DB->get_recordset_sql($sql, $placeholders);
 123              $currentassignmentid = null;
 124              $assignment = null;
 125              foreach ($rs as $rd) {
 126                  $grade = array();
 127                  $grade['id'] = $rd->id;
 128                  $grade['userid'] = $rd->userid;
 129                  $grade['timecreated'] = $rd->timecreated;
 130                  $grade['timemodified'] = $rd->timemodified;
 131                  $grade['grader'] = $rd->grader;
 132                  $grade['attemptnumber'] = $rd->attemptnumber;
 133                  $grade['grade'] = (string)$rd->grade;
 134  
 135                  if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
 136                      if (!is_null($assignment)) {
 137                          $assignments[] = $assignment;
 138                      }
 139                      $assignment = array();
 140                      $assignment['assignmentid'] = $rd->assignment;
 141                      $assignment['grades'] = array();
 142                      $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
 143                  }
 144                  $assignment['grades'][] = $grade;
 145  
 146                  $currentassignmentid = $rd->assignment;
 147              }
 148              if (!is_null($assignment)) {
 149                  $assignments[] = $assignment;
 150              }
 151              $rs->close();
 152          }
 153          foreach ($requestedassignmentids as $assignmentid) {
 154              $warning = array();
 155              $warning['item'] = 'assignment';
 156              $warning['itemid'] = $assignmentid;
 157              $warning['warningcode'] = '3';
 158              $warning['message'] = 'No grades found';
 159              $warnings[] = $warning;
 160          }
 161  
 162          $result = array();
 163          $result['assignments'] = $assignments;
 164          $result['warnings'] = $warnings;
 165          return $result;
 166      }
 167  
 168      /**
 169       * Creates a grade single structure.
 170       *
 171       * @return external_single_structure a grade single structure.
 172       * @since  Moodle 3.1
 173       */
 174      private static function get_grade_structure($required = VALUE_REQUIRED) {
 175          return new external_single_structure(
 176              array(
 177                  'id'                => new external_value(PARAM_INT, 'grade id'),
 178                  'assignment'        => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
 179                  'userid'            => new external_value(PARAM_INT, 'student id'),
 180                  'attemptnumber'     => new external_value(PARAM_INT, 'attempt number'),
 181                  'timecreated'       => new external_value(PARAM_INT, 'grade creation time'),
 182                  'timemodified'      => new external_value(PARAM_INT, 'grade last modified time'),
 183                  'grader'            => new external_value(PARAM_INT, 'grader, -1 if grader is hidden'),
 184                  'grade'             => new external_value(PARAM_TEXT, 'grade'),
 185                  'gradefordisplay'   => new external_value(PARAM_RAW, 'grade rendered into a format suitable for display',
 186                                                              VALUE_OPTIONAL),
 187              ), 'grade information', $required
 188          );
 189      }
 190  
 191      /**
 192       * Creates an assign_grades external_single_structure
 193       * @return external_single_structure
 194       * @since  Moodle 2.4
 195       */
 196      private static function assign_grades() {
 197          return new external_single_structure(
 198              array (
 199                  'assignmentid'  => new external_value(PARAM_INT, 'assignment id'),
 200                  'grades'        => new external_multiple_structure(self::get_grade_structure())
 201              )
 202          );
 203      }
 204  
 205      /**
 206       * Describes the get_grades return value
 207       * @return external_single_structure
 208       * @since  Moodle 2.4
 209       */
 210      public static function get_grades_returns() {
 211          return new external_single_structure(
 212              array(
 213                  'assignments' => new external_multiple_structure(self::assign_grades(), 'list of assignment grade information'),
 214                  'warnings'      => new external_warnings('item is always \'assignment\'',
 215                      'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
 216                      'errorcode can be 3 (no grades found) or 1 (no permission to get grades)')
 217              )
 218          );
 219      }
 220  
 221      /**
 222       * Returns description of method parameters
 223       *
 224       * @return external_function_parameters
 225       * @since  Moodle 2.4
 226       */
 227      public static function get_assignments_parameters() {
 228          return new external_function_parameters(
 229              array(
 230                  'courseids' => new external_multiple_structure(
 231                      new external_value(PARAM_INT, 'course id, empty for retrieving all the courses where the user is enroled in'),
 232                      '0 or more course ids',
 233                      VALUE_DEFAULT, array()
 234                  ),
 235                  'capabilities'  => new external_multiple_structure(
 236                      new external_value(PARAM_CAPABILITY, 'capability'),
 237                      'list of capabilities used to filter courses',
 238                      VALUE_DEFAULT, array()
 239                  ),
 240                  'includenotenrolledcourses' => new external_value(PARAM_BOOL, 'whether to return courses that the user can see
 241                                                                      even if is not enroled in. This requires the parameter courseids
 242                                                                      to not be empty.', VALUE_DEFAULT, false)
 243              )
 244          );
 245      }
 246  
 247      /**
 248       * Returns an array of courses the user is enrolled, and for each course all of the assignments that the user can
 249       * view within that course.
 250       *
 251       * @param array $courseids An optional array of course ids. If provided only assignments within the given course
 252       * will be returned. If the user is not enrolled in or can't view a given course a warning will be generated and returned.
 253       * @param array $capabilities An array of additional capability checks you wish to be made on the course context.
 254       * @param bool $includenotenrolledcourses Wheter to return courses that the user can see even if is not enroled in.
 255       * This requires the parameter $courseids to not be empty.
 256       * @return An array of courses and warnings.
 257       * @since  Moodle 2.4
 258       */
 259      public static function get_assignments($courseids = array(), $capabilities = array(), $includenotenrolledcourses = false) {
 260          global $USER, $DB, $CFG;
 261  
 262          $params = self::validate_parameters(
 263              self::get_assignments_parameters(),
 264              array(
 265                  'courseids' => $courseids,
 266                  'capabilities' => $capabilities,
 267                  'includenotenrolledcourses' => $includenotenrolledcourses
 268              )
 269          );
 270  
 271          $warnings = array();
 272          $courses = array();
 273          $fields = 'sortorder,shortname,fullname,timemodified';
 274  
 275          // If the courseids list is empty, we return only the courses where the user is enrolled in.
 276          if (empty($params['courseids'])) {
 277              $courses = enrol_get_users_courses($USER->id, true, $fields);
 278              $courseids = array_keys($courses);
 279          } else if ($includenotenrolledcourses) {
 280              // In this case, we don't have to check here for enrolmnents. Maybe the user can see the course even if is not enrolled.
 281              $courseids = $params['courseids'];
 282          } else {
 283              // We need to check for enrolments.
 284              $mycourses = enrol_get_users_courses($USER->id, true, $fields);
 285              $mycourseids = array_keys($mycourses);
 286  
 287              foreach ($params['courseids'] as $courseid) {
 288                  if (!in_array($courseid, $mycourseids)) {
 289                      unset($courses[$courseid]);
 290                      $warnings[] = array(
 291                          'item' => 'course',
 292                          'itemid' => $courseid,
 293                          'warningcode' => '2',
 294                          'message' => 'User is not enrolled or does not have requested capability'
 295                      );
 296                  } else {
 297                      $courses[$courseid] = $mycourses[$courseid];
 298                  }
 299              }
 300              $courseids = array_keys($courses);
 301          }
 302  
 303          foreach ($courseids as $cid) {
 304  
 305              try {
 306                  $context = context_course::instance($cid);
 307                  self::validate_context($context);
 308  
 309                  // Check if this course was already loaded (by enrol_get_users_courses).
 310                  if (!isset($courses[$cid])) {
 311                      $courses[$cid] = get_course($cid);
 312                  }
 313                  $courses[$cid]->contextid = $context->id;
 314              } catch (Exception $e) {
 315                  unset($courses[$cid]);
 316                  $warnings[] = array(
 317                      'item' => 'course',
 318                      'itemid' => $cid,
 319                      'warningcode' => '1',
 320                      'message' => 'No access rights in course context '.$e->getMessage()
 321                  );
 322                  continue;
 323              }
 324              if (count($params['capabilities']) > 0 && !has_all_capabilities($params['capabilities'], $context)) {
 325                  unset($courses[$cid]);
 326              }
 327          }
 328          $extrafields='m.id as assignmentid, ' .
 329                       'm.course, ' .
 330                       'm.nosubmissions, ' .
 331                       'm.submissiondrafts, ' .
 332                       'm.sendnotifications, '.
 333                       'm.sendlatenotifications, ' .
 334                       'm.sendstudentnotifications, ' .
 335                       'm.duedate, ' .
 336                       'm.allowsubmissionsfromdate, '.
 337                       'm.grade, ' .
 338                       'm.timemodified, '.
 339                       'm.completionsubmit, ' .
 340                       'm.cutoffdate, ' .
 341                       'm.gradingduedate, ' .
 342                       'm.teamsubmission, ' .
 343                       'm.requireallteammemberssubmit, '.
 344                       'm.teamsubmissiongroupingid, ' .
 345                       'm.blindmarking, ' .
 346                       'm.hidegrader, ' .
 347                       'm.revealidentities, ' .
 348                       'm.attemptreopenmethod, '.
 349                       'm.maxattempts, ' .
 350                       'm.markingworkflow, ' .
 351                       'm.markingallocation, ' .
 352                       'm.requiresubmissionstatement, '.
 353                       'm.preventsubmissionnotingroup, '.
 354                       'm.intro, '.
 355                       'm.introformat,' .
 356                       'm.activity,' .
 357                       'm.activityformat,' .
 358                       'm.timelimit,' .
 359                       'm.submissionattachments';
 360          $coursearray = array();
 361          foreach ($courses as $id => $course) {
 362              $assignmentarray = array();
 363              // Get a list of assignments for the course.
 364              if ($modules = get_coursemodules_in_course('assign', $courses[$id]->id, $extrafields)) {
 365                  foreach ($modules as $module) {
 366                      $context = context_module::instance($module->id);
 367                      try {
 368                          self::validate_context($context);
 369                          require_capability('mod/assign:view', $context);
 370                      } catch (Exception $e) {
 371                          $warnings[] = array(
 372                              'item' => 'module',
 373                              'itemid' => $module->id,
 374                              'warningcode' => '1',
 375                              'message' => 'No access rights in module context'
 376                          );
 377                          continue;
 378                      }
 379  
 380                      $assign = new assign($context, null, null);
 381                      // Update assign with override information.
 382                      $assign->update_effective_access($USER->id);
 383  
 384                      // Get configurations for only enabled plugins.
 385                      $plugins = $assign->get_submission_plugins();
 386                      $plugins = array_merge($plugins, $assign->get_feedback_plugins());
 387  
 388                      $configarray = array();
 389                      foreach ($plugins as $plugin) {
 390                          if ($plugin->is_enabled() && $plugin->is_visible()) {
 391                              $configrecords = $plugin->get_config_for_external();
 392                              foreach ($configrecords as $name => $value) {
 393                                  $configarray[] = array(
 394                                      'plugin' => $plugin->get_type(),
 395                                      'subtype' => $plugin->get_subtype(),
 396                                      'name' => $name,
 397                                      'value' => $value
 398                                  );
 399                              }
 400                          }
 401                      }
 402  
 403                      $assignment = array(
 404                          'id' => $module->assignmentid,
 405                          'cmid' => $module->id,
 406                          'course' => $module->course,
 407                          'name' => external_format_string($module->name, $context),
 408                          'nosubmissions' => $module->nosubmissions,
 409                          'submissiondrafts' => $module->submissiondrafts,
 410                          'sendnotifications' => $module->sendnotifications,
 411                          'sendlatenotifications' => $module->sendlatenotifications,
 412                          'sendstudentnotifications' => $module->sendstudentnotifications,
 413                          'duedate' => $assign->get_instance()->duedate,
 414                          'allowsubmissionsfromdate' => $assign->get_instance()->allowsubmissionsfromdate,
 415                          'grade' => $module->grade,
 416                          'timemodified' => $module->timemodified,
 417                          'completionsubmit' => $module->completionsubmit,
 418                          'cutoffdate' => $assign->get_instance()->cutoffdate,
 419                          'gradingduedate' => $assign->get_instance()->gradingduedate,
 420                          'teamsubmission' => $module->teamsubmission,
 421                          'requireallteammemberssubmit' => $module->requireallteammemberssubmit,
 422                          'teamsubmissiongroupingid' => $module->teamsubmissiongroupingid,
 423                          'blindmarking' => $module->blindmarking,
 424                          'hidegrader' => $module->hidegrader,
 425                          'revealidentities' => $module->revealidentities,
 426                          'attemptreopenmethod' => $module->attemptreopenmethod,
 427                          'maxattempts' => $module->maxattempts,
 428                          'markingworkflow' => $module->markingworkflow,
 429                          'markingallocation' => $module->markingallocation,
 430                          'requiresubmissionstatement' => $module->requiresubmissionstatement,
 431                          'preventsubmissionnotingroup' => $module->preventsubmissionnotingroup,
 432                          'timelimit' => $module->timelimit,
 433                          'submissionattachments' => $module->submissionattachments,
 434                          'configs' => $configarray
 435                      );
 436  
 437                      // Return or not intro and file attachments depending on the plugin settings.
 438                      if ($assign->show_intro()) {
 439                          $options = array('noclean' => true);
 440                          list($assignment['intro'], $assignment['introformat']) =
 441                              external_format_text($module->intro, $module->introformat, $context->id, 'mod_assign', 'intro', null,
 442                                  $options);
 443                          $assignment['introfiles'] = external_util::get_area_files($context->id, 'mod_assign', 'intro', false,
 444                                                                                      false);
 445                          if ($assign->should_provide_intro_attachments($USER->id)) {
 446                              $assignment['introattachments'] = external_util::get_area_files($context->id, 'mod_assign',
 447                                  ASSIGN_INTROATTACHMENT_FILEAREA, 0);
 448                          }
 449                      }
 450  
 451                      if ($module->requiresubmissionstatement) {
 452                          // Submission statement is required, return the submission statement value.
 453                          $adminconfig = get_config('assign');
 454                          // Single submission.
 455                          if (!$module->teamsubmission) {
 456                              list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
 457                                  external_format_text($adminconfig->submissionstatement, FORMAT_MOODLE, $context->id,
 458                                      'mod_assign', '', 0);
 459                          } else { // Team submission.
 460                              // One user can submit for the whole team.
 461                              if (!empty($adminconfig->submissionstatementteamsubmission) && !$module->requireallteammemberssubmit) {
 462                                  list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
 463                                      external_format_text($adminconfig->submissionstatementteamsubmission,
 464                                          FORMAT_MOODLE, $context->id, 'mod_assign', '', 0);
 465                              } else if (!empty($adminconfig->submissionstatementteamsubmissionallsubmit) &&
 466                                  $module->requireallteammemberssubmit) {
 467                                  // All team members must submit.
 468                                  list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
 469                                      external_format_text($adminconfig->submissionstatementteamsubmissionallsubmit,
 470                                          FORMAT_MOODLE, $context->id, 'mod_assign', '', 0);
 471                              }
 472                          }
 473                      }
 474  
 475                      if ($module->activity && $assign->submissions_open($USER->id, true)) {
 476                          list($assignment['activity'], $assignment['activityformat']) = external_format_text($module->activity,
 477                                  $module->activityformat, $context->id, 'mod_assign', ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
 478                          $assignment['activityattachments'] = external_util::get_area_files($context->id, 'mod_assign',
 479                              ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
 480                      }
 481  
 482                      $assignmentarray[] = $assignment;
 483                  }
 484              }
 485              $coursearray[]= array(
 486                  'id' => $courses[$id]->id,
 487                  'fullname' => external_format_string($courses[$id]->fullname, $course->contextid),
 488                  'shortname' => external_format_string($courses[$id]->shortname, $course->contextid),
 489                  'timemodified' => $courses[$id]->timemodified,
 490                  'assignments' => $assignmentarray
 491              );
 492          }
 493  
 494          $result = array(
 495              'courses' => $coursearray,
 496              'warnings' => $warnings
 497          );
 498          return $result;
 499      }
 500  
 501      /**
 502       * Creates an assignment external_single_structure
 503       *
 504       * @return external_single_structure
 505       * @since Moodle 2.4
 506       */
 507      private static function get_assignments_assignment_structure() {
 508          return new external_single_structure(
 509              array(
 510                  'id' => new external_value(PARAM_INT, 'assignment id'),
 511                  'cmid' => new external_value(PARAM_INT, 'course module id'),
 512                  'course' => new external_value(PARAM_INT, 'course id'),
 513                  'name' => new external_value(PARAM_RAW, 'assignment name'),
 514                  'nosubmissions' => new external_value(PARAM_INT, 'no submissions'),
 515                  'submissiondrafts' => new external_value(PARAM_INT, 'submissions drafts'),
 516                  'sendnotifications' => new external_value(PARAM_INT, 'send notifications'),
 517                  'sendlatenotifications' => new external_value(PARAM_INT, 'send notifications'),
 518                  'sendstudentnotifications' => new external_value(PARAM_INT, 'send student notifications (default)'),
 519                  'duedate' => new external_value(PARAM_INT, 'assignment due date'),
 520                  'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allow submissions from date'),
 521                  'grade' => new external_value(PARAM_INT, 'grade type'),
 522                  'timemodified' => new external_value(PARAM_INT, 'last time assignment was modified'),
 523                  'completionsubmit' => new external_value(PARAM_INT, 'if enabled, set activity as complete following submission'),
 524                  'cutoffdate' => new external_value(PARAM_INT, 'date after which submission is not accepted without an extension'),
 525                  'gradingduedate' => new external_value(PARAM_INT, 'the expected date for marking the submissions'),
 526                  'teamsubmission' => new external_value(PARAM_INT, 'if enabled, students submit as a team'),
 527                  'requireallteammemberssubmit' => new external_value(PARAM_INT, 'if enabled, all team members must submit'),
 528                  'teamsubmissiongroupingid' => new external_value(PARAM_INT, 'the grouping id for the team submission groups'),
 529                  'blindmarking' => new external_value(PARAM_INT, 'if enabled, hide identities until reveal identities actioned'),
 530                  'hidegrader' => new external_value(PARAM_INT, 'If enabled, hide grader to student'),
 531                  'revealidentities' => new external_value(PARAM_INT, 'show identities for a blind marking assignment'),
 532                  'attemptreopenmethod' => new external_value(PARAM_TEXT, 'method used to control opening new attempts'),
 533                  'maxattempts' => new external_value(PARAM_INT, 'maximum number of attempts allowed'),
 534                  'markingworkflow' => new external_value(PARAM_INT, 'enable marking workflow'),
 535                  'markingallocation' => new external_value(PARAM_INT, 'enable marking allocation'),
 536                  'requiresubmissionstatement' => new external_value(PARAM_INT, 'student must accept submission statement'),
 537                  'preventsubmissionnotingroup' => new external_value(PARAM_INT, 'Prevent submission not in group', VALUE_OPTIONAL),
 538                  'submissionstatement' => new external_value(PARAM_RAW, 'Submission statement formatted.', VALUE_OPTIONAL),
 539                  'submissionstatementformat' => new external_format_value('submissionstatement', VALUE_OPTIONAL),
 540                  'configs' => new external_multiple_structure(self::get_assignments_config_structure(), 'configuration settings'),
 541                  'intro' => new external_value(PARAM_RAW,
 542                      'assignment intro, not allways returned because it deppends on the activity configuration', VALUE_OPTIONAL),
 543                  'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
 544                  'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
 545                  'introattachments' => new external_files('intro attachments files', VALUE_OPTIONAL),
 546                  'activity' => new external_value(PARAM_RAW, 'Description of activity', VALUE_OPTIONAL),
 547                  'activityformat' => new external_format_value('activity', VALUE_OPTIONAL),
 548                  'activityattachments' => new external_files('Files from activity field', VALUE_OPTIONAL),
 549                  'timelimit' => new external_value(PARAM_INT, 'Time limit to complete assigment', VALUE_OPTIONAL),
 550                  'submissionattachments' => new external_value(PARAM_INT,
 551                      'Flag to only show files during submission', VALUE_OPTIONAL),
 552              ), 'assignment information object');
 553      }
 554  
 555      /**
 556       * Creates an assign_plugin_config external_single_structure
 557       *
 558       * @return external_single_structure
 559       * @since Moodle 2.4
 560       */
 561      private static function get_assignments_config_structure() {
 562          return new external_single_structure(
 563              array(
 564                  'id' => new external_value(PARAM_INT, 'assign_plugin_config id', VALUE_OPTIONAL),
 565                  'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
 566                  'plugin' => new external_value(PARAM_TEXT, 'plugin'),
 567                  'subtype' => new external_value(PARAM_TEXT, 'subtype'),
 568                  'name' => new external_value(PARAM_TEXT, 'name'),
 569                  'value' => new external_value(PARAM_TEXT, 'value')
 570              ), 'assignment configuration object'
 571          );
 572      }
 573  
 574      /**
 575       * Creates a course external_single_structure
 576       *
 577       * @return external_single_structure
 578       * @since Moodle 2.4
 579       */
 580      private static function get_assignments_course_structure() {
 581          return new external_single_structure(
 582              array(
 583                  'id' => new external_value(PARAM_INT, 'course id'),
 584                  'fullname' => new external_value(PARAM_RAW, 'course full name'),
 585                  'shortname' => new external_value(PARAM_RAW, 'course short name'),
 586                  'timemodified' => new external_value(PARAM_INT, 'last time modified'),
 587                  'assignments' => new external_multiple_structure(self::get_assignments_assignment_structure(), 'assignment info')
 588                ), 'course information object'
 589          );
 590      }
 591  
 592      /**
 593       * Describes the return value for get_assignments
 594       *
 595       * @return external_single_structure
 596       * @since Moodle 2.4
 597       */
 598      public static function get_assignments_returns() {
 599          return new external_single_structure(
 600              array(
 601                  'courses' => new external_multiple_structure(self::get_assignments_course_structure(), 'list of courses'),
 602                  'warnings'  => new external_warnings('item can be \'course\' (errorcode 1 or 2) or \'module\' (errorcode 1)',
 603                      'When item is a course then itemid is a course id. When the item is a module then itemid is a module id',
 604                      'errorcode can be 1 (no access rights) or 2 (not enrolled or no permissions)')
 605              )
 606          );
 607      }
 608  
 609      /**
 610       * Return information (files and text fields) for the given plugins in the assignment.
 611       *
 612       * @param  assign $assign the assignment object
 613       * @param  array $assignplugins array of assignment plugins (submission or feedback)
 614       * @param  stdClass $item the item object (submission or grade)
 615       * @return array an array containing the plugins returned information
 616       */
 617      private static function get_plugins_data($assign, $assignplugins, $item) {
 618          global $CFG;
 619  
 620          $plugins = array();
 621          $fs = get_file_storage();
 622  
 623          foreach ($assignplugins as $assignplugin) {
 624  
 625              if (!$assignplugin->is_enabled() or !$assignplugin->is_visible()) {
 626                  continue;
 627              }
 628  
 629              $plugin = array(
 630                  'name' => $assignplugin->get_name(),
 631                  'type' => $assignplugin->get_type()
 632              );
 633              // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
 634              $component = $assignplugin->get_subtype().'_'.$assignplugin->get_type();
 635  
 636              $fileareas = $assignplugin->get_file_areas();
 637              foreach ($fileareas as $filearea => $name) {
 638                  $fileareainfo = array('area' => $filearea);
 639  
 640                  $fileareainfo['files'] = external_util::get_area_files(
 641                      $assign->get_context()->id,
 642                      $component,
 643                      $filearea,
 644                      $item->id
 645                  );
 646  
 647                  $plugin['fileareas'][] = $fileareainfo;
 648              }
 649  
 650              $editorfields = $assignplugin->get_editor_fields();
 651              foreach ($editorfields as $name => $description) {
 652                  $editorfieldinfo = array(
 653                      'name' => $name,
 654                      'description' => $description,
 655                      'text' => $assignplugin->get_editor_text($name, $item->id),
 656                      'format' => $assignplugin->get_editor_format($name, $item->id)
 657                  );
 658  
 659                  // Now format the text.
 660                  foreach ($fileareas as $filearea => $name) {
 661                      list($editorfieldinfo['text'], $editorfieldinfo['format']) = external_format_text(
 662                          $editorfieldinfo['text'], $editorfieldinfo['format'], $assign->get_context()->id,
 663                          $component, $filearea, $item->id);
 664                  }
 665  
 666                  $plugin['editorfields'][] = $editorfieldinfo;
 667              }
 668              $plugins[] = $plugin;
 669          }
 670          return $plugins;
 671      }
 672  
 673      /**
 674       * Describes the parameters for get_submissions
 675       *
 676       * @return external_function_parameters
 677       * @since Moodle 2.5
 678       */
 679      public static function get_submissions_parameters() {
 680          return new external_function_parameters(
 681              array(
 682                  'assignmentids' => new external_multiple_structure(
 683                      new external_value(PARAM_INT, 'assignment id'),
 684                      '1 or more assignment ids',
 685                      VALUE_REQUIRED),
 686                  'status' => new external_value(PARAM_ALPHA, 'status', VALUE_DEFAULT, ''),
 687                  'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0),
 688                  'before' => new external_value(PARAM_INT, 'submitted before', VALUE_DEFAULT, 0)
 689              )
 690          );
 691      }
 692  
 693      /**
 694       * Returns submissions for the requested assignment ids
 695       *
 696       * @param int[] $assignmentids
 697       * @param string $status only return submissions with this status
 698       * @param int $since only return submissions with timemodified >= since
 699       * @param int $before only return submissions with timemodified <= before
 700       * @return array of submissions for each requested assignment
 701       * @since Moodle 2.5
 702       */
 703      public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
 704          global $DB, $CFG;
 705  
 706          $params = self::validate_parameters(self::get_submissions_parameters(),
 707                          array('assignmentids' => $assignmentids,
 708                                'status' => $status,
 709                                'since' => $since,
 710                                'before' => $before));
 711  
 712          $warnings = array();
 713          $assignments = array();
 714  
 715          // Check the user is allowed to get the submissions for the assignments requested.
 716          $placeholders = array();
 717          list($inorequalsql, $placeholders) = $DB->get_in_or_equal($params['assignmentids'], SQL_PARAMS_NAMED);
 718          $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
 719                 "WHERE md.name = :modname AND cm.instance ".$inorequalsql;
 720          $placeholders['modname'] = 'assign';
 721          $cms = $DB->get_records_sql($sql, $placeholders);
 722          $assigns = array();
 723          foreach ($cms as $cm) {
 724              try {
 725                  $context = context_module::instance($cm->id);
 726                  self::validate_context($context);
 727                  $assign = new assign($context, null, null);
 728                  $assign->require_view_grades();
 729                  $assigns[] = $assign;
 730              } catch (Exception $e) {
 731                  $warnings[] = array(
 732                      'item' => 'assignment',
 733                      'itemid' => $cm->instance,
 734                      'warningcode' => '1',
 735                      'message' => 'No access rights in module context'
 736                  );
 737              }
 738          }
 739  
 740          foreach ($assigns as $assign) {
 741              $submissions = array();
 742              $placeholders = array('assignid1' => $assign->get_instance()->id,
 743                                    'assignid2' => $assign->get_instance()->id);
 744  
 745              $submissionmaxattempt = 'SELECT mxs.userid, mxs.groupid, MAX(mxs.attemptnumber) AS maxattempt
 746                                       FROM {assign_submission} mxs
 747                                       WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid, mxs.groupid';
 748  
 749              $sql = "SELECT mas.id, mas.assignment,mas.userid,".
 750                     "mas.timecreated,mas.timemodified,mas.timestarted,mas.status,mas.groupid,mas.attemptnumber ".
 751                     "FROM {assign_submission} mas ".
 752                     "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
 753                     "AND mas.groupid = smx.groupid ".
 754                     "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
 755  
 756              if (!empty($params['status'])) {
 757                  $placeholders['status'] = $params['status'];
 758                  $sql = $sql." AND mas.status = :status";
 759              }
 760              if (!empty($params['before'])) {
 761                  $placeholders['since'] = $params['since'];
 762                  $placeholders['before'] = $params['before'];
 763                  $sql = $sql." AND mas.timemodified BETWEEN :since AND :before";
 764              } else {
 765                  $placeholders['since'] = $params['since'];
 766                  $sql = $sql." AND mas.timemodified >= :since";
 767              }
 768  
 769              $submissionrecords = $DB->get_records_sql($sql, $placeholders);
 770  
 771              if (!empty($submissionrecords)) {
 772                  $submissionplugins = $assign->get_submission_plugins();
 773                  foreach ($submissionrecords as $submissionrecord) {
 774                      $submission = array(
 775                          'id' => $submissionrecord->id,
 776                          'userid' => $submissionrecord->userid,
 777                          'timecreated' => $submissionrecord->timecreated,
 778                          'timemodified' => $submissionrecord->timemodified,
 779                          'timestarted' => $submissionrecord->timestarted,
 780                          'status' => $submissionrecord->status,
 781                          'attemptnumber' => $submissionrecord->attemptnumber,
 782                          'groupid' => $submissionrecord->groupid,
 783                          'plugins' => self::get_plugins_data($assign, $submissionplugins, $submissionrecord),
 784                          'gradingstatus' => $assign->get_grading_status($submissionrecord->userid)
 785                      );
 786  
 787                      if (($assign->get_instance()->teamsubmission
 788                          && $assign->can_view_group_submission($submissionrecord->groupid))
 789                          || (!$assign->get_instance()->teamsubmission
 790                          && $assign->can_view_submission($submissionrecord->userid))
 791                      ) {
 792                          $submissions[] = $submission;
 793                      }
 794                  }
 795              } else {
 796                  $warnings[] = array(
 797                      'item' => 'module',
 798                      'itemid' => $assign->get_instance()->id,
 799                      'warningcode' => '3',
 800                      'message' => 'No submissions found'
 801                  );
 802              }
 803  
 804              $assignments[] = array(
 805                  'assignmentid' => $assign->get_instance()->id,
 806                  'submissions' => $submissions
 807              );
 808  
 809          }
 810  
 811          $result = array(
 812              'assignments' => $assignments,
 813              'warnings' => $warnings
 814          );
 815          return $result;
 816      }
 817  
 818      /**
 819       * Creates an assignment plugin structure.
 820       *
 821       * @return external_single_structure the plugin structure
 822       */
 823      private static function get_plugin_structure() {
 824          return new external_single_structure(
 825              array(
 826                  'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
 827                  'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
 828                  'fileareas' => new external_multiple_structure(
 829                      new external_single_structure(
 830                          array (
 831                              'area' => new external_value (PARAM_TEXT, 'file area'),
 832                              'files' => new external_files('files', VALUE_OPTIONAL),
 833                          )
 834                      ), 'fileareas', VALUE_OPTIONAL
 835                  ),
 836                  'editorfields' => new external_multiple_structure(
 837                      new external_single_structure(
 838                          array(
 839                              'name' => new external_value(PARAM_TEXT, 'field name'),
 840                              'description' => new external_value(PARAM_RAW, 'field description'),
 841                              'text' => new external_value (PARAM_RAW, 'field value'),
 842                              'format' => new external_format_value ('text')
 843                          )
 844                      )
 845                      , 'editorfields', VALUE_OPTIONAL
 846                  )
 847              )
 848          );
 849      }
 850  
 851      /**
 852       * Creates a submission structure.
 853       *
 854       * @return external_single_structure the submission structure
 855       */
 856      private static function get_submission_structure($required = VALUE_REQUIRED) {
 857          return new external_single_structure(
 858              array(
 859                  'id' => new external_value(PARAM_INT, 'submission id'),
 860                  'userid' => new external_value(PARAM_INT, 'student id'),
 861                  'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
 862                  'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
 863                  'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
 864                  'timestarted' => new external_value(PARAM_INT, 'submission start time', VALUE_OPTIONAL),
 865                  'status' => new external_value(PARAM_TEXT, 'submission status'),
 866                  'groupid' => new external_value(PARAM_INT, 'group id'),
 867                  'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
 868                  'latest' => new external_value(PARAM_INT, 'latest attempt', VALUE_OPTIONAL),
 869                  'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'plugins', VALUE_OPTIONAL),
 870                  'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.', VALUE_OPTIONAL),
 871              ), 'submission info', $required
 872          );
 873      }
 874  
 875      /**
 876       * Creates an assign_submissions external_single_structure
 877       *
 878       * @return external_single_structure
 879       * @since Moodle 2.5
 880       */
 881      private static function get_submissions_structure() {
 882          return new external_single_structure(
 883              array (
 884                  'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
 885                  'submissions' => new external_multiple_structure(self::get_submission_structure())
 886              )
 887          );
 888      }
 889  
 890      /**
 891       * Describes the get_submissions return value
 892       *
 893       * @return external_single_structure
 894       * @since Moodle 2.5
 895       */
 896      public static function get_submissions_returns() {
 897          return new external_single_structure(
 898              array(
 899                  'assignments' => new external_multiple_structure(self::get_submissions_structure(), 'assignment submissions'),
 900                  'warnings' => new external_warnings()
 901              )
 902          );
 903      }
 904  
 905      /**
 906       * Describes the parameters for set_user_flags
 907       * @return external_function_parameters
 908       * @since  Moodle 2.6
 909       */
 910      public static function set_user_flags_parameters() {
 911          return new external_function_parameters(
 912              array(
 913                  'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
 914                  'userflags' => new external_multiple_structure(
 915                      new external_single_structure(
 916                          array(
 917                              'userid'           => new external_value(PARAM_INT, 'student id'),
 918                              'locked'           => new external_value(PARAM_INT, 'locked', VALUE_OPTIONAL),
 919                              'mailed'           => new external_value(PARAM_INT, 'mailed', VALUE_OPTIONAL),
 920                              'extensionduedate' => new external_value(PARAM_INT, 'extension due date', VALUE_OPTIONAL),
 921                              'workflowstate'    => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
 922                              'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker', VALUE_OPTIONAL)
 923                          )
 924                      )
 925                  )
 926              )
 927          );
 928      }
 929  
 930      /**
 931       * Create or update user_flags records
 932       *
 933       * @param int $assignmentid the assignment for which the userflags are created or updated
 934       * @param array $userflags  An array of userflags to create or update
 935       * @return array containing success or failure information for each record
 936       * @since Moodle 2.6
 937       */
 938      public static function set_user_flags($assignmentid, $userflags = array()) {
 939          global $CFG, $DB;
 940  
 941          $params = self::validate_parameters(self::set_user_flags_parameters(),
 942                                              array('assignmentid' => $assignmentid,
 943                                                    'userflags' => $userflags));
 944  
 945          // Load assignment if it exists and if the user has the capability.
 946          list($assign, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
 947          require_capability('mod/assign:grade', $context);
 948  
 949          $results = array();
 950          foreach ($params['userflags'] as $userflag) {
 951              $success = true;
 952              $result = array();
 953  
 954              $record = $assign->get_user_flags($userflag['userid'], false);
 955              if ($record) {
 956                  if (isset($userflag['locked'])) {
 957                      $record->locked = $userflag['locked'];
 958                  }
 959                  if (isset($userflag['mailed'])) {
 960                      $record->mailed = $userflag['mailed'];
 961                  }
 962                  if (isset($userflag['extensionduedate'])) {
 963                      $record->extensionduedate = $userflag['extensionduedate'];
 964                  }
 965                  if (isset($userflag['workflowstate'])) {
 966                      $record->workflowstate = $userflag['workflowstate'];
 967                  }
 968                  if (isset($userflag['allocatedmarker'])) {
 969                      $record->allocatedmarker = $userflag['allocatedmarker'];
 970                  }
 971                  if ($assign->update_user_flags($record)) {
 972                      $result['id'] = $record->id;
 973                      $result['userid'] = $userflag['userid'];
 974                  } else {
 975                      $result['id'] = $record->id;
 976                      $result['userid'] = $userflag['userid'];
 977                      $result['errormessage'] = 'Record created but values could not be set';
 978                  }
 979              } else {
 980                  $record = $assign->get_user_flags($userflag['userid'], true);
 981                  $setfields = isset($userflag['locked'])
 982                               || isset($userflag['mailed'])
 983                               || isset($userflag['extensionduedate'])
 984                               || isset($userflag['workflowstate'])
 985                               || isset($userflag['allocatedmarker']);
 986                  if ($record) {
 987                      if ($setfields) {
 988                          if (isset($userflag['locked'])) {
 989                              $record->locked = $userflag['locked'];
 990                          }
 991                          if (isset($userflag['mailed'])) {
 992                              $record->mailed = $userflag['mailed'];
 993                          }
 994                          if (isset($userflag['extensionduedate'])) {
 995                              $record->extensionduedate = $userflag['extensionduedate'];
 996                          }
 997                          if (isset($userflag['workflowstate'])) {
 998                              $record->workflowstate = $userflag['workflowstate'];
 999                          }
1000                          if (isset($userflag['allocatedmarker'])) {
1001                              $record->allocatedmarker = $userflag['allocatedmarker'];
1002                          }
1003                          if ($assign->update_user_flags($record)) {
1004                              $result['id'] = $record->id;
1005                              $result['userid'] = $userflag['userid'];
1006                          } else {
1007                              $result['id'] = $record->id;
1008                              $result['userid'] = $userflag['userid'];
1009                              $result['errormessage'] = 'Record created but values could not be set';
1010                          }
1011                      } else {
1012                          $result['id'] = $record->id;
1013                          $result['userid'] = $userflag['userid'];
1014                      }
1015                  } else {
1016                      $result['id'] = -1;
1017                      $result['userid'] = $userflag['userid'];
1018                      $result['errormessage'] = 'Record could not be created';
1019                  }
1020              }
1021  
1022              $results[] = $result;
1023          }
1024          return $results;
1025      }
1026  
1027      /**
1028       * Describes the set_user_flags return value
1029       * @return external_multiple_structure
1030       * @since  Moodle 2.6
1031       */
1032      public static function set_user_flags_returns() {
1033          return new external_multiple_structure(
1034              new external_single_structure(
1035                  array(
1036                      'id' => new external_value(PARAM_INT, 'id of record if successful, -1 for failure'),
1037                      'userid' => new external_value(PARAM_INT, 'userid of record'),
1038                      'errormessage' => new external_value(PARAM_TEXT, 'Failure error message', VALUE_OPTIONAL)
1039                  )
1040              )
1041          );
1042      }
1043  
1044      /**
1045       * Describes the parameters for get_user_flags
1046       * @return external_function_parameters
1047       * @since  Moodle 2.6
1048       */
1049      public static function get_user_flags_parameters() {
1050          return new external_function_parameters(
1051              array(
1052                  'assignmentids' => new external_multiple_structure(
1053                      new external_value(PARAM_INT, 'assignment id'),
1054                      '1 or more assignment ids',
1055                      VALUE_REQUIRED)
1056              )
1057          );
1058      }
1059  
1060      /**
1061       * Returns user flag information from assign_user_flags for the requested assignment ids
1062       * @param int[] $assignmentids
1063       * @return array of user flag records for each requested assignment
1064       * @since  Moodle 2.6
1065       */
1066      public static function get_user_flags($assignmentids) {
1067          global $DB;
1068          $params = self::validate_parameters(self::get_user_flags_parameters(),
1069                          array('assignmentids' => $assignmentids));
1070  
1071          $assignments = array();
1072          $warnings = array();
1073          $requestedassignmentids = $params['assignmentids'];
1074  
1075          // Check the user is allowed to get the user flags for the assignments requested.
1076          $placeholders = array();
1077          list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1078          $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1079                 "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1080          $placeholders['modname'] = 'assign';
1081          $cms = $DB->get_records_sql($sql, $placeholders);
1082          foreach ($cms as $cm) {
1083              try {
1084                  $context = context_module::instance($cm->id);
1085                  self::validate_context($context);
1086                  require_capability('mod/assign:grade', $context);
1087              } catch (Exception $e) {
1088                  $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1089                  $warning = array();
1090                  $warning['item'] = 'assignment';
1091                  $warning['itemid'] = $cm->instance;
1092                  $warning['warningcode'] = '1';
1093                  $warning['message'] = 'No access rights in module context';
1094                  $warnings[] = $warning;
1095              }
1096          }
1097  
1098          // Create the query and populate an array of assign_user_flags records from the recordset results.
1099          if (count ($requestedassignmentids) > 0) {
1100              $placeholders = array();
1101              list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1102  
1103              $sql = "SELECT auf.id,auf.assignment,auf.userid,auf.locked,auf.mailed,".
1104                     "auf.extensionduedate,auf.workflowstate,auf.allocatedmarker ".
1105                     "FROM {assign_user_flags} auf ".
1106                     "WHERE auf.assignment ".$inorequalsql.
1107                     " ORDER BY auf.assignment, auf.id";
1108  
1109              $rs = $DB->get_recordset_sql($sql, $placeholders);
1110              $currentassignmentid = null;
1111              $assignment = null;
1112              foreach ($rs as $rd) {
1113                  $userflag = array();
1114                  $userflag['id'] = $rd->id;
1115                  $userflag['userid'] = $rd->userid;
1116                  $userflag['locked'] = $rd->locked;
1117                  $userflag['mailed'] = $rd->mailed;
1118                  $userflag['extensionduedate'] = $rd->extensionduedate;
1119                  $userflag['workflowstate'] = $rd->workflowstate;
1120                  $userflag['allocatedmarker'] = $rd->allocatedmarker;
1121  
1122                  if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1123                      if (!is_null($assignment)) {
1124                          $assignments[] = $assignment;
1125                      }
1126                      $assignment = array();
1127                      $assignment['assignmentid'] = $rd->assignment;
1128                      $assignment['userflags'] = array();
1129                      $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1130                  }
1131                  $assignment['userflags'][] = $userflag;
1132  
1133                  $currentassignmentid = $rd->assignment;
1134              }
1135              if (!is_null($assignment)) {
1136                  $assignments[] = $assignment;
1137              }
1138              $rs->close();
1139  
1140          }
1141  
1142          foreach ($requestedassignmentids as $assignmentid) {
1143              $warning = array();
1144              $warning['item'] = 'assignment';
1145              $warning['itemid'] = $assignmentid;
1146              $warning['warningcode'] = '3';
1147              $warning['message'] = 'No user flags found';
1148              $warnings[] = $warning;
1149          }
1150  
1151          $result = array();
1152          $result['assignments'] = $assignments;
1153          $result['warnings'] = $warnings;
1154          return $result;
1155      }
1156  
1157      /**
1158       * Creates an assign_user_flags external_single_structure
1159       * @return external_single_structure
1160       * @since  Moodle 2.6
1161       */
1162      private static function assign_user_flags() {
1163          return new external_single_structure(
1164              array (
1165                  'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1166                  'userflags'   => new external_multiple_structure(new external_single_structure(
1167                          array(
1168                              'id'               => new external_value(PARAM_INT, 'user flag id'),
1169                              'userid'           => new external_value(PARAM_INT, 'student id'),
1170                              'locked'           => new external_value(PARAM_INT, 'locked'),
1171                              'mailed'           => new external_value(PARAM_INT, 'mailed'),
1172                              'extensionduedate' => new external_value(PARAM_INT, 'extension due date'),
1173                              'workflowstate'    => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
1174                              'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker')
1175                          )
1176                      )
1177                  )
1178              )
1179          );
1180      }
1181  
1182      /**
1183       * Describes the get_user_flags return value
1184       * @return external_single_structure
1185       * @since  Moodle 2.6
1186       */
1187      public static function get_user_flags_returns() {
1188          return new external_single_structure(
1189              array(
1190                  'assignments' => new external_multiple_structure(self::assign_user_flags(), 'list of assign user flag information'),
1191                  'warnings'      => new external_warnings('item is always \'assignment\'',
1192                      'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1193                      'errorcode can be 3 (no user flags found) or 1 (no permission to get user flags)')
1194              )
1195          );
1196      }
1197  
1198      /**
1199       * Describes the parameters for get_user_mappings
1200       * @return external_function_parameters
1201       * @since  Moodle 2.6
1202       */
1203      public static function get_user_mappings_parameters() {
1204          return new external_function_parameters(
1205              array(
1206                  'assignmentids' => new external_multiple_structure(
1207                      new external_value(PARAM_INT, 'assignment id'),
1208                      '1 or more assignment ids',
1209                      VALUE_REQUIRED)
1210              )
1211          );
1212      }
1213  
1214      /**
1215       * Returns user mapping information from assign_user_mapping for the requested assignment ids
1216       * @param int[] $assignmentids
1217       * @return array of user mapping records for each requested assignment
1218       * @since  Moodle 2.6
1219       */
1220      public static function get_user_mappings($assignmentids) {
1221          global $DB;
1222          $params = self::validate_parameters(self::get_user_mappings_parameters(),
1223                          array('assignmentids' => $assignmentids));
1224  
1225          $assignments = array();
1226          $warnings = array();
1227          $requestedassignmentids = $params['assignmentids'];
1228  
1229          // Check the user is allowed to get the mappings for the assignments requested.
1230          $placeholders = array();
1231          list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1232          $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1233                 "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1234          $placeholders['modname'] = 'assign';
1235          $cms = $DB->get_records_sql($sql, $placeholders);
1236          foreach ($cms as $cm) {
1237              try {
1238                  $context = context_module::instance($cm->id);
1239                  self::validate_context($context);
1240                  require_capability('mod/assign:revealidentities', $context);
1241              } catch (Exception $e) {
1242                  $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1243                  $warning = array();
1244                  $warning['item'] = 'assignment';
1245                  $warning['itemid'] = $cm->instance;
1246                  $warning['warningcode'] = '1';
1247                  $warning['message'] = 'No access rights in module context';
1248                  $warnings[] = $warning;
1249              }
1250          }
1251  
1252          // Create the query and populate an array of assign_user_mapping records from the recordset results.
1253          if (count ($requestedassignmentids) > 0) {
1254              $placeholders = array();
1255              list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1256  
1257              $sql = "SELECT aum.id,aum.assignment,aum.userid ".
1258                     "FROM {assign_user_mapping} aum ".
1259                     "WHERE aum.assignment ".$inorequalsql.
1260                     " ORDER BY aum.assignment, aum.id";
1261  
1262              $rs = $DB->get_recordset_sql($sql, $placeholders);
1263              $currentassignmentid = null;
1264              $assignment = null;
1265              foreach ($rs as $rd) {
1266                  $mapping = array();
1267                  $mapping['id'] = $rd->id;
1268                  $mapping['userid'] = $rd->userid;
1269  
1270                  if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1271                      if (!is_null($assignment)) {
1272                          $assignments[] = $assignment;
1273                      }
1274                      $assignment = array();
1275                      $assignment['assignmentid'] = $rd->assignment;
1276                      $assignment['mappings'] = array();
1277                      $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1278                  }
1279                  $assignment['mappings'][] = $mapping;
1280  
1281                  $currentassignmentid = $rd->assignment;
1282              }
1283              if (!is_null($assignment)) {
1284                  $assignments[] = $assignment;
1285              }
1286              $rs->close();
1287  
1288          }
1289  
1290          foreach ($requestedassignmentids as $assignmentid) {
1291              $warning = array();
1292              $warning['item'] = 'assignment';
1293              $warning['itemid'] = $assignmentid;
1294              $warning['warningcode'] = '3';
1295              $warning['message'] = 'No mappings found';
1296              $warnings[] = $warning;
1297          }
1298  
1299          $result = array();
1300          $result['assignments'] = $assignments;
1301          $result['warnings'] = $warnings;
1302          return $result;
1303      }
1304  
1305      /**
1306       * Creates an assign_user_mappings external_single_structure
1307       * @return external_single_structure
1308       * @since  Moodle 2.6
1309       */
1310      private static function assign_user_mappings() {
1311          return new external_single_structure(
1312              array (
1313                  'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1314                  'mappings'   => new external_multiple_structure(new external_single_structure(
1315                          array(
1316                              'id'     => new external_value(PARAM_INT, 'user mapping id'),
1317                              'userid' => new external_value(PARAM_INT, 'student id')
1318                          )
1319                      )
1320                  )
1321              )
1322          );
1323      }
1324  
1325      /**
1326       * Describes the get_user_mappings return value
1327       * @return external_single_structure
1328       * @since  Moodle 2.6
1329       */
1330      public static function get_user_mappings_returns() {
1331          return new external_single_structure(
1332              array(
1333                  'assignments' => new external_multiple_structure(self::assign_user_mappings(), 'list of assign user mapping data'),
1334                  'warnings'      => new external_warnings('item is always \'assignment\'',
1335                      'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1336                      'errorcode can be 3 (no user mappings found) or 1 (no permission to get user mappings)')
1337              )
1338          );
1339      }
1340  
1341      /**
1342       * Describes the parameters for lock_submissions
1343       * @return external_function_parameters
1344       * @since  Moodle 2.6
1345       */
1346      public static function lock_submissions_parameters() {
1347          return new external_function_parameters(
1348              array(
1349                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1350                  'userids' => new external_multiple_structure(
1351                      new external_value(PARAM_INT, 'user id'),
1352                      '1 or more user ids',
1353                      VALUE_REQUIRED),
1354              )
1355          );
1356      }
1357  
1358      /**
1359       * Locks (prevent updates to) submissions in this assignment.
1360       *
1361       * @param int $assignmentid The id of the assignment
1362       * @param array $userids Array of user ids to lock
1363       * @return array of warnings for each submission that could not be locked.
1364       * @since Moodle 2.6
1365       */
1366      public static function lock_submissions($assignmentid, $userids) {
1367          global $CFG;
1368  
1369          $params = self::validate_parameters(self::lock_submissions_parameters(),
1370                          array('assignmentid' => $assignmentid,
1371                                'userids' => $userids));
1372  
1373          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1374  
1375          $warnings = array();
1376          foreach ($params['userids'] as $userid) {
1377              if (!$assignment->lock_submission($userid)) {
1378                  $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1379                  $warnings[] = self::generate_warning($params['assignmentid'],
1380                                                       'couldnotlock',
1381                                                       $detail);
1382              }
1383          }
1384  
1385          return $warnings;
1386      }
1387  
1388      /**
1389       * Describes the return value for lock_submissions
1390       *
1391       * @return external_single_structure
1392       * @since Moodle 2.6
1393       */
1394      public static function lock_submissions_returns() {
1395          return new external_warnings();
1396      }
1397  
1398      /**
1399       * Describes the parameters for revert_submissions_to_draft
1400       * @return external_function_parameters
1401       * @since  Moodle 2.6
1402       */
1403      public static function revert_submissions_to_draft_parameters() {
1404          return new external_function_parameters(
1405              array(
1406                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1407                  'userids' => new external_multiple_structure(
1408                      new external_value(PARAM_INT, 'user id'),
1409                      '1 or more user ids',
1410                      VALUE_REQUIRED),
1411              )
1412          );
1413      }
1414  
1415      /**
1416       * Reverts a list of user submissions to draft for a single assignment.
1417       *
1418       * @param int $assignmentid The id of the assignment
1419       * @param array $userids Array of user ids to revert
1420       * @return array of warnings for each submission that could not be reverted.
1421       * @since Moodle 2.6
1422       */
1423      public static function revert_submissions_to_draft($assignmentid, $userids) {
1424          global $CFG;
1425  
1426          $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
1427                          array('assignmentid' => $assignmentid,
1428                                'userids' => $userids));
1429  
1430          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1431  
1432          $warnings = array();
1433          foreach ($params['userids'] as $userid) {
1434              if (!$assignment->revert_to_draft($userid)) {
1435                  $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1436                  $warnings[] = self::generate_warning($params['assignmentid'],
1437                                                       'couldnotrevert',
1438                                                       $detail);
1439              }
1440          }
1441  
1442          return $warnings;
1443      }
1444  
1445      /**
1446       * Describes the return value for revert_submissions_to_draft
1447       *
1448       * @return external_single_structure
1449       * @since Moodle 2.6
1450       */
1451      public static function revert_submissions_to_draft_returns() {
1452          return new external_warnings();
1453      }
1454  
1455      /**
1456       * Describes the parameters for unlock_submissions
1457       * @return external_function_parameters
1458       * @since  Moodle 2.6
1459       */
1460      public static function unlock_submissions_parameters() {
1461          return new external_function_parameters(
1462              array(
1463                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1464                  'userids' => new external_multiple_structure(
1465                      new external_value(PARAM_INT, 'user id'),
1466                      '1 or more user ids',
1467                      VALUE_REQUIRED),
1468              )
1469          );
1470      }
1471  
1472      /**
1473       * Locks (prevent updates to) submissions in this assignment.
1474       *
1475       * @param int $assignmentid The id of the assignment
1476       * @param array $userids Array of user ids to lock
1477       * @return array of warnings for each submission that could not be locked.
1478       * @since Moodle 2.6
1479       */
1480      public static function unlock_submissions($assignmentid, $userids) {
1481          global $CFG;
1482  
1483          $params = self::validate_parameters(self::unlock_submissions_parameters(),
1484                          array('assignmentid' => $assignmentid,
1485                                'userids' => $userids));
1486  
1487          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1488  
1489          $warnings = array();
1490          foreach ($params['userids'] as $userid) {
1491              if (!$assignment->unlock_submission($userid)) {
1492                  $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1493                  $warnings[] = self::generate_warning($params['assignmentid'],
1494                                                       'couldnotunlock',
1495                                                       $detail);
1496              }
1497          }
1498  
1499          return $warnings;
1500      }
1501  
1502      /**
1503       * Describes the return value for unlock_submissions
1504       *
1505       * @return external_single_structure
1506       * @since Moodle 2.6
1507       */
1508      public static function unlock_submissions_returns() {
1509          return new external_warnings();
1510      }
1511  
1512      /**
1513       * Describes the parameters for submit_grading_form webservice.
1514       * @return external_function_parameters
1515       * @since  Moodle 3.1
1516       */
1517      public static function submit_grading_form_parameters() {
1518          return new external_function_parameters(
1519              array(
1520                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1521                  'userid' => new external_value(PARAM_INT, 'The user id the submission belongs to'),
1522                  'jsonformdata' => new external_value(PARAM_RAW, 'The data from the grading form, encoded as a json array')
1523              )
1524          );
1525      }
1526  
1527      /**
1528       * Submit the logged in users assignment for grading.
1529       *
1530       * @param int $assignmentid The id of the assignment
1531       * @param int $userid The id of the user the submission belongs to.
1532       * @param string $jsonformdata The data from the form, encoded as a json array.
1533       * @return array of warnings to indicate any errors.
1534       * @since Moodle 3.1
1535       */
1536      public static function submit_grading_form($assignmentid, $userid, $jsonformdata) {
1537          global $CFG, $USER;
1538  
1539          require_once($CFG->dirroot . '/mod/assign/locallib.php');
1540          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
1541  
1542          $params = self::validate_parameters(self::submit_grading_form_parameters(),
1543                                              array(
1544                                                  'assignmentid' => $assignmentid,
1545                                                  'userid' => $userid,
1546                                                  'jsonformdata' => $jsonformdata
1547                                              ));
1548  
1549          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1550  
1551          $serialiseddata = json_decode($params['jsonformdata']);
1552  
1553          $data = array();
1554          parse_str($serialiseddata, $data);
1555  
1556          $warnings = array();
1557  
1558          $options = array(
1559              'userid' => $params['userid'],
1560              'attemptnumber' => $data['attemptnumber'],
1561              'rownum' => 0,
1562              'gradingpanel' => true
1563          );
1564  
1565          if (WS_SERVER) {
1566              // Assume form submission if coming from WS.
1567              $USER->ignoresesskey = true;
1568              $data['_qf__mod_assign_grade_form_'.$params['userid']] = 1;
1569          }
1570  
1571          $customdata = (object) $data;
1572          $formparams = array($assignment, $customdata, $options);
1573  
1574          // Data is injected into the form by the last param for the constructor.
1575          $mform = new mod_assign_grade_form(null, $formparams, 'post', '', null, true, $data);
1576          $validateddata = $mform->get_data();
1577  
1578          if ($validateddata) {
1579              $assignment->save_grade($params['userid'], $validateddata);
1580          } else {
1581              $warnings[] = self::generate_warning($params['assignmentid'],
1582                                                   'couldnotsavegrade',
1583                                                   'Form validation failed.');
1584          }
1585  
1586  
1587          return $warnings;
1588      }
1589  
1590      /**
1591       * Describes the return for submit_grading_form
1592       * @return external_function_parameters
1593       * @since  Moodle 3.1
1594       */
1595      public static function submit_grading_form_returns() {
1596          return new external_warnings();
1597      }
1598  
1599      /**
1600       * Describes the parameters for submit_for_grading
1601       * @return external_function_parameters
1602       * @since  Moodle 2.6
1603       */
1604      public static function submit_for_grading_parameters() {
1605          return new external_function_parameters(
1606              array(
1607                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1608                  'acceptsubmissionstatement' => new external_value(PARAM_BOOL, 'Accept the assignment submission statement')
1609              )
1610          );
1611      }
1612  
1613      /**
1614       * Submit the logged in users assignment for grading.
1615       *
1616       * @param int $assignmentid The id of the assignment
1617       * @return array of warnings to indicate any errors.
1618       * @since Moodle 2.6
1619       */
1620      public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) {
1621          global $CFG, $USER;
1622  
1623          $params = self::validate_parameters(self::submit_for_grading_parameters(),
1624                                              array('assignmentid' => $assignmentid,
1625                                                    'acceptsubmissionstatement' => $acceptsubmissionstatement));
1626  
1627          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1628  
1629          $warnings = array();
1630          $data = new stdClass();
1631          $data->submissionstatement = $params['acceptsubmissionstatement'];
1632          $notices = array();
1633  
1634          if (!$assignment->submit_for_grading($data, $notices)) {
1635              $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'] . ' Notices:' . implode(', ', $notices);
1636              $warnings[] = self::generate_warning($params['assignmentid'],
1637                                                   'couldnotsubmitforgrading',
1638                                                   $detail);
1639          }
1640  
1641          return $warnings;
1642      }
1643  
1644      /**
1645       * Describes the return value for submit_for_grading
1646       *
1647       * @return external_single_structure
1648       * @since Moodle 2.6
1649       */
1650      public static function submit_for_grading_returns() {
1651          return new external_warnings();
1652      }
1653  
1654      /**
1655       * Describes the parameters for save_user_extensions
1656       * @return external_function_parameters
1657       * @since  Moodle 2.6
1658       */
1659      public static function save_user_extensions_parameters() {
1660          return new external_function_parameters(
1661              array(
1662                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1663                  'userids' => new external_multiple_structure(
1664                      new external_value(PARAM_INT, 'user id'),
1665                      '1 or more user ids',
1666                      VALUE_REQUIRED),
1667                  'dates' => new external_multiple_structure(
1668                      new external_value(PARAM_INT, 'dates'),
1669                      '1 or more extension dates (timestamp)',
1670                      VALUE_REQUIRED),
1671              )
1672          );
1673      }
1674  
1675      /**
1676       * Grant extension dates to students for an assignment.
1677       *
1678       * @param int $assignmentid The id of the assignment
1679       * @param array $userids Array of user ids to grant extensions to
1680       * @param array $dates Array of extension dates
1681       * @return array of warnings for each extension date that could not be granted
1682       * @since Moodle 2.6
1683       */
1684      public static function save_user_extensions($assignmentid, $userids, $dates) {
1685          global $CFG;
1686  
1687          $params = self::validate_parameters(self::save_user_extensions_parameters(),
1688                          array('assignmentid' => $assignmentid,
1689                                'userids' => $userids,
1690                                'dates' => $dates));
1691  
1692          if (count($params['userids']) != count($params['dates'])) {
1693              $detail = 'Length of userids and dates parameters differ.';
1694              $warnings[] = self::generate_warning($params['assignmentid'],
1695                                                   'invalidparameters',
1696                                                   $detail);
1697  
1698              return $warnings;
1699          }
1700  
1701          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1702  
1703          $warnings = array();
1704          foreach ($params['userids'] as $idx => $userid) {
1705              $duedate = $params['dates'][$idx];
1706              if (!$assignment->save_user_extension($userid, $duedate)) {
1707                  $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'] . ', Extension date: ' . $duedate;
1708                  $warnings[] = self::generate_warning($params['assignmentid'],
1709                                                       'couldnotgrantextensions',
1710                                                       $detail);
1711              }
1712          }
1713  
1714          return $warnings;
1715      }
1716  
1717      /**
1718       * Describes the return value for save_user_extensions
1719       *
1720       * @return external_single_structure
1721       * @since Moodle 2.6
1722       */
1723      public static function save_user_extensions_returns() {
1724          return new external_warnings();
1725      }
1726  
1727      /**
1728       * Describes the parameters for reveal_identities
1729       * @return external_function_parameters
1730       * @since  Moodle 2.6
1731       */
1732      public static function reveal_identities_parameters() {
1733          return new external_function_parameters(
1734              array(
1735                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
1736              )
1737          );
1738      }
1739  
1740      /**
1741       * Reveal the identities of anonymous students to markers for a single assignment.
1742       *
1743       * @param int $assignmentid The id of the assignment
1744       * @return array of warnings to indicate any errors.
1745       * @since Moodle 2.6
1746       */
1747      public static function reveal_identities($assignmentid) {
1748          global $CFG, $USER;
1749  
1750          $params = self::validate_parameters(self::reveal_identities_parameters(),
1751                                              array('assignmentid' => $assignmentid));
1752  
1753          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1754  
1755          $warnings = array();
1756          if (!$assignment->reveal_identities()) {
1757              $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'];
1758              $warnings[] = self::generate_warning($params['assignmentid'],
1759                                                   'couldnotrevealidentities',
1760                                                   $detail);
1761          }
1762  
1763          return $warnings;
1764      }
1765  
1766      /**
1767       * Describes the return value for reveal_identities
1768       *
1769       * @return external_single_structure
1770       * @since Moodle 2.6
1771       */
1772      public static function reveal_identities_returns() {
1773          return new external_warnings();
1774      }
1775  
1776      /**
1777       * Describes the parameters for save_submission
1778       * @return external_function_parameters
1779       * @since  Moodle 2.6
1780       */
1781      public static function save_submission_parameters() {
1782          global $CFG;
1783          $instance = new assign(null, null, null);
1784          $pluginsubmissionparams = array();
1785  
1786          foreach ($instance->get_submission_plugins() as $plugin) {
1787              if ($plugin->is_visible()) {
1788                  $pluginparams = $plugin->get_external_parameters();
1789                  if (!empty($pluginparams)) {
1790                      $pluginsubmissionparams = array_merge($pluginsubmissionparams, $pluginparams);
1791                  }
1792              }
1793          }
1794  
1795          return new external_function_parameters(
1796              array(
1797                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1798                  'plugindata' => new external_single_structure(
1799                      $pluginsubmissionparams
1800                  )
1801              )
1802          );
1803      }
1804  
1805      /**
1806       * Save a student submission for a single assignment
1807       *
1808       * @param int $assignmentid The id of the assignment
1809       * @param array $plugindata - The submitted data for plugins
1810       * @return array of warnings to indicate any errors
1811       * @since Moodle 2.6
1812       */
1813      public static function save_submission($assignmentid, $plugindata) {
1814          global $CFG, $USER;
1815  
1816          $params = self::validate_parameters(self::save_submission_parameters(),
1817                                              array('assignmentid' => $assignmentid,
1818                                                    'plugindata' => $plugindata));
1819  
1820          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1821  
1822          $notices = array();
1823  
1824          $assignment->update_effective_access($USER->id);
1825          if (!$assignment->submissions_open($USER->id)) {
1826              $notices[] = get_string('duedatereached', 'assign');
1827          } else {
1828              $submissiondata = (object)$params['plugindata'];
1829              $assignment->save_submission($submissiondata, $notices);
1830          }
1831  
1832          $warnings = array();
1833          foreach ($notices as $notice) {
1834              $warnings[] = self::generate_warning($params['assignmentid'],
1835                                                   'couldnotsavesubmission',
1836                                                   $notice);
1837          }
1838  
1839          return $warnings;
1840      }
1841  
1842      /**
1843       * Describes the return value for save_submission
1844       *
1845       * @return external_single_structure
1846       * @since Moodle 2.6
1847       */
1848      public static function save_submission_returns() {
1849          return new external_warnings();
1850      }
1851  
1852      /**
1853       * Describes the parameters for save_grade
1854       * @return external_function_parameters
1855       * @since  Moodle 2.6
1856       */
1857      public static function save_grade_parameters() {
1858          global $CFG;
1859          require_once("$CFG->dirroot/grade/grading/lib.php");
1860          $instance = new assign(null, null, null);
1861          $pluginfeedbackparams = array();
1862  
1863          foreach ($instance->get_feedback_plugins() as $plugin) {
1864              if ($plugin->is_visible()) {
1865                  $pluginparams = $plugin->get_external_parameters();
1866                  if (!empty($pluginparams)) {
1867                      $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
1868                  }
1869              }
1870          }
1871  
1872          $advancedgradingdata = array();
1873          $methods = array_keys(grading_manager::available_methods(false));
1874          foreach ($methods as $method) {
1875              require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
1876              $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
1877              if (!empty($details)) {
1878                  $items = array();
1879                  foreach ($details as $key => $value) {
1880                      $value->required = VALUE_OPTIONAL;
1881                      unset($value->content->keys['id']);
1882                      $items[$key] = new external_multiple_structure (new external_single_structure(
1883                          array(
1884                              'criterionid' => new external_value(PARAM_INT, 'criterion id'),
1885                              'fillings' => $value
1886                          )
1887                      ));
1888                  }
1889                  $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
1890              }
1891          }
1892  
1893          return new external_function_parameters(
1894              array(
1895                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1896                  'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
1897                  'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. Ignored if advanced grading used'),
1898                  'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
1899                  'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
1900                  'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
1901                  'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
1902                                                                 'to all members ' .
1903                                                                 'of the group (for group assignments).'),
1904                  'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data', VALUE_DEFAULT, array()),
1905                  'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
1906                                                                         VALUE_DEFAULT, array())
1907              )
1908          );
1909      }
1910  
1911      /**
1912       * Save a student grade for a single assignment.
1913       *
1914       * @param int $assignmentid The id of the assignment
1915       * @param int $userid The id of the user
1916       * @param float $grade The grade (ignored if the assignment uses advanced grading)
1917       * @param int $attemptnumber The attempt number
1918       * @param bool $addattempt Allow another attempt
1919       * @param string $workflowstate New workflow state
1920       * @param bool $applytoall Apply the grade to all members of the group
1921       * @param array $plugindata Custom data used by plugins
1922       * @param array $advancedgradingdata Advanced grading data
1923       * @return null
1924       * @since Moodle 2.6
1925       */
1926      public static function save_grade($assignmentid,
1927                                        $userid,
1928                                        $grade,
1929                                        $attemptnumber,
1930                                        $addattempt,
1931                                        $workflowstate,
1932                                        $applytoall,
1933                                        $plugindata = array(),
1934                                        $advancedgradingdata = array()) {
1935          global $CFG, $USER;
1936  
1937          $params = self::validate_parameters(self::save_grade_parameters(),
1938                                              array('assignmentid' => $assignmentid,
1939                                                    'userid' => $userid,
1940                                                    'grade' => $grade,
1941                                                    'attemptnumber' => $attemptnumber,
1942                                                    'workflowstate' => $workflowstate,
1943                                                    'addattempt' => $addattempt,
1944                                                    'applytoall' => $applytoall,
1945                                                    'plugindata' => $plugindata,
1946                                                    'advancedgradingdata' => $advancedgradingdata));
1947  
1948          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1949  
1950          $gradedata = (object)$params['plugindata'];
1951  
1952          $gradedata->addattempt = $params['addattempt'];
1953          $gradedata->attemptnumber = $params['attemptnumber'];
1954          $gradedata->workflowstate = $params['workflowstate'];
1955          $gradedata->applytoall = $params['applytoall'];
1956          $gradedata->grade = $params['grade'];
1957  
1958          if (!empty($params['advancedgradingdata'])) {
1959              $advancedgrading = array();
1960              $criteria = reset($params['advancedgradingdata']);
1961              foreach ($criteria as $key => $criterion) {
1962                  $details = array();
1963                  foreach ($criterion as $value) {
1964                      foreach ($value['fillings'] as $filling) {
1965                          $details[$value['criterionid']] = $filling;
1966                      }
1967                  }
1968                  $advancedgrading[$key] = $details;
1969              }
1970              $gradedata->advancedgrading = $advancedgrading;
1971          }
1972  
1973          $assignment->save_grade($params['userid'], $gradedata);
1974  
1975          return null;
1976      }
1977  
1978      /**
1979       * Describes the return value for save_grade
1980       *
1981       * @return external_single_structure
1982       * @since Moodle 2.6
1983       */
1984      public static function save_grade_returns() {
1985          return null;
1986      }
1987  
1988      /**
1989       * Describes the parameters for save_grades
1990       * @return external_function_parameters
1991       * @since  Moodle 2.7
1992       */
1993      public static function save_grades_parameters() {
1994          global $CFG;
1995          require_once("$CFG->dirroot/grade/grading/lib.php");
1996          $instance = new assign(null, null, null);
1997          $pluginfeedbackparams = array();
1998  
1999          foreach ($instance->get_feedback_plugins() as $plugin) {
2000              if ($plugin->is_visible()) {
2001                  $pluginparams = $plugin->get_external_parameters();
2002                  if (!empty($pluginparams)) {
2003                      $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
2004                  }
2005              }
2006          }
2007  
2008          $advancedgradingdata = array();
2009          $methods = array_keys(grading_manager::available_methods(false));
2010          foreach ($methods as $method) {
2011              require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
2012              $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
2013              if (!empty($details)) {
2014                  $items = array();
2015                  foreach ($details as $key => $value) {
2016                      $value->required = VALUE_OPTIONAL;
2017                      unset($value->content->keys['id']);
2018                      $items[$key] = new external_multiple_structure (new external_single_structure(
2019                          array(
2020                              'criterionid' => new external_value(PARAM_INT, 'criterion id'),
2021                              'fillings' => $value
2022                          )
2023                      ));
2024                  }
2025                  $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
2026              }
2027          }
2028  
2029          return new external_function_parameters(
2030              array(
2031                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2032                  'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
2033                                                                 'to all members ' .
2034                                                                 'of the group (for group assignments).'),
2035                  'grades' => new external_multiple_structure(
2036                      new external_single_structure(
2037                          array (
2038                              'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
2039                              'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. '.
2040                                                                         'Ignored if advanced grading used'),
2041                              'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
2042                              'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if manual attempt reopen method'),
2043                              'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
2044                              'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data',
2045                                                                            VALUE_DEFAULT, array()),
2046                              'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
2047                                                                                     VALUE_DEFAULT, array())
2048                          )
2049                      )
2050                  )
2051              )
2052          );
2053      }
2054  
2055      /**
2056       * Save multiple student grades for a single assignment.
2057       *
2058       * @param int $assignmentid The id of the assignment
2059       * @param boolean $applytoall If set to true and this is a team assignment,
2060       * apply the grade to all members of the group
2061       * @param array $grades grade data for one or more students that includes
2062       *                  userid - The id of the student being graded
2063       *                  grade - The grade (ignored if the assignment uses advanced grading)
2064       *                  attemptnumber - The attempt number
2065       *                  addattempt - Allow another attempt
2066       *                  workflowstate - New workflow state
2067       *                  plugindata - Custom data used by plugins
2068       *                  advancedgradingdata - Optional Advanced grading data
2069       * @throws invalid_parameter_exception if multiple grades are supplied for
2070       * a team assignment that has $applytoall set to true
2071       * @return null
2072       * @since Moodle 2.7
2073       */
2074      public static function save_grades($assignmentid, $applytoall, $grades) {
2075          global $CFG, $USER;
2076  
2077          $params = self::validate_parameters(self::save_grades_parameters(),
2078                                              array('assignmentid' => $assignmentid,
2079                                                    'applytoall' => $applytoall,
2080                                                    'grades' => $grades));
2081  
2082          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
2083  
2084          if ($assignment->get_instance()->teamsubmission && $params['applytoall']) {
2085              // Check that only 1 user per submission group is provided.
2086              $groupids = array();
2087              foreach ($params['grades'] as $gradeinfo) {
2088                  $group = $assignment->get_submission_group($gradeinfo['userid']);
2089                  if (in_array($group->id, $groupids)) {
2090                      throw new invalid_parameter_exception('Multiple grades for the same team have been supplied '
2091                                                            .' this is not permitted when the applytoall flag is set');
2092                  } else {
2093                      $groupids[] = $group->id;
2094                  }
2095              }
2096          }
2097  
2098          foreach ($params['grades'] as $gradeinfo) {
2099              $gradedata = (object)$gradeinfo['plugindata'];
2100              $gradedata->addattempt = $gradeinfo['addattempt'];
2101              $gradedata->attemptnumber = $gradeinfo['attemptnumber'];
2102              $gradedata->workflowstate = $gradeinfo['workflowstate'];
2103              $gradedata->applytoall = $params['applytoall'];
2104              $gradedata->grade = $gradeinfo['grade'];
2105  
2106              if (!empty($gradeinfo['advancedgradingdata'])) {
2107                  $advancedgrading = array();
2108                  $criteria = reset($gradeinfo['advancedgradingdata']);
2109                  foreach ($criteria as $key => $criterion) {
2110                      $details = array();
2111                      foreach ($criterion as $value) {
2112                          foreach ($value['fillings'] as $filling) {
2113                              $details[$value['criterionid']] = $filling;
2114                          }
2115                      }
2116                      $advancedgrading[$key] = $details;
2117                  }
2118                  $gradedata->advancedgrading = $advancedgrading;
2119              }
2120              $assignment->save_grade($gradeinfo['userid'], $gradedata);
2121          }
2122  
2123          return null;
2124      }
2125  
2126      /**
2127       * Describes the return value for save_grades
2128       *
2129       * @return external_single_structure
2130       * @since Moodle 2.7
2131       */
2132      public static function save_grades_returns() {
2133          return null;
2134      }
2135  
2136      /**
2137       * Describes the parameters for copy_previous_attempt
2138       * @return external_function_parameters
2139       * @since  Moodle 2.6
2140       */
2141      public static function copy_previous_attempt_parameters() {
2142          return new external_function_parameters(
2143              array(
2144                  'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2145              )
2146          );
2147      }
2148  
2149      /**
2150       * Copy a students previous attempt to a new attempt.
2151       *
2152       * @param int $assignmentid
2153       * @return array of warnings to indicate any errors.
2154       * @since Moodle 2.6
2155       */
2156      public static function copy_previous_attempt($assignmentid) {
2157  
2158          $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
2159                                              array('assignmentid' => $assignmentid));
2160  
2161          list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
2162  
2163          $notices = array();
2164  
2165          $assignment->copy_previous_attempt($notices);
2166  
2167          $warnings = array();
2168          foreach ($notices as $notice) {
2169              $warnings[] = self::generate_warning($assignmentid,
2170                                                   'couldnotcopyprevioussubmission',
2171                                                   $notice);
2172          }
2173  
2174          return $warnings;
2175      }
2176  
2177      /**
2178       * Describes the return value for save_submission
2179       *
2180       * @return external_single_structure
2181       * @since Moodle 2.6
2182       */
2183      public static function copy_previous_attempt_returns() {
2184          return new external_warnings();
2185      }
2186  
2187      /**
2188       * Returns description of method parameters
2189       *
2190       * @return external_function_parameters
2191       * @since Moodle 3.0
2192       */
2193      public static function view_grading_table_parameters() {
2194          return new external_function_parameters(
2195              array(
2196                  'assignid' => new external_value(PARAM_INT, 'assign instance id')
2197              )
2198          );
2199      }
2200  
2201      /**
2202       * Trigger the grading_table_viewed event.
2203       *
2204       * @param int $assignid the assign instance id
2205       * @return array of warnings and status result
2206       * @since Moodle 3.0
2207       * @throws moodle_exception
2208       */
2209      public static function view_grading_table($assignid) {
2210  
2211          $params = self::validate_parameters(self::view_grading_table_parameters(),
2212                                              array(
2213                                                  'assignid' => $assignid
2214                                              ));
2215          $warnings = array();
2216  
2217          list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2218  
2219          $assign->require_view_grades();
2220          \mod_assign\event\grading_table_viewed::create_from_assign($assign)->trigger();
2221  
2222          $result = array();
2223          $result['status'] = true;
2224          $result['warnings'] = $warnings;
2225          return $result;
2226      }
2227  
2228      /**
2229       * Returns description of method result value
2230       *
2231       * @return external_description
2232       * @since Moodle 3.0
2233       */
2234      public static function view_grading_table_returns() {
2235          return new external_single_structure(
2236              array(
2237                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2238                  'warnings' => new external_warnings()
2239              )
2240          );
2241      }
2242  
2243      /**
2244       * Describes the parameters for view_submission_status.
2245       *
2246       * @return external_function_parameters
2247       * @since Moodle 3.1
2248       */
2249      public static function view_submission_status_parameters() {
2250          return new external_function_parameters (
2251              array(
2252                  'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2253              )
2254          );
2255      }
2256  
2257      /**
2258       * Trigger the submission status viewed event.
2259       *
2260       * @param int $assignid assign instance id
2261       * @return array of warnings and status result
2262       * @since Moodle 3.1
2263       */
2264      public static function view_submission_status($assignid) {
2265  
2266          $warnings = array();
2267          $params = array(
2268              'assignid' => $assignid,
2269          );
2270          $params = self::validate_parameters(self::view_submission_status_parameters(), $params);
2271  
2272          list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2273  
2274          \mod_assign\event\submission_status_viewed::create_from_assign($assign)->trigger();
2275  
2276          $result = array();
2277          $result['status'] = true;
2278          $result['warnings'] = $warnings;
2279          return $result;
2280      }
2281  
2282      /**
2283       * Describes the view_submission_status return value.
2284       *
2285       * @return external_single_structure
2286       * @since Moodle 3.1
2287       */
2288      public static function view_submission_status_returns() {
2289          return new external_single_structure(
2290              array(
2291                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2292                  'warnings' => new external_warnings(),
2293              )
2294          );
2295      }
2296  
2297      /**
2298       * Describes the parameters for get_submission_status.
2299       *
2300       * @return external_function_parameters
2301       * @since Moodle 3.1
2302       */
2303      public static function get_submission_status_parameters() {
2304          return new external_function_parameters (
2305              array(
2306                  'assignid' => new external_value(PARAM_INT, 'assignment instance id'),
2307                  'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0),
2308                  'groupid' => new external_value(PARAM_INT, 'filter by users in group (used for generating the grading summary).
2309                      0 for all groups information, any other empty value will calculate currrent group.', VALUE_DEFAULT, 0),
2310              )
2311          );
2312      }
2313  
2314      /**
2315       * Returns information about an assignment submission status for a given user.
2316       *
2317       * @param int $assignid assignment instance id
2318       * @param int $userid user id (empty for current user)
2319       * @param int $groupid filter by users in group id (used for generating the grading summary). Use 0 for all groups information.
2320       * @return array of warnings and grading, status, feedback and previous attempts information
2321       * @since Moodle 3.1
2322       * @throws required_capability_exception
2323       */
2324      public static function get_submission_status($assignid, $userid = 0, $groupid = 0) {
2325          global $USER;
2326  
2327          $warnings = array();
2328  
2329          $params = array(
2330              'assignid' => $assignid,
2331              'userid' => $userid,
2332              'groupid' => $groupid,
2333          );
2334          $params = self::validate_parameters(self::get_submission_status_parameters(), $params);
2335  
2336          list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2337  
2338          // Default value for userid.
2339          if (empty($params['userid'])) {
2340              $params['userid'] = $USER->id;
2341          }
2342          $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
2343          core_user::require_active_user($user);
2344  
2345          if (!$assign->can_view_submission($user->id)) {
2346              throw new required_capability_exception($context, 'mod/assign:viewgrades', 'nopermission', '');
2347          }
2348  
2349          $assign->update_effective_access($user->id);
2350  
2351          $gradingsummary = $lastattempt = $feedback = $previousattempts = null;
2352  
2353          // Get the renderable since it contais all the info we need.
2354          if (!empty($params['groupid'])) {
2355              $groupid = $params['groupid'];
2356              // Determine is the group is visible to user.
2357              if (!groups_group_visible($groupid, $course, $cm)) {
2358                  throw new moodle_exception('notingroup');
2359              }
2360          } else {
2361              // A null group means that following functions will calculate the current group.
2362              // A groupid set to 0 means all groups.
2363              $groupid = ($params['groupid'] == 0) ? 0 : null;
2364          }
2365          if ($assign->can_view_grades($groupid)) {
2366              $gradingsummary = $assign->get_assign_grading_summary_renderable($groupid);
2367          }
2368  
2369          // Retrieve the rest of the renderable objects.
2370          if (has_capability('mod/assign:viewownsubmissionsummary', $context, $user, false)) {
2371              // The user can view the submission summary.
2372              $lastattempt = $assign->get_assign_submission_status_renderable($user, true);
2373          }
2374  
2375          $feedback = $assign->get_assign_feedback_status_renderable($user);
2376  
2377          $previousattempts = $assign->get_assign_attempt_history_renderable($user);
2378  
2379          // Now, build the result.
2380          $result = array();
2381  
2382          // First of all, grading summary, this is suitable for teachers/managers.
2383          if ($gradingsummary) {
2384              $result['gradingsummary'] = $gradingsummary;
2385          }
2386          // Show the grader's identity if 'Hide Grader' is disabled or has the 'Show Hidden Grader' capability.
2387          $showgradername = (has_capability('mod/assign:showhiddengrader', $context) or
2388              !$assign->is_hidden_grader());
2389  
2390          // Did we submit anything?
2391          if ($lastattempt) {
2392              $submissionplugins = $assign->get_submission_plugins();
2393  
2394              if (empty($lastattempt->submission)) {
2395                  unset($lastattempt->submission);
2396              } else {
2397                  $lastattempt->submission->plugins = self::get_plugins_data($assign, $submissionplugins, $lastattempt->submission);
2398              }
2399  
2400              if (empty($lastattempt->teamsubmission)) {
2401                  unset($lastattempt->teamsubmission);
2402              } else {
2403                  $lastattempt->teamsubmission->plugins = self::get_plugins_data($assign, $submissionplugins,
2404                                                                                  $lastattempt->teamsubmission);
2405              }
2406  
2407              // We need to change the type of some of the structures retrieved from the renderable.
2408              if (!empty($lastattempt->submissiongroup)) {
2409                  $lastattempt->submissiongroup = $lastattempt->submissiongroup->id;
2410              } else {
2411                  unset($lastattempt->submissiongroup);
2412              }
2413  
2414              if (!empty($lastattempt->usergroups)) {
2415                  $lastattempt->usergroups = array_keys($lastattempt->usergroups);
2416              }
2417              // We cannot use array_keys here.
2418              if (!empty($lastattempt->submissiongroupmemberswhoneedtosubmit)) {
2419                  $lastattempt->submissiongroupmemberswhoneedtosubmit = array_map(
2420                                                                              function($e){
2421                                                                                  return $e->id;
2422                                                                              },
2423                                                                              $lastattempt->submissiongroupmemberswhoneedtosubmit);
2424              }
2425  
2426              // Can edit its own submission?
2427              $lastattempt->caneditowner = has_capability('mod/assign:submit', $context, $user, false)
2428                  && $assign->submissions_open($user->id) && $assign->is_any_submission_plugin_enabled();
2429  
2430              $result['lastattempt'] = $lastattempt;
2431          }
2432  
2433          // The feedback for our latest submission.
2434          if ($feedback) {
2435              if ($feedback->grade) {
2436                  if (!$showgradername) {
2437                      $feedback->grade->grader = -1;
2438                  }
2439                  $feedbackplugins = $assign->get_feedback_plugins();
2440                  $feedback->plugins = self::get_plugins_data($assign, $feedbackplugins, $feedback->grade);
2441              } else {
2442                  unset($feedback->plugins);
2443                  unset($feedback->grade);
2444              }
2445  
2446              $result['feedback'] = $feedback;
2447          }
2448  
2449          // Retrieve only previous attempts.
2450          if ($previousattempts and count($previousattempts->submissions) > 1) {
2451              // Don't show the last one because it is the current submission.
2452              array_pop($previousattempts->submissions);
2453  
2454              // Show newest to oldest.
2455              $previousattempts->submissions = array_reverse($previousattempts->submissions);
2456  
2457              foreach ($previousattempts->submissions as $i => $submission) {
2458                  $attempt = array();
2459  
2460                  $grade = null;
2461                  foreach ($previousattempts->grades as $onegrade) {
2462                      if ($onegrade->attemptnumber == $submission->attemptnumber) {
2463                          $grade = $onegrade;
2464                          break;
2465                      }
2466                  }
2467  
2468                  $attempt['attemptnumber'] = $submission->attemptnumber;
2469  
2470                  if ($submission) {
2471                      $submission->plugins = self::get_plugins_data($assign, $previousattempts->submissionplugins, $submission);
2472                      $attempt['submission'] = $submission;
2473                  }
2474  
2475                  if ($grade) {
2476                      // From object to id.
2477                      if (!$showgradername) {
2478                          $grade->grader = -1;
2479                      } else {
2480                          $grade->grader = $grade->grader->id;
2481                      }
2482  
2483                      $feedbackplugins = self::get_plugins_data($assign, $previousattempts->feedbackplugins, $grade);
2484  
2485                      $attempt['grade'] = $grade;
2486                      $attempt['feedbackplugins'] = $feedbackplugins;
2487                  }
2488                  $result['previousattempts'][] = $attempt;
2489              }
2490          }
2491  
2492          // Send back some assignment data as well.
2493          $instance = $assign->get_instance();
2494          $assignmentdata = [];
2495          $attachments = [];
2496          if ($assign->should_provide_intro_attachments($user->id)) {
2497              $attachments['intro'] = external_util::get_area_files($context->id, 'mod_assign',
2498                      ASSIGN_INTROATTACHMENT_FILEAREA, 0);
2499          }
2500          if ($instance->activity && ($lastattempt || $assign->submissions_open($user->id, true))) {
2501              list($assignmentdata['activity'], $assignmentdata['activityformat']) = external_format_text($instance->activity,
2502                  $instance->activityformat, $context->id, 'mod_assign', ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
2503              $attachments['activity'] = external_util::get_area_files($context->id, 'mod_assign',
2504                  ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
2505          }
2506          if (!empty($attachments)) {
2507              $assignmentdata['attachments'] = $attachments;
2508          }
2509          $result['assignmentdata'] = $assignmentdata;
2510  
2511          $result['warnings'] = $warnings;
2512          return $result;
2513      }
2514  
2515      /**
2516       * Describes the get_submission_status return value.
2517       *
2518       * @return external_single_structure
2519       * @since Moodle 3.1
2520       */
2521      public static function get_submission_status_returns() {
2522          return new external_single_structure(
2523              array(
2524                  'gradingsummary' => new external_single_structure(
2525                      array(
2526                          'participantcount' => new external_value(PARAM_INT, 'Number of users who can submit.'),
2527                          'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2528                          'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2529                          'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2530                          'submissionssubmittedcount' => new external_value(PARAM_INT, 'Number of submissions in submitted status.'),
2531                          'submissionsneedgradingcount' => new external_value(PARAM_INT, 'Number of submissions that need grading.'),
2532                          'warnofungroupedusers' => new external_value(PARAM_ALPHA, 'Whether we need to warn people that there
2533                                                                          are users without groups (\'warningrequired\'), warn
2534                                                                          people there are users who will submit in the default
2535                                                                          group (\'warningoptional\') or no warning (\'\').'),
2536                      ), 'Grading information.', VALUE_OPTIONAL
2537                  ),
2538                  'lastattempt' => new external_single_structure(
2539                      array(
2540                          'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2541                          'teamsubmission' => self::get_submission_structure(VALUE_OPTIONAL),
2542                          'submissiongroup' => new external_value(PARAM_INT, 'The submission group id (for group submissions only).',
2543                                                                  VALUE_OPTIONAL),
2544                          'submissiongroupmemberswhoneedtosubmit' => new external_multiple_structure(
2545                              new external_value(PARAM_INT, 'USER id.'),
2546                              'List of users who still need to submit (for group submissions only).',
2547                              VALUE_OPTIONAL
2548                          ),
2549                          'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2550                          'locked' => new external_value(PARAM_BOOL, 'Whether new submissions are locked.'),
2551                          'graded' => new external_value(PARAM_BOOL, 'Whether the submission is graded.'),
2552                          'canedit' => new external_value(PARAM_BOOL, 'Whether the user can edit the current submission.'),
2553                          'caneditowner' => new external_value(PARAM_BOOL, 'Whether the owner of the submission can edit it.'),
2554                          'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
2555                          'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
2556                          'timelimit' => new external_value(PARAM_INT, 'Time limit for submission.', VALUE_OPTIONAL),
2557                          'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
2558                          'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.'),
2559                          'usergroups' => new external_multiple_structure(
2560                              new external_value(PARAM_INT, 'Group id.'), 'User groups in the course.'
2561                          ),
2562                      ), 'Last attempt information.', VALUE_OPTIONAL
2563                  ),
2564                  'feedback' => new external_single_structure(
2565                      array(
2566                          'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2567                          'gradefordisplay' => new external_value(PARAM_RAW, 'Grade rendered into a format suitable for display.'),
2568                          'gradeddate' => new external_value(PARAM_INT, 'The date the user was graded.'),
2569                          'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'Plugins info.', VALUE_OPTIONAL),
2570                      ), 'Feedback for the last attempt.', VALUE_OPTIONAL
2571                  ),
2572                  'previousattempts' => new external_multiple_structure(
2573                      new external_single_structure(
2574                          array(
2575                              'attemptnumber' => new external_value(PARAM_INT, 'Attempt number.'),
2576                              'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2577                              'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2578                              'feedbackplugins' => new external_multiple_structure(self::get_plugin_structure(), 'Feedback info.',
2579                                                                                      VALUE_OPTIONAL),
2580                          )
2581                      ), 'List all the previous attempts did by the user.', VALUE_OPTIONAL
2582                  ),
2583                  'assignmentdata' => new external_single_structure([
2584                      'attachments' => new external_single_structure([
2585                          'intro' => new external_files('Intro attachments files', VALUE_OPTIONAL),
2586                          'activity' => new external_files('Activity attachments files', VALUE_OPTIONAL),
2587                      ], 'Intro and activity attachments', VALUE_OPTIONAL),
2588                      'activity' => new external_value(PARAM_RAW, 'Text of activity', VALUE_OPTIONAL),
2589                      'activityformat' => new external_format_value('activity', VALUE_OPTIONAL),
2590                  ], 'Extra information about assignment', VALUE_OPTIONAL),
2591                  'warnings' => new external_warnings(),
2592              )
2593          );
2594      }
2595  
2596      /**
2597       * Returns description of method parameters
2598       *
2599       * @return external_function_parameters
2600       * @since Moodle 3.1
2601       */
2602      public static function list_participants_parameters() {
2603          return new external_function_parameters(
2604              array(
2605                  'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2606                  'groupid' => new external_value(PARAM_INT, 'group id'),
2607                  'filter' => new external_value(PARAM_RAW, 'search string to filter the results'),
2608                  'skip' => new external_value(PARAM_INT, 'number of records to skip', VALUE_DEFAULT, 0),
2609                  'limit' => new external_value(PARAM_INT, 'maximum number of records to return', VALUE_DEFAULT, 0),
2610                  'onlyids' => new external_value(PARAM_BOOL, 'Do not return all user fields', VALUE_DEFAULT, false),
2611                  'includeenrolments' => new external_value(PARAM_BOOL, 'Do return courses where the user is enrolled',
2612                                                            VALUE_DEFAULT, true),
2613                  'tablesort' => new external_value(PARAM_BOOL, 'Apply current user table sorting preferences.',
2614                                                            VALUE_DEFAULT, false)
2615              )
2616          );
2617      }
2618  
2619      /**
2620       * Retrieves the list of students to be graded for the assignment.
2621       *
2622       * @param int $assignid the assign instance id
2623       * @param int $groupid the current group id
2624       * @param string $filter search string to filter the results.
2625       * @param int $skip Number of records to skip
2626       * @param int $limit Maximum number of records to return
2627       * @param bool $onlyids Only return user ids.
2628       * @param bool $includeenrolments Return courses where the user is enrolled.
2629       * @param bool $tablesort Apply current user table sorting params from the grading table.
2630       * @return array of warnings and status result
2631       * @since Moodle 3.1
2632       * @throws moodle_exception
2633       */
2634      public static function list_participants($assignid, $groupid, $filter, $skip,
2635              $limit, $onlyids, $includeenrolments, $tablesort) {
2636          global $DB, $CFG;
2637          require_once($CFG->dirroot . "/mod/assign/locallib.php");
2638          require_once($CFG->dirroot . "/user/lib.php");
2639          require_once($CFG->libdir . '/grouplib.php');
2640  
2641          $params = self::validate_parameters(self::list_participants_parameters(),
2642                                              array(
2643                                                  'assignid' => $assignid,
2644                                                  'groupid' => $groupid,
2645                                                  'filter' => $filter,
2646                                                  'skip' => $skip,
2647                                                  'limit' => $limit,
2648                                                  'onlyids' => $onlyids,
2649                                                  'includeenrolments' => $includeenrolments,
2650                                                  'tablesort' => $tablesort
2651                                              ));
2652          $warnings = array();
2653  
2654          list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2655  
2656          require_capability('mod/assign:view', $context);
2657  
2658          $assign->require_view_grades();
2659  
2660          $participants = array();
2661          $coursegroups = [];
2662          if (groups_group_visible($params['groupid'], $course, $cm)) {
2663              $participants = $assign->list_participants_with_filter_status_and_group($params['groupid'], $params['tablesort']);
2664              $coursegroups = groups_get_all_groups($course->id);
2665          }
2666  
2667          $userfields = user_get_default_fields();
2668          if (!$params['includeenrolments']) {
2669              // Remove enrolled courses from users fields to be returned.
2670              $key = array_search('enrolledcourses', $userfields);
2671              if ($key !== false) {
2672                  unset($userfields[$key]);
2673              } else {
2674                  throw new moodle_exception('invaliduserfield', 'error', '', 'enrolledcourses');
2675              }
2676          }
2677  
2678          $result = array();
2679          $index = 0;
2680          foreach ($participants as $record) {
2681              // Preserve the fullname set by the assignment.
2682              $fullname = $record->fullname;
2683              $searchable = $fullname;
2684              $match = false;
2685              if (empty($filter)) {
2686                  $match = true;
2687              } else {
2688                  $filter = core_text::strtolower($filter);
2689                  $value = core_text::strtolower($searchable);
2690                  if (is_string($value) && (core_text::strpos($value, $filter) !== false)) {
2691                      $match = true;
2692                  }
2693              }
2694              if ($match) {
2695                  $index++;
2696                  if ($index <= $params['skip']) {
2697                      continue;
2698                  }
2699                  if (($params['limit'] > 0) && (($index - $params['skip']) > $params['limit'])) {
2700                      break;
2701                  }
2702                  // Now we do the expensive lookup of user details because we completed the filtering.
2703                  if (!$assign->is_blind_marking() && !$params['onlyids']) {
2704                      $userdetails = user_get_user_details($record, $course, $userfields);
2705                  } else {
2706                      $userdetails = array('id' => $record->id);
2707                  }
2708                  $userdetails['fullname'] = $fullname;
2709                  $userdetails['submitted'] = $record->submitted;
2710                  $userdetails['requiregrading'] = $record->requiregrading;
2711                  $userdetails['grantedextension'] = $record->grantedextension;
2712                  $userdetails['submissionstatus'] = $record->submissionstatus;
2713                  if (!empty($record->groupid)) {
2714                      $userdetails['groupid'] = $record->groupid;
2715  
2716                      if (!empty($coursegroups[$record->groupid])) {
2717                          // Format properly the group name.
2718                          $group = $coursegroups[$record->groupid];
2719                          $userdetails['groupname'] = format_string($group->name);
2720                      }
2721                  }
2722                  // Unique id is required for blind marking.
2723                  $userdetails['recordid'] = -1;
2724                  if (!empty($record->recordid)) {
2725                      $userdetails['recordid'] = $record->recordid;
2726                  }
2727  
2728                  $result[] = $userdetails;
2729              }
2730          }
2731          return $result;
2732      }
2733  
2734      /**
2735       * Returns the description of the results of the mod_assign_external::list_participants() method.
2736       *
2737       * @return external_description
2738       * @since Moodle 3.1
2739       */
2740      public static function list_participants_returns() {
2741          // Get user description.
2742          $userdesc = core_user_external::user_description();
2743          // List unneeded properties.
2744          $unneededproperties = [
2745              'auth', 'confirmed', 'lang', 'calendartype', 'theme', 'timezone', 'mailformat'
2746          ];
2747          // Remove unneeded properties for consistency with the previous version.
2748          foreach ($unneededproperties as $prop) {
2749              unset($userdesc->keys[$prop]);
2750          }
2751  
2752          // Override property attributes for consistency with the previous version.
2753          $userdesc->keys['fullname']->type = PARAM_NOTAGS;
2754          $userdesc->keys['profileimageurlsmall']->required = VALUE_OPTIONAL;
2755          $userdesc->keys['profileimageurl']->required = VALUE_OPTIONAL;
2756          $userdesc->keys['email']->desc = 'Email address';
2757          $userdesc->keys['idnumber']->desc = 'The idnumber of the user';
2758          $userdesc->keys['recordid'] = new external_value(PARAM_INT, 'record id');
2759  
2760          // Define other keys.
2761          $otherkeys = [
2762              'groups' => new external_multiple_structure(
2763                  new external_single_structure(
2764                      [
2765                          'id' => new external_value(PARAM_INT, 'group id'),
2766                          'name' => new external_value(PARAM_RAW, 'group name'),
2767                          'description' => new external_value(PARAM_RAW, 'group description'),
2768                      ]
2769                  ), 'user groups', VALUE_OPTIONAL
2770              ),
2771              'roles' => new external_multiple_structure(
2772                  new external_single_structure(
2773                      [
2774                          'roleid' => new external_value(PARAM_INT, 'role id'),
2775                          'name' => new external_value(PARAM_RAW, 'role name'),
2776                          'shortname' => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
2777                          'sortorder' => new external_value(PARAM_INT, 'role sortorder')
2778                      ]
2779                  ), 'user roles', VALUE_OPTIONAL
2780              ),
2781              'enrolledcourses' => new external_multiple_structure(
2782                  new external_single_structure(
2783                      [
2784                          'id' => new external_value(PARAM_INT, 'Id of the course'),
2785                          'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
2786                          'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
2787                      ]
2788                  ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL
2789              ),
2790              'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
2791              'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
2792              'grantedextension' => new external_value(PARAM_BOOL, 'have they been granted an extension'),
2793              'submissionstatus' => new external_value(PARAM_ALPHA, 'The submission status (new, draft, reopened or submitted).
2794                  Empty when not submitted.', VALUE_OPTIONAL),
2795              'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
2796              'groupname' => new external_value(PARAM_TEXT, 'for group assignments this is the group name', VALUE_OPTIONAL),
2797          ];
2798  
2799          // Merge keys.
2800          $userdesc->keys = array_merge($userdesc->keys, $otherkeys);
2801          return new external_multiple_structure($userdesc);
2802      }
2803  
2804      /**
2805       * Returns description of method parameters
2806       *
2807       * @return external_function_parameters
2808       * @since Moodle 3.1
2809       */
2810      public static function get_participant_parameters() {
2811          return new external_function_parameters(
2812              array(
2813                  'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2814                  'userid' => new external_value(PARAM_INT, 'user id'),
2815                  'embeduser' => new external_value(PARAM_BOOL, 'user id', VALUE_DEFAULT, false),
2816              )
2817          );
2818      }
2819  
2820      /**
2821       * Get the user participating in the given assignment. An error with code 'usernotincourse'
2822       * is thrown is the user isn't a participant of the given assignment.
2823       *
2824       * @param int $assignid the assign instance id
2825       * @param int $userid the user id
2826       * @param bool $embeduser return user details (only applicable if not blind marking)
2827       * @return array of warnings and status result
2828       * @since Moodle 3.1
2829       * @throws moodle_exception
2830       */
2831      public static function get_participant($assignid, $userid, $embeduser) {
2832          global $DB, $CFG;
2833          require_once($CFG->dirroot . "/mod/assign/locallib.php");
2834          require_once($CFG->dirroot . "/user/lib.php");
2835          require_once($CFG->libdir . '/grouplib.php');
2836  
2837          $params = self::validate_parameters(self::get_participant_parameters(), array(
2838              'assignid' => $assignid,
2839              'userid' => $userid,
2840              'embeduser' => $embeduser
2841          ));
2842  
2843          list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2844          $assign->require_view_grades();
2845  
2846          $participant = $assign->get_participant($params['userid']);
2847  
2848          // Update assign with override information.
2849          $assign->update_effective_access($params['userid']);
2850  
2851          if (!$participant) {
2852              // No participant found so we can return early.
2853              throw new moodle_exception('usernotincourse');
2854          }
2855  
2856          $filtered = $assign->is_userid_filtered($userid);
2857          if (!$filtered) {
2858              // User is filtered out by user filters or table preferences.
2859              throw new moodle_exception('userisfilteredout');
2860          }
2861  
2862          $return = array(
2863              'id' => $participant->id,
2864              'fullname' => $participant->fullname,
2865              'submitted' => $participant->submitted,
2866              'requiregrading' => $participant->requiregrading,
2867              'grantedextension' => $participant->grantedextension,
2868              'submissionstatus' => $participant->submissionstatus,
2869              'blindmarking' => $assign->is_blind_marking(),
2870              'allowsubmissionsfromdate' => $assign->get_instance($userid)->allowsubmissionsfromdate,
2871              'duedate' => $assign->get_instance($userid)->duedate,
2872              'cutoffdate' => $assign->get_instance($userid)->cutoffdate,
2873              'duedatestr' => userdate($assign->get_instance($userid)->duedate, get_string('strftimedatetime', 'langconfig')),
2874          );
2875  
2876          if (!empty($participant->groupid)) {
2877              $return['groupid'] = $participant->groupid;
2878  
2879              if ($group = groups_get_group($participant->groupid)) {
2880                  // Format properly the group name.
2881                  $return['groupname'] = format_string($group->name);
2882              }
2883          }
2884  
2885          // Skip the expensive lookup of user detail if we're blind marking or the caller
2886          // hasn't asked for user details to be embedded.
2887          if (!$assign->is_blind_marking() && $embeduser) {
2888              if ($userdetails = user_get_user_details($participant, $course)) {
2889                  $return['user'] = $userdetails;
2890              }
2891          }
2892  
2893          return $return;
2894      }
2895  
2896      /**
2897       * Returns description of method result value
2898       *
2899       * @return external_description
2900       * @since Moodle 3.1
2901       */
2902      public static function get_participant_returns() {
2903          $userdescription = core_user_external::user_description();
2904          $userdescription->default = [];
2905          $userdescription->required = VALUE_OPTIONAL;
2906  
2907          return new external_single_structure(array(
2908              'id' => new external_value(PARAM_INT, 'ID of the user'),
2909              'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
2910              'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
2911              'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
2912              'grantedextension' => new external_value(PARAM_BOOL, 'have they been granted an extension'),
2913              'blindmarking' => new external_value(PARAM_BOOL, 'is blind marking enabled for this assignment'),
2914              'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allowsubmissionsfromdate for the user'),
2915              'duedate' => new external_value(PARAM_INT, 'duedate for the user'),
2916              'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user'),
2917              'duedatestr' => new external_value(PARAM_TEXT, 'duedate for the user'),
2918              'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
2919              'groupname' => new external_value(PARAM_TEXT, 'for group assignments this is the group name', VALUE_OPTIONAL),
2920              'submissionstatus' => new external_value(PARAM_ALPHA, 'The submission status (new, draft, reopened or submitted).
2921                  Empty when not submitted.', VALUE_OPTIONAL),
2922              'user' => $userdescription,
2923          ));
2924      }
2925  
2926      /**
2927       * Describes the parameters for view_assign.
2928       *
2929       * @return external_function_parameters
2930       * @since Moodle 3.2
2931       */
2932      public static function view_assign_parameters() {
2933          return new external_function_parameters (
2934              array(
2935                  'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2936              )
2937          );
2938      }
2939  
2940      /**
2941       * Update the module completion status.
2942       *
2943       * @param int $assignid assign instance id
2944       * @return array of warnings and status result
2945       * @since Moodle 3.2
2946       */
2947      public static function view_assign($assignid) {
2948          $warnings = array();
2949          $params = array(
2950              'assignid' => $assignid,
2951          );
2952          $params = self::validate_parameters(self::view_assign_parameters(), $params);
2953  
2954          list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2955  
2956          $assign->set_module_viewed();
2957  
2958          $result = array();
2959          $result['status'] = true;
2960          $result['warnings'] = $warnings;
2961          return $result;
2962      }
2963  
2964      /**
2965       * Describes the view_assign return value.
2966       *
2967       * @return external_single_structure
2968       * @since Moodle 3.2
2969       */
2970      public static function view_assign_returns() {
2971          return new external_single_structure(
2972              array(
2973                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2974                  'warnings' => new external_warnings(),
2975              )
2976          );
2977      }
2978  }