Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

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