Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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