[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/mod/quiz/classes/ -> external.php (source)

   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   * Quiz external API
  19   *
  20   * @package    mod_quiz
  21   * @category   external
  22   * @copyright  2016 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 3.1
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die;
  28  
  29  require_once($CFG->libdir . '/externallib.php');
  30  require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  31  
  32  /**
  33   * Quiz external functions
  34   *
  35   * @package    mod_quiz
  36   * @category   external
  37   * @copyright  2016 Juan Leyva <juan@moodle.com>
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   * @since      Moodle 3.1
  40   */
  41  class mod_quiz_external extends external_api {
  42  
  43      /**
  44       * Describes the parameters for get_quizzes_by_courses.
  45       *
  46       * @return external_external_function_parameters
  47       * @since Moodle 3.1
  48       */
  49      public static function get_quizzes_by_courses_parameters() {
  50          return new external_function_parameters (
  51              array(
  52                  'courseids' => new external_multiple_structure(
  53                      new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
  54                  ),
  55              )
  56          );
  57      }
  58  
  59      /**
  60       * Returns a list of quizzes in a provided list of courses,
  61       * if no list is provided all quizzes that the user can view will be returned.
  62       *
  63       * @param array $courseids Array of course ids
  64       * @return array of quizzes details
  65       * @since Moodle 3.1
  66       */
  67      public static function get_quizzes_by_courses($courseids = array()) {
  68          global $USER;
  69  
  70          $warnings = array();
  71          $returnedquizzes = array();
  72  
  73          $params = array(
  74              'courseids' => $courseids,
  75          );
  76          $params = self::validate_parameters(self::get_quizzes_by_courses_parameters(), $params);
  77  
  78          $mycourses = array();
  79          if (empty($params['courseids'])) {
  80              $mycourses = enrol_get_my_courses();
  81              $params['courseids'] = array_keys($mycourses);
  82          }
  83  
  84          // Ensure there are courseids to loop through.
  85          if (!empty($params['courseids'])) {
  86  
  87              list($courses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses);
  88  
  89              // Get the quizzes in this course, this function checks users visibility permissions.
  90              // We can avoid then additional validate_context calls.
  91              $quizzes = get_all_instances_in_courses("quiz", $courses);
  92              foreach ($quizzes as $quiz) {
  93                  $context = context_module::instance($quiz->coursemodule);
  94  
  95                  // Update quiz with override information.
  96                  $quiz = quiz_update_effective_access($quiz, $USER->id);
  97  
  98                  // Entry to return.
  99                  $quizdetails = array();
 100                  // First, we return information that any user can see in the web interface.
 101                  $quizdetails['id'] = $quiz->id;
 102                  $quizdetails['coursemodule']      = $quiz->coursemodule;
 103                  $quizdetails['course']            = $quiz->course;
 104                  $quizdetails['name']              = external_format_string($quiz->name, $context->id);
 105  
 106                  if (has_capability('mod/quiz:view', $context)) {
 107                      // Format intro.
 108                      list($quizdetails['intro'], $quizdetails['introformat']) = external_format_text($quiz->intro,
 109                                                                      $quiz->introformat, $context->id, 'mod_quiz', 'intro', null);
 110  
 111                      $quizdetails['introfiles'] = external_util::get_area_files($context->id, 'mod_quiz', 'intro', false, false);
 112                      $viewablefields = array('timeopen', 'timeclose', 'grademethod', 'section', 'visible', 'groupmode',
 113                                              'groupingid');
 114  
 115                      $timenow = time();
 116                      $quizobj = quiz::create($quiz->id, $USER->id);
 117                      $accessmanager = new quiz_access_manager($quizobj, $timenow, has_capability('mod/quiz:ignoretimelimits',
 118                                                                  $context, null, false));
 119  
 120                      // Fields the user could see if have access to the quiz.
 121                      if (!$accessmanager->prevent_access()) {
 122                          // Some times this function returns just empty.
 123                          $hasfeedback = quiz_has_feedback($quiz);
 124                          $quizdetails['hasfeedback'] = (!empty($hasfeedback)) ? 1 : 0;
 125                          $quizdetails['hasquestions'] = (int) $quizobj->has_questions();
 126                          $quizdetails['autosaveperiod'] = get_config('quiz', 'autosaveperiod');
 127  
 128                          $additionalfields = array('timelimit', 'attempts', 'attemptonlast', 'grademethod', 'decimalpoints',
 129                                                      'questiondecimalpoints', 'reviewattempt', 'reviewcorrectness', 'reviewmarks',
 130                                                      'reviewspecificfeedback', 'reviewgeneralfeedback', 'reviewrightanswer',
 131                                                      'reviewoverallfeedback', 'questionsperpage', 'navmethod', 'sumgrades', 'grade',
 132                                                      'browsersecurity', 'delay1', 'delay2', 'showuserpicture', 'showblocks',
 133                                                      'completionattemptsexhausted', 'completionpass', 'overduehandling',
 134                                                      'graceperiod', 'preferredbehaviour', 'canredoquestions');
 135                          $viewablefields = array_merge($viewablefields, $additionalfields);
 136                      }
 137  
 138                      // Fields only for managers.
 139                      if (has_capability('moodle/course:manageactivities', $context)) {
 140                          $additionalfields = array('shuffleanswers', 'timecreated', 'timemodified', 'password', 'subnet');
 141                          $viewablefields = array_merge($viewablefields, $additionalfields);
 142                      }
 143  
 144                      foreach ($viewablefields as $field) {
 145                          $quizdetails[$field] = $quiz->{$field};
 146                      }
 147                  }
 148                  $returnedquizzes[] = $quizdetails;
 149              }
 150          }
 151          $result = array();
 152          $result['quizzes'] = $returnedquizzes;
 153          $result['warnings'] = $warnings;
 154          return $result;
 155      }
 156  
 157      /**
 158       * Describes the get_quizzes_by_courses return value.
 159       *
 160       * @return external_single_structure
 161       * @since Moodle 3.1
 162       */
 163      public static function get_quizzes_by_courses_returns() {
 164          return new external_single_structure(
 165              array(
 166                  'quizzes' => new external_multiple_structure(
 167                      new external_single_structure(
 168                          array(
 169                              'id' => new external_value(PARAM_INT, 'Standard Moodle primary key.'),
 170                              'course' => new external_value(PARAM_INT, 'Foreign key reference to the course this quiz is part of.'),
 171                              'coursemodule' => new external_value(PARAM_INT, 'Course module id.'),
 172                              'name' => new external_value(PARAM_RAW, 'Quiz name.'),
 173                              'intro' => new external_value(PARAM_RAW, 'Quiz introduction text.', VALUE_OPTIONAL),
 174                              'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
 175                              'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
 176                              'timeopen' => new external_value(PARAM_INT, 'The time when this quiz opens. (0 = no restriction.)',
 177                                                                  VALUE_OPTIONAL),
 178                              'timeclose' => new external_value(PARAM_INT, 'The time when this quiz closes. (0 = no restriction.)',
 179                                                                  VALUE_OPTIONAL),
 180                              'timelimit' => new external_value(PARAM_INT, 'The time limit for quiz attempts, in seconds.',
 181                                                                  VALUE_OPTIONAL),
 182                              'overduehandling' => new external_value(PARAM_ALPHA, 'The method used to handle overdue attempts.
 183                                                                      \'autosubmit\', \'graceperiod\' or \'autoabandon\'.',
 184                                                                      VALUE_OPTIONAL),
 185                              'graceperiod' => new external_value(PARAM_INT, 'The amount of time (in seconds) after the time limit
 186                                                                  runs out during which attempts can still be submitted,
 187                                                                  if overduehandling is set to allow it.', VALUE_OPTIONAL),
 188                              'preferredbehaviour' => new external_value(PARAM_ALPHANUMEXT, 'The behaviour to ask questions to use.',
 189                                                                          VALUE_OPTIONAL),
 190                              'canredoquestions' => new external_value(PARAM_INT, 'Allows students to redo any completed question
 191                                                                          within a quiz attempt.', VALUE_OPTIONAL),
 192                              'attempts' => new external_value(PARAM_INT, 'The maximum number of attempts a student is allowed.',
 193                                                                  VALUE_OPTIONAL),
 194                              'attemptonlast' => new external_value(PARAM_INT, 'Whether subsequent attempts start from the answer
 195                                                                      to the previous attempt (1) or start blank (0).',
 196                                                                      VALUE_OPTIONAL),
 197                              'grademethod' => new external_value(PARAM_INT, 'One of the values QUIZ_GRADEHIGHEST, QUIZ_GRADEAVERAGE,
 198                                                                      QUIZ_ATTEMPTFIRST or QUIZ_ATTEMPTLAST.', VALUE_OPTIONAL),
 199                              'decimalpoints' => new external_value(PARAM_INT, 'Number of decimal points to use when displaying
 200                                                                      grades.', VALUE_OPTIONAL),
 201                              'questiondecimalpoints' => new external_value(PARAM_INT, 'Number of decimal points to use when
 202                                                                              displaying question grades.
 203                                                                              (-1 means use decimalpoints.)', VALUE_OPTIONAL),
 204                              'reviewattempt' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz
 205                                                                      attempts at various times. This is a bit field, decoded by the
 206                                                                      mod_quiz_display_options class. It is formed by ORing together
 207                                                                      the constants defined there.', VALUE_OPTIONAL),
 208                              'reviewcorrectness' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz
 209                                                                          attempts at various times.
 210                                                                          A bit field, like reviewattempt.', VALUE_OPTIONAL),
 211                              'reviewmarks' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz attempts
 212                                                                  at various times. A bit field, like reviewattempt.',
 213                                                                  VALUE_OPTIONAL),
 214                              'reviewspecificfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their
 215                                                                              quiz attempts at various times. A bit field, like
 216                                                                              reviewattempt.', VALUE_OPTIONAL),
 217                              'reviewgeneralfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their
 218                                                                              quiz attempts at various times. A bit field, like
 219                                                                              reviewattempt.', VALUE_OPTIONAL),
 220                              'reviewrightanswer' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz
 221                                                                          attempts at various times. A bit field, like
 222                                                                          reviewattempt.', VALUE_OPTIONAL),
 223                              'reviewoverallfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz
 224                                                                              attempts at various times. A bit field, like
 225                                                                              reviewattempt.', VALUE_OPTIONAL),
 226                              'questionsperpage' => new external_value(PARAM_INT, 'How often to insert a page break when editing
 227                                                                          the quiz, or when shuffling the question order.',
 228                                                                          VALUE_OPTIONAL),
 229                              'navmethod' => new external_value(PARAM_ALPHA, 'Any constraints on how the user is allowed to navigate
 230                                                                  around the quiz. Currently recognised values are
 231                                                                  \'free\' and \'seq\'.', VALUE_OPTIONAL),
 232                              'shuffleanswers' => new external_value(PARAM_INT, 'Whether the parts of the question should be shuffled,
 233                                                                      in those question types that support it.', VALUE_OPTIONAL),
 234                              'sumgrades' => new external_value(PARAM_FLOAT, 'The total of all the question instance maxmarks.',
 235                                                                  VALUE_OPTIONAL),
 236                              'grade' => new external_value(PARAM_FLOAT, 'The total that the quiz overall grade is scaled to be
 237                                                              out of.', VALUE_OPTIONAL),
 238                              'timecreated' => new external_value(PARAM_INT, 'The time when the quiz was added to the course.',
 239                                                                  VALUE_OPTIONAL),
 240                              'timemodified' => new external_value(PARAM_INT, 'Last modified time.',
 241                                                                      VALUE_OPTIONAL),
 242                              'password' => new external_value(PARAM_RAW, 'A password that the student must enter before starting or
 243                                                                  continuing a quiz attempt.', VALUE_OPTIONAL),
 244                              'subnet' => new external_value(PARAM_RAW, 'Used to restrict the IP addresses from which this quiz can
 245                                                              be attempted. The format is as requried by the address_in_subnet
 246                                                              function.', VALUE_OPTIONAL),
 247                              'browsersecurity' => new external_value(PARAM_ALPHANUMEXT, 'Restriciton on the browser the student must
 248                                                                      use. E.g. \'securewindow\'.', VALUE_OPTIONAL),
 249                              'delay1' => new external_value(PARAM_INT, 'Delay that must be left between the first and second attempt,
 250                                                              in seconds.', VALUE_OPTIONAL),
 251                              'delay2' => new external_value(PARAM_INT, 'Delay that must be left between the second and subsequent
 252                                                              attempt, in seconds.', VALUE_OPTIONAL),
 253                              'showuserpicture' => new external_value(PARAM_INT, 'Option to show the user\'s picture during the
 254                                                                      attempt and on the review page.', VALUE_OPTIONAL),
 255                              'showblocks' => new external_value(PARAM_INT, 'Whether blocks should be shown on the attempt.php and
 256                                                                  review.php pages.', VALUE_OPTIONAL),
 257                              'completionattemptsexhausted' => new external_value(PARAM_INT, 'Mark quiz complete when the student has
 258                                                                                  exhausted the maximum number of attempts',
 259                                                                                  VALUE_OPTIONAL),
 260                              'completionpass' => new external_value(PARAM_INT, 'Whether to require passing grade', VALUE_OPTIONAL),
 261                              'autosaveperiod' => new external_value(PARAM_INT, 'Auto-save delay', VALUE_OPTIONAL),
 262                              'hasfeedback' => new external_value(PARAM_INT, 'Whether the quiz has any non-blank feedback text',
 263                                                                  VALUE_OPTIONAL),
 264                              'hasquestions' => new external_value(PARAM_INT, 'Whether the quiz has questions', VALUE_OPTIONAL),
 265                              'section' => new external_value(PARAM_INT, 'Course section id', VALUE_OPTIONAL),
 266                              'visible' => new external_value(PARAM_INT, 'Module visibility', VALUE_OPTIONAL),
 267                              'groupmode' => new external_value(PARAM_INT, 'Group mode', VALUE_OPTIONAL),
 268                              'groupingid' => new external_value(PARAM_INT, 'Grouping id', VALUE_OPTIONAL),
 269                          )
 270                      )
 271                  ),
 272                  'warnings' => new external_warnings(),
 273              )
 274          );
 275      }
 276  
 277  
 278      /**
 279       * Utility function for validating a quiz.
 280       *
 281       * @param int $quizid quiz instance id
 282       * @return array array containing the quiz, course, context and course module objects
 283       * @since  Moodle 3.1
 284       */
 285      protected static function validate_quiz($quizid) {
 286          global $DB;
 287  
 288          // Request and permission validation.
 289          $quiz = $DB->get_record('quiz', array('id' => $quizid), '*', MUST_EXIST);
 290          list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz');
 291  
 292          $context = context_module::instance($cm->id);
 293          self::validate_context($context);
 294  
 295          return array($quiz, $course, $cm, $context);
 296      }
 297  
 298      /**
 299       * Describes the parameters for view_quiz.
 300       *
 301       * @return external_external_function_parameters
 302       * @since Moodle 3.1
 303       */
 304      public static function view_quiz_parameters() {
 305          return new external_function_parameters (
 306              array(
 307                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
 308              )
 309          );
 310      }
 311  
 312      /**
 313       * Trigger the course module viewed event and update the module completion status.
 314       *
 315       * @param int $quizid quiz instance id
 316       * @return array of warnings and status result
 317       * @since Moodle 3.1
 318       * @throws moodle_exception
 319       */
 320      public static function view_quiz($quizid) {
 321          global $DB;
 322  
 323          $params = self::validate_parameters(self::view_quiz_parameters(), array('quizid' => $quizid));
 324          $warnings = array();
 325  
 326          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
 327  
 328          // Trigger course_module_viewed event and completion.
 329          quiz_view($quiz, $course, $cm, $context);
 330  
 331          $result = array();
 332          $result['status'] = true;
 333          $result['warnings'] = $warnings;
 334          return $result;
 335      }
 336  
 337      /**
 338       * Describes the view_quiz return value.
 339       *
 340       * @return external_single_structure
 341       * @since Moodle 3.1
 342       */
 343      public static function view_quiz_returns() {
 344          return new external_single_structure(
 345              array(
 346                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
 347                  'warnings' => new external_warnings(),
 348              )
 349          );
 350      }
 351  
 352      /**
 353       * Describes the parameters for get_user_attempts.
 354       *
 355       * @return external_external_function_parameters
 356       * @since Moodle 3.1
 357       */
 358      public static function get_user_attempts_parameters() {
 359          return new external_function_parameters (
 360              array(
 361                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
 362                  'userid' => new external_value(PARAM_INT, 'user id, empty for current user', VALUE_DEFAULT, 0),
 363                  'status' => new external_value(PARAM_ALPHA, 'quiz status: all, finished or unfinished', VALUE_DEFAULT, 'finished'),
 364                  'includepreviews' => new external_value(PARAM_BOOL, 'whether to include previews or not', VALUE_DEFAULT, false),
 365  
 366              )
 367          );
 368      }
 369  
 370      /**
 371       * Return a list of attempts for the given quiz and user.
 372       *
 373       * @param int $quizid quiz instance id
 374       * @param int $userid user id
 375       * @param string $status quiz status: all, finished or unfinished
 376       * @param bool $includepreviews whether to include previews or not
 377       * @return array of warnings and the list of attempts
 378       * @since Moodle 3.1
 379       * @throws invalid_parameter_exception
 380       */
 381      public static function get_user_attempts($quizid, $userid = 0, $status = 'finished', $includepreviews = false) {
 382          global $DB, $USER;
 383  
 384          $warnings = array();
 385  
 386          $params = array(
 387              'quizid' => $quizid,
 388              'userid' => $userid,
 389              'status' => $status,
 390              'includepreviews' => $includepreviews,
 391          );
 392          $params = self::validate_parameters(self::get_user_attempts_parameters(), $params);
 393  
 394          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
 395  
 396          if (!in_array($params['status'], array('all', 'finished', 'unfinished'))) {
 397              throw new invalid_parameter_exception('Invalid status value');
 398          }
 399  
 400          // Default value for userid.
 401          if (empty($params['userid'])) {
 402              $params['userid'] = $USER->id;
 403          }
 404  
 405          $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
 406          core_user::require_active_user($user);
 407  
 408          // Extra checks so only users with permissions can view other users attempts.
 409          if ($USER->id != $user->id) {
 410              require_capability('mod/quiz:viewreports', $context);
 411          }
 412  
 413          $attempts = quiz_get_user_attempts($quiz->id, $user->id, $params['status'], $params['includepreviews']);
 414  
 415          $result = array();
 416          $result['attempts'] = $attempts;
 417          $result['warnings'] = $warnings;
 418          return $result;
 419      }
 420  
 421      /**
 422       * Describes a single attempt structure.
 423       *
 424       * @return external_single_structure the attempt structure
 425       */
 426      private static function attempt_structure() {
 427          return new external_single_structure(
 428              array(
 429                  'id' => new external_value(PARAM_INT, 'Attempt id.', VALUE_OPTIONAL),
 430                  'quiz' => new external_value(PARAM_INT, 'Foreign key reference to the quiz that was attempted.',
 431                                                  VALUE_OPTIONAL),
 432                  'userid' => new external_value(PARAM_INT, 'Foreign key reference to the user whose attempt this is.',
 433                                                  VALUE_OPTIONAL),
 434                  'attempt' => new external_value(PARAM_INT, 'Sequentially numbers this students attempts at this quiz.',
 435                                                  VALUE_OPTIONAL),
 436                  'uniqueid' => new external_value(PARAM_INT, 'Foreign key reference to the question_usage that holds the
 437                                                      details of the the question_attempts that make up this quiz
 438                                                      attempt.', VALUE_OPTIONAL),
 439                  'layout' => new external_value(PARAM_RAW, 'Attempt layout.', VALUE_OPTIONAL),
 440                  'currentpage' => new external_value(PARAM_INT, 'Attempt current page.', VALUE_OPTIONAL),
 441                  'preview' => new external_value(PARAM_INT, 'Whether is a preview attempt or not.', VALUE_OPTIONAL),
 442                  'state' => new external_value(PARAM_ALPHA, 'The current state of the attempts. \'inprogress\',
 443                                                  \'overdue\', \'finished\' or \'abandoned\'.', VALUE_OPTIONAL),
 444                  'timestart' => new external_value(PARAM_INT, 'Time when the attempt was started.', VALUE_OPTIONAL),
 445                  'timefinish' => new external_value(PARAM_INT, 'Time when the attempt was submitted.
 446                                                      0 if the attempt has not been submitted yet.', VALUE_OPTIONAL),
 447                  'timemodified' => new external_value(PARAM_INT, 'Last modified time.', VALUE_OPTIONAL),
 448                  'timecheckstate' => new external_value(PARAM_INT, 'Next time quiz cron should check attempt for
 449                                                          state changes.  NULL means never check.', VALUE_OPTIONAL),
 450                  'sumgrades' => new external_value(PARAM_FLOAT, 'Total marks for this attempt.', VALUE_OPTIONAL),
 451              )
 452          );
 453      }
 454  
 455      /**
 456       * Describes the get_user_attempts return value.
 457       *
 458       * @return external_single_structure
 459       * @since Moodle 3.1
 460       */
 461      public static function get_user_attempts_returns() {
 462          return new external_single_structure(
 463              array(
 464                  'attempts' => new external_multiple_structure(self::attempt_structure()),
 465                  'warnings' => new external_warnings(),
 466              )
 467          );
 468      }
 469  
 470      /**
 471       * Describes the parameters for get_user_best_grade.
 472       *
 473       * @return external_external_function_parameters
 474       * @since Moodle 3.1
 475       */
 476      public static function get_user_best_grade_parameters() {
 477          return new external_function_parameters (
 478              array(
 479                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
 480                  'userid' => new external_value(PARAM_INT, 'user id', VALUE_DEFAULT, 0),
 481              )
 482          );
 483      }
 484  
 485      /**
 486       * Get the best current grade for the given user on a quiz.
 487       *
 488       * @param int $quizid quiz instance id
 489       * @param int $userid user id
 490       * @return array of warnings and the grade information
 491       * @since Moodle 3.1
 492       */
 493      public static function get_user_best_grade($quizid, $userid = 0) {
 494          global $DB, $USER;
 495  
 496          $warnings = array();
 497  
 498          $params = array(
 499              'quizid' => $quizid,
 500              'userid' => $userid,
 501          );
 502          $params = self::validate_parameters(self::get_user_best_grade_parameters(), $params);
 503  
 504          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
 505  
 506          // Default value for userid.
 507          if (empty($params['userid'])) {
 508              $params['userid'] = $USER->id;
 509          }
 510  
 511          $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
 512          core_user::require_active_user($user);
 513  
 514          // Extra checks so only users with permissions can view other users attempts.
 515          if ($USER->id != $user->id) {
 516              require_capability('mod/quiz:viewreports', $context);
 517          }
 518  
 519          $result = array();
 520          $grade = quiz_get_best_grade($quiz, $user->id);
 521  
 522          if ($grade === null) {
 523              $result['hasgrade'] = false;
 524          } else {
 525              $result['hasgrade'] = true;
 526              $result['grade'] = $grade;
 527          }
 528          $result['warnings'] = $warnings;
 529          return $result;
 530      }
 531  
 532      /**
 533       * Describes the get_user_best_grade return value.
 534       *
 535       * @return external_single_structure
 536       * @since Moodle 3.1
 537       */
 538      public static function get_user_best_grade_returns() {
 539          return new external_single_structure(
 540              array(
 541                  'hasgrade' => new external_value(PARAM_BOOL, 'Whether the user has a grade on the given quiz.'),
 542                  'grade' => new external_value(PARAM_FLOAT, 'The grade (only if the user has a grade).', VALUE_OPTIONAL),
 543                  'warnings' => new external_warnings(),
 544              )
 545          );
 546      }
 547  
 548      /**
 549       * Describes the parameters for get_combined_review_options.
 550       *
 551       * @return external_external_function_parameters
 552       * @since Moodle 3.1
 553       */
 554      public static function get_combined_review_options_parameters() {
 555          return new external_function_parameters (
 556              array(
 557                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
 558                  'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0),
 559  
 560              )
 561          );
 562      }
 563  
 564      /**
 565       * Combines the review options from a number of different quiz attempts.
 566       *
 567       * @param int $quizid quiz instance id
 568       * @param int $userid user id (empty for current user)
 569       * @return array of warnings and the review options
 570       * @since Moodle 3.1
 571       */
 572      public static function get_combined_review_options($quizid, $userid = 0) {
 573          global $DB, $USER;
 574  
 575          $warnings = array();
 576  
 577          $params = array(
 578              'quizid' => $quizid,
 579              'userid' => $userid,
 580          );
 581          $params = self::validate_parameters(self::get_combined_review_options_parameters(), $params);
 582  
 583          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
 584  
 585          // Default value for userid.
 586          if (empty($params['userid'])) {
 587              $params['userid'] = $USER->id;
 588          }
 589  
 590          $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
 591          core_user::require_active_user($user);
 592  
 593          // Extra checks so only users with permissions can view other users attempts.
 594          if ($USER->id != $user->id) {
 595              require_capability('mod/quiz:viewreports', $context);
 596          }
 597  
 598          $attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all', true);
 599  
 600          $result = array();
 601          $result['someoptions'] = [];
 602          $result['alloptions'] = [];
 603  
 604          list($someoptions, $alloptions) = quiz_get_combined_reviewoptions($quiz, $attempts);
 605  
 606          foreach (array('someoptions', 'alloptions') as $typeofoption) {
 607              foreach ($$typeofoption as $key => $value) {
 608                  $result[$typeofoption][] = array(
 609                      "name" => $key,
 610                      "value" => (!empty($value)) ? $value : 0
 611                  );
 612              }
 613          }
 614  
 615          $result['warnings'] = $warnings;
 616          return $result;
 617      }
 618  
 619      /**
 620       * Describes the get_combined_review_options return value.
 621       *
 622       * @return external_single_structure
 623       * @since Moodle 3.1
 624       */
 625      public static function get_combined_review_options_returns() {
 626          return new external_single_structure(
 627              array(
 628                  'someoptions' => new external_multiple_structure(
 629                      new external_single_structure(
 630                          array(
 631                              'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'),
 632                              'value' => new external_value(PARAM_INT, 'option value'),
 633                          )
 634                      )
 635                  ),
 636                  'alloptions' => new external_multiple_structure(
 637                      new external_single_structure(
 638                          array(
 639                              'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'),
 640                              'value' => new external_value(PARAM_INT, 'option value'),
 641                          )
 642                      )
 643                  ),
 644                  'warnings' => new external_warnings(),
 645              )
 646          );
 647      }
 648  
 649      /**
 650       * Describes the parameters for start_attempt.
 651       *
 652       * @return external_external_function_parameters
 653       * @since Moodle 3.1
 654       */
 655      public static function start_attempt_parameters() {
 656          return new external_function_parameters (
 657              array(
 658                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
 659                  'preflightdata' => new external_multiple_structure(
 660                      new external_single_structure(
 661                          array(
 662                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
 663                              'value' => new external_value(PARAM_RAW, 'data value'),
 664                          )
 665                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
 666                  ),
 667                  'forcenew' => new external_value(PARAM_BOOL, 'Whether to force a new attempt or not.', VALUE_DEFAULT, false),
 668  
 669              )
 670          );
 671      }
 672  
 673      /**
 674       * Starts a new attempt at a quiz.
 675       *
 676       * @param int $quizid quiz instance id
 677       * @param array $preflightdata preflight required data (like passwords)
 678       * @param bool $forcenew Whether to force a new attempt or not.
 679       * @return array of warnings and the attempt basic data
 680       * @since Moodle 3.1
 681       * @throws moodle_quiz_exception
 682       */
 683      public static function start_attempt($quizid, $preflightdata = array(), $forcenew = false) {
 684          global $DB, $USER;
 685  
 686          $warnings = array();
 687          $attempt = array();
 688  
 689          $params = array(
 690              'quizid' => $quizid,
 691              'preflightdata' => $preflightdata,
 692              'forcenew' => $forcenew,
 693          );
 694          $params = self::validate_parameters(self::start_attempt_parameters(), $params);
 695          $forcenew = $params['forcenew'];
 696  
 697          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
 698  
 699          $quizobj = quiz::create($cm->instance, $USER->id);
 700  
 701          // Check questions.
 702          if (!$quizobj->has_questions()) {
 703              throw new moodle_quiz_exception($quizobj, 'noquestionsfound');
 704          }
 705  
 706          // Create an object to manage all the other (non-roles) access rules.
 707          $timenow = time();
 708          $accessmanager = $quizobj->get_access_manager($timenow);
 709  
 710          // Validate permissions for creating a new attempt and start a new preview attempt if required.
 711          list($currentattemptid, $attemptnumber, $lastattempt, $messages, $page) =
 712              quiz_validate_new_attempt($quizobj, $accessmanager, $forcenew, -1, false);
 713  
 714          // Check access.
 715          if (!$quizobj->is_preview_user() && $messages) {
 716              // Create warnings with the exact messages.
 717              foreach ($messages as $message) {
 718                  $warnings[] = array(
 719                      'item' => 'quiz',
 720                      'itemid' => $quiz->id,
 721                      'warningcode' => '1',
 722                      'message' => clean_text($message, PARAM_TEXT)
 723                  );
 724              }
 725          } else {
 726              if ($accessmanager->is_preflight_check_required($currentattemptid)) {
 727                  // Need to do some checks before allowing the user to continue.
 728  
 729                  $provideddata = array();
 730                  foreach ($params['preflightdata'] as $data) {
 731                      $provideddata[$data['name']] = $data['value'];
 732                  }
 733  
 734                  $errors = $accessmanager->validate_preflight_check($provideddata, [], $currentattemptid);
 735  
 736                  if (!empty($errors)) {
 737                      throw new moodle_quiz_exception($quizobj, array_shift($errors));
 738                  }
 739  
 740                  // Pre-flight check passed.
 741                  $accessmanager->notify_preflight_check_passed($currentattemptid);
 742              }
 743  
 744              if ($currentattemptid) {
 745                  if ($lastattempt->state == quiz_attempt::OVERDUE) {
 746                      throw new moodle_quiz_exception($quizobj, 'stateoverdue');
 747                  } else {
 748                      throw new moodle_quiz_exception($quizobj, 'attemptstillinprogress');
 749                  }
 750              }
 751              $attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, $lastattempt);
 752          }
 753  
 754          $result = array();
 755          $result['attempt'] = $attempt;
 756          $result['warnings'] = $warnings;
 757          return $result;
 758      }
 759  
 760      /**
 761       * Describes the start_attempt return value.
 762       *
 763       * @return external_single_structure
 764       * @since Moodle 3.1
 765       */
 766      public static function start_attempt_returns() {
 767          return new external_single_structure(
 768              array(
 769                  'attempt' => self::attempt_structure(),
 770                  'warnings' => new external_warnings(),
 771              )
 772          );
 773      }
 774  
 775      /**
 776       * Utility function for validating a given attempt
 777       *
 778       * @param  array $params array of parameters including the attemptid and preflight data
 779       * @param  bool $checkaccessrules whether to check the quiz access rules or not
 780       * @param  bool $failifoverdue whether to return error if the attempt is overdue
 781       * @return  array containing the attempt object and access messages
 782       * @throws moodle_quiz_exception
 783       * @since  Moodle 3.1
 784       */
 785      protected static function validate_attempt($params, $checkaccessrules = true, $failifoverdue = true) {
 786          global $USER;
 787  
 788          $attemptobj = quiz_attempt::create($params['attemptid']);
 789  
 790          $context = context_module::instance($attemptobj->get_cm()->id);
 791          self::validate_context($context);
 792  
 793          // Check that this attempt belongs to this user.
 794          if ($attemptobj->get_userid() != $USER->id) {
 795              throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
 796          }
 797  
 798          // General capabilities check.
 799          $ispreviewuser = $attemptobj->is_preview_user();
 800          if (!$ispreviewuser) {
 801              $attemptobj->require_capability('mod/quiz:attempt');
 802          }
 803  
 804          // Check the access rules.
 805          $accessmanager = $attemptobj->get_access_manager(time());
 806          $messages = array();
 807          if ($checkaccessrules) {
 808              // If the attempt is now overdue, or abandoned, deal with that.
 809              $attemptobj->handle_if_time_expired(time(), true);
 810  
 811              $messages = $accessmanager->prevent_access();
 812              if (!$ispreviewuser && $messages) {
 813                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attempterror');
 814              }
 815          }
 816  
 817          // Attempt closed?.
 818          if ($attemptobj->is_finished()) {
 819              throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptalreadyclosed');
 820          } else if ($failifoverdue && $attemptobj->get_state() == quiz_attempt::OVERDUE) {
 821              throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'stateoverdue');
 822          }
 823  
 824          // User submitted data (like the quiz password).
 825          if ($accessmanager->is_preflight_check_required($attemptobj->get_attemptid())) {
 826              $provideddata = array();
 827              foreach ($params['preflightdata'] as $data) {
 828                  $provideddata[$data['name']] = $data['value'];
 829              }
 830  
 831              $errors = $accessmanager->validate_preflight_check($provideddata, [], $params['attemptid']);
 832              if (!empty($errors)) {
 833                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), array_shift($errors));
 834              }
 835              // Pre-flight check passed.
 836              $accessmanager->notify_preflight_check_passed($params['attemptid']);
 837          }
 838  
 839          if (isset($params['page'])) {
 840              // Check if the page is out of range.
 841              if ($params['page'] != $attemptobj->force_page_number_into_range($params['page'])) {
 842                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Invalid page number');
 843              }
 844  
 845              // Prevent out of sequence access.
 846              if (!$attemptobj->check_page_access($params['page'])) {
 847                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access');
 848              }
 849  
 850              // Check slots.
 851              $slots = $attemptobj->get_slots($params['page']);
 852  
 853              if (empty($slots)) {
 854                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound');
 855              }
 856          }
 857  
 858          return array($attemptobj, $messages);
 859      }
 860  
 861      /**
 862       * Describes a single question structure.
 863       *
 864       * @return external_single_structure the question structure
 865       * @since  Moodle 3.1
 866       */
 867      private static function question_structure() {
 868          return new external_single_structure(
 869              array(
 870                  'slot' => new external_value(PARAM_INT, 'slot number'),
 871                  'type' => new external_value(PARAM_ALPHANUMEXT, 'question type, i.e: multichoice'),
 872                  'page' => new external_value(PARAM_INT, 'page of the quiz this question appears on'),
 873                  'html' => new external_value(PARAM_RAW, 'the question rendered'),
 874                  'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not'),
 875                  'number' => new external_value(PARAM_INT, 'question ordering number in the quiz', VALUE_OPTIONAL),
 876                  'state' => new external_value(PARAM_ALPHA, 'the state where the question is in', VALUE_OPTIONAL),
 877                  'status' => new external_value(PARAM_RAW, 'current formatted state of the question', VALUE_OPTIONAL),
 878                  'mark' => new external_value(PARAM_RAW, 'the mark awarded', VALUE_OPTIONAL),
 879                  'maxmark' => new external_value(PARAM_FLOAT, 'the maximum mark possible for this question attempt', VALUE_OPTIONAL),
 880              )
 881          );
 882      }
 883  
 884      /**
 885       * Return questions information for a given attempt.
 886       *
 887       * @param  quiz_attempt  $attemptobj  the quiz attempt object
 888       * @param  bool  $review  whether if we are in review mode or not
 889       * @param  mixed  $page  string 'all' or integer page number
 890       * @return array array of questions including data
 891       */
 892      private static function get_attempt_questions_data(quiz_attempt $attemptobj, $review, $page = 'all') {
 893          global $PAGE;
 894  
 895          $questions = array();
 896          $contextid = $attemptobj->get_quizobj()->get_context()->id;
 897          $displayoptions = $attemptobj->get_display_options($review);
 898          $renderer = $PAGE->get_renderer('mod_quiz');
 899  
 900          foreach ($attemptobj->get_slots($page) as $slot) {
 901  
 902              $question = array(
 903                  'slot' => $slot,
 904                  'type' => $attemptobj->get_question_type_name($slot),
 905                  'page' => $attemptobj->get_question_page($slot),
 906                  'flagged' => $attemptobj->is_question_flagged($slot),
 907                  'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code()
 908              );
 909  
 910              if ($attemptobj->is_real_question($slot)) {
 911                  $question['number'] = $attemptobj->get_question_number($slot);
 912                  $question['state'] = (string) $attemptobj->get_question_state($slot);
 913                  $question['status'] = $attemptobj->get_question_status($slot, $displayoptions->correctness);
 914              }
 915              if ($displayoptions->marks >= question_display_options::MAX_ONLY) {
 916                  $question['maxmark'] = $attemptobj->get_question_attempt($slot)->get_max_mark();
 917              }
 918              if ($displayoptions->marks >= question_display_options::MARK_AND_MAX) {
 919                  $question['mark'] = $attemptobj->get_question_mark($slot);
 920              }
 921  
 922              $questions[] = $question;
 923          }
 924          return $questions;
 925      }
 926  
 927      /**
 928       * Describes the parameters for get_attempt_data.
 929       *
 930       * @return external_external_function_parameters
 931       * @since Moodle 3.1
 932       */
 933      public static function get_attempt_data_parameters() {
 934          return new external_function_parameters (
 935              array(
 936                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
 937                  'page' => new external_value(PARAM_INT, 'page number'),
 938                  'preflightdata' => new external_multiple_structure(
 939                      new external_single_structure(
 940                          array(
 941                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
 942                              'value' => new external_value(PARAM_RAW, 'data value'),
 943                          )
 944                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
 945                  )
 946              )
 947          );
 948      }
 949  
 950      /**
 951       * Returns information for the given attempt page for a quiz attempt in progress.
 952       *
 953       * @param int $attemptid attempt id
 954       * @param int $page page number
 955       * @param array $preflightdata preflight required data (like passwords)
 956       * @return array of warnings and the attempt data, next page, message and questions
 957       * @since Moodle 3.1
 958       * @throws moodle_quiz_exceptions
 959       */
 960      public static function get_attempt_data($attemptid, $page, $preflightdata = array()) {
 961  
 962          $warnings = array();
 963  
 964          $params = array(
 965              'attemptid' => $attemptid,
 966              'page' => $page,
 967              'preflightdata' => $preflightdata,
 968          );
 969          $params = self::validate_parameters(self::get_attempt_data_parameters(), $params);
 970  
 971          list($attemptobj, $messages) = self::validate_attempt($params);
 972  
 973          if ($attemptobj->is_last_page($params['page'])) {
 974              $nextpage = -1;
 975          } else {
 976              $nextpage = $params['page'] + 1;
 977          }
 978  
 979          $result = array();
 980          $result['attempt'] = $attemptobj->get_attempt();
 981          $result['messages'] = $messages;
 982          $result['nextpage'] = $nextpage;
 983          $result['warnings'] = $warnings;
 984          $result['questions'] = self::get_attempt_questions_data($attemptobj, false, $params['page']);
 985  
 986          return $result;
 987      }
 988  
 989      /**
 990       * Describes the get_attempt_data return value.
 991       *
 992       * @return external_single_structure
 993       * @since Moodle 3.1
 994       */
 995      public static function get_attempt_data_returns() {
 996          return new external_single_structure(
 997              array(
 998                  'attempt' => self::attempt_structure(),
 999                  'messages' => new external_multiple_structure(
1000                      new external_value(PARAM_TEXT, 'access message'),
1001                      'access messages, will only be returned for users with mod/quiz:preview capability,
1002                      for other users this method will throw an exception if there are messages'),
1003                  'nextpage' => new external_value(PARAM_INT, 'next page number'),
1004                  'questions' => new external_multiple_structure(self::question_structure()),
1005                  'warnings' => new external_warnings(),
1006              )
1007          );
1008      }
1009  
1010      /**
1011       * Describes the parameters for get_attempt_summary.
1012       *
1013       * @return external_external_function_parameters
1014       * @since Moodle 3.1
1015       */
1016      public static function get_attempt_summary_parameters() {
1017          return new external_function_parameters (
1018              array(
1019                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1020                  'preflightdata' => new external_multiple_structure(
1021                      new external_single_structure(
1022                          array(
1023                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
1024                              'value' => new external_value(PARAM_RAW, 'data value'),
1025                          )
1026                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
1027                  )
1028              )
1029          );
1030      }
1031  
1032      /**
1033       * Returns a summary of a quiz attempt before it is submitted.
1034       *
1035       * @param int $attemptid attempt id
1036       * @param int $preflightdata preflight required data (like passwords)
1037       * @return array of warnings and the attempt summary data for each question
1038       * @since Moodle 3.1
1039       */
1040      public static function get_attempt_summary($attemptid, $preflightdata = array()) {
1041  
1042          $warnings = array();
1043  
1044          $params = array(
1045              'attemptid' => $attemptid,
1046              'preflightdata' => $preflightdata,
1047          );
1048          $params = self::validate_parameters(self::get_attempt_summary_parameters(), $params);
1049  
1050          list($attemptobj, $messages) = self::validate_attempt($params, true, false);
1051  
1052          $result = array();
1053          $result['warnings'] = $warnings;
1054          $result['questions'] = self::get_attempt_questions_data($attemptobj, false, 'all');
1055  
1056          return $result;
1057      }
1058  
1059      /**
1060       * Describes the get_attempt_summary return value.
1061       *
1062       * @return external_single_structure
1063       * @since Moodle 3.1
1064       */
1065      public static function get_attempt_summary_returns() {
1066          return new external_single_structure(
1067              array(
1068                  'questions' => new external_multiple_structure(self::question_structure()),
1069                  'warnings' => new external_warnings(),
1070              )
1071          );
1072      }
1073  
1074      /**
1075       * Describes the parameters for save_attempt.
1076       *
1077       * @return external_external_function_parameters
1078       * @since Moodle 3.1
1079       */
1080      public static function save_attempt_parameters() {
1081          return new external_function_parameters (
1082              array(
1083                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1084                  'data' => new external_multiple_structure(
1085                      new external_single_structure(
1086                          array(
1087                              'name' => new external_value(PARAM_RAW, 'data name'),
1088                              'value' => new external_value(PARAM_RAW, 'data value'),
1089                          )
1090                      ), 'the data to be saved'
1091                  ),
1092                  'preflightdata' => new external_multiple_structure(
1093                      new external_single_structure(
1094                          array(
1095                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
1096                              'value' => new external_value(PARAM_RAW, 'data value'),
1097                          )
1098                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
1099                  )
1100              )
1101          );
1102      }
1103  
1104      /**
1105       * Processes save requests during the quiz. This function is intended for the quiz auto-save feature.
1106       *
1107       * @param int $attemptid attempt id
1108       * @param array $data the data to be saved
1109       * @param  array $preflightdata preflight required data (like passwords)
1110       * @return array of warnings and execution result
1111       * @since Moodle 3.1
1112       */
1113      public static function save_attempt($attemptid, $data, $preflightdata = array()) {
1114          global $DB;
1115  
1116          $warnings = array();
1117  
1118          $params = array(
1119              'attemptid' => $attemptid,
1120              'data' => $data,
1121              'preflightdata' => $preflightdata,
1122          );
1123          $params = self::validate_parameters(self::save_attempt_parameters(), $params);
1124  
1125          // Add a page, required by validate_attempt.
1126          list($attemptobj, $messages) = self::validate_attempt($params);
1127  
1128          $transaction = $DB->start_delegated_transaction();
1129          // Create the $_POST object required by the question engine.
1130          $_POST = array();
1131          foreach ($data as $element) {
1132              $_POST[$element['name']] = $element['value'];
1133          }
1134          $timenow = time();
1135          $attemptobj->process_auto_save($timenow);
1136          $transaction->allow_commit();
1137  
1138          $result = array();
1139          $result['status'] = true;
1140          $result['warnings'] = $warnings;
1141          return $result;
1142      }
1143  
1144      /**
1145       * Describes the save_attempt return value.
1146       *
1147       * @return external_single_structure
1148       * @since Moodle 3.1
1149       */
1150      public static function save_attempt_returns() {
1151          return new external_single_structure(
1152              array(
1153                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1154                  'warnings' => new external_warnings(),
1155              )
1156          );
1157      }
1158  
1159      /**
1160       * Describes the parameters for process_attempt.
1161       *
1162       * @return external_external_function_parameters
1163       * @since Moodle 3.1
1164       */
1165      public static function process_attempt_parameters() {
1166          return new external_function_parameters (
1167              array(
1168                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1169                  'data' => new external_multiple_structure(
1170                      new external_single_structure(
1171                          array(
1172                              'name' => new external_value(PARAM_RAW, 'data name'),
1173                              'value' => new external_value(PARAM_RAW, 'data value'),
1174                          )
1175                      ),
1176                      'the data to be saved', VALUE_DEFAULT, array()
1177                  ),
1178                  'finishattempt' => new external_value(PARAM_BOOL, 'whether to finish or not the attempt', VALUE_DEFAULT, false),
1179                  'timeup' => new external_value(PARAM_BOOL, 'whether the WS was called by a timer when the time is up',
1180                                                  VALUE_DEFAULT, false),
1181                  'preflightdata' => new external_multiple_structure(
1182                      new external_single_structure(
1183                          array(
1184                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
1185                              'value' => new external_value(PARAM_RAW, 'data value'),
1186                          )
1187                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
1188                  )
1189              )
1190          );
1191      }
1192  
1193      /**
1194       * Process responses during an attempt at a quiz and also deals with attempts finishing.
1195       *
1196       * @param int $attemptid attempt id
1197       * @param array $data the data to be saved
1198       * @param bool $finishattempt whether to finish or not the attempt
1199       * @param bool $timeup whether the WS was called by a timer when the time is up
1200       * @param array $preflightdata preflight required data (like passwords)
1201       * @return array of warnings and the attempt state after the processing
1202       * @since Moodle 3.1
1203       */
1204      public static function process_attempt($attemptid, $data, $finishattempt = false, $timeup = false, $preflightdata = array()) {
1205  
1206          $warnings = array();
1207  
1208          $params = array(
1209              'attemptid' => $attemptid,
1210              'data' => $data,
1211              'finishattempt' => $finishattempt,
1212              'timeup' => $timeup,
1213              'preflightdata' => $preflightdata,
1214          );
1215          $params = self::validate_parameters(self::process_attempt_parameters(), $params);
1216  
1217          // Do not check access manager rules.
1218          list($attemptobj, $messages) = self::validate_attempt($params, false);
1219  
1220          // Create the $_POST object required by the question engine.
1221          $_POST = array();
1222          foreach ($params['data'] as $element) {
1223              $_POST[$element['name']] = $element['value'];
1224          }
1225          $timenow = time();
1226          $finishattempt = $params['finishattempt'];
1227          $timeup = $params['timeup'];
1228  
1229          $result = array();
1230          $result['state'] = $attemptobj->process_attempt($timenow, $finishattempt, $timeup, 0);
1231          $result['warnings'] = $warnings;
1232          return $result;
1233      }
1234  
1235      /**
1236       * Describes the process_attempt return value.
1237       *
1238       * @return external_single_structure
1239       * @since Moodle 3.1
1240       */
1241      public static function process_attempt_returns() {
1242          return new external_single_structure(
1243              array(
1244                  'state' => new external_value(PARAM_ALPHANUMEXT, 'state: the new attempt state:
1245                                                                      inprogress, finished, overdue, abandoned'),
1246                  'warnings' => new external_warnings(),
1247              )
1248          );
1249      }
1250  
1251      /**
1252       * Validate an attempt finished for review. The attempt would be reviewed by a user or a teacher.
1253       *
1254       * @param  array $params Array of parameters including the attemptid
1255       * @return  array containing the attempt object and display options
1256       * @since  Moodle 3.1
1257       * @throws  moodle_exception
1258       * @throws  moodle_quiz_exception
1259       */
1260      protected static function validate_attempt_review($params) {
1261  
1262          $attemptobj = quiz_attempt::create($params['attemptid']);
1263          $attemptobj->check_review_capability();
1264  
1265          $displayoptions = $attemptobj->get_display_options(true);
1266          if ($attemptobj->is_own_attempt()) {
1267              if (!$attemptobj->is_finished()) {
1268                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptclosed');
1269              } else if (!$displayoptions->attempt) {
1270                  throw new moodle_exception($attemptobj->cannot_review_message());
1271              }
1272          } else if (!$attemptobj->is_review_allowed()) {
1273              throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreviewattempt');
1274          }
1275          return array($attemptobj, $displayoptions);
1276      }
1277  
1278      /**
1279       * Describes the parameters for get_attempt_review.
1280       *
1281       * @return external_external_function_parameters
1282       * @since Moodle 3.1
1283       */
1284      public static function get_attempt_review_parameters() {
1285          return new external_function_parameters (
1286              array(
1287                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1288                  'page' => new external_value(PARAM_INT, 'page number, empty for all the questions in all the pages',
1289                                                  VALUE_DEFAULT, -1),
1290              )
1291          );
1292      }
1293  
1294      /**
1295       * Returns review information for the given finished attempt, can be used by users or teachers.
1296       *
1297       * @param int $attemptid attempt id
1298       * @param int $page page number, empty for all the questions in all the pages
1299       * @return array of warnings and the attempt data, feedback and questions
1300       * @since Moodle 3.1
1301       * @throws  moodle_exception
1302       * @throws  moodle_quiz_exception
1303       */
1304      public static function get_attempt_review($attemptid, $page = -1) {
1305          global $PAGE;
1306  
1307          $warnings = array();
1308  
1309          $params = array(
1310              'attemptid' => $attemptid,
1311              'page' => $page,
1312          );
1313          $params = self::validate_parameters(self::get_attempt_review_parameters(), $params);
1314  
1315          list($attemptobj, $displayoptions) = self::validate_attempt_review($params);
1316  
1317          if ($params['page'] !== -1) {
1318              $page = $attemptobj->force_page_number_into_range($params['page']);
1319          } else {
1320              $page = 'all';
1321          }
1322  
1323          // Prepare the output.
1324          $result = array();
1325          $result['attempt'] = $attemptobj->get_attempt();
1326          $result['questions'] = self::get_attempt_questions_data($attemptobj, true, $page, true);
1327  
1328          $result['additionaldata'] = array();
1329          // Summary data (from behaviours).
1330          $summarydata = $attemptobj->get_additional_summary_data($displayoptions);
1331          foreach ($summarydata as $key => $data) {
1332              // This text does not need formatting (no need for external_format_[string|text]).
1333              $result['additionaldata'][] = array(
1334                  'id' => $key,
1335                  'title' => $data['title'], $attemptobj->get_quizobj()->get_context()->id,
1336                  'content' => $data['content'],
1337              );
1338          }
1339  
1340          // Feedback if there is any, and the user is allowed to see it now.
1341          $grade = quiz_rescale_grade($attemptobj->get_attempt()->sumgrades, $attemptobj->get_quiz(), false);
1342  
1343          $feedback = $attemptobj->get_overall_feedback($grade);
1344          if ($displayoptions->overallfeedback && $feedback) {
1345              $result['additionaldata'][] = array(
1346                  'id' => 'feedback',
1347                  'title' => get_string('feedback', 'quiz'),
1348                  'content' => $feedback,
1349              );
1350          }
1351  
1352          $result['grade'] = $grade;
1353          $result['warnings'] = $warnings;
1354          return $result;
1355      }
1356  
1357      /**
1358       * Describes the get_attempt_review return value.
1359       *
1360       * @return external_single_structure
1361       * @since Moodle 3.1
1362       */
1363      public static function get_attempt_review_returns() {
1364          return new external_single_structure(
1365              array(
1366                  'grade' => new external_value(PARAM_RAW, 'grade for the quiz (or empty or "notyetgraded")'),
1367                  'attempt' => self::attempt_structure(),
1368                  'additionaldata' => new external_multiple_structure(
1369                      new external_single_structure(
1370                          array(
1371                              'id' => new external_value(PARAM_ALPHANUMEXT, 'id of the data'),
1372                              'title' => new external_value(PARAM_TEXT, 'data title'),
1373                              'content' => new external_value(PARAM_RAW, 'data content'),
1374                          )
1375                      )
1376                  ),
1377                  'questions' => new external_multiple_structure(self::question_structure()),
1378                  'warnings' => new external_warnings(),
1379              )
1380          );
1381      }
1382  
1383      /**
1384       * Describes the parameters for view_attempt.
1385       *
1386       * @return external_external_function_parameters
1387       * @since Moodle 3.1
1388       */
1389      public static function view_attempt_parameters() {
1390          return new external_function_parameters (
1391              array(
1392                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1393                  'page' => new external_value(PARAM_INT, 'page number'),
1394                  'preflightdata' => new external_multiple_structure(
1395                      new external_single_structure(
1396                          array(
1397                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
1398                              'value' => new external_value(PARAM_RAW, 'data value'),
1399                          )
1400                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
1401                  )
1402              )
1403          );
1404      }
1405  
1406      /**
1407       * Trigger the attempt viewed event.
1408       *
1409       * @param int $attemptid attempt id
1410       * @param int $page page number
1411       * @param array $preflightdata preflight required data (like passwords)
1412       * @return array of warnings and status result
1413       * @since Moodle 3.1
1414       */
1415      public static function view_attempt($attemptid, $page, $preflightdata = array()) {
1416  
1417          $warnings = array();
1418  
1419          $params = array(
1420              'attemptid' => $attemptid,
1421              'page' => $page,
1422              'preflightdata' => $preflightdata,
1423          );
1424          $params = self::validate_parameters(self::view_attempt_parameters(), $params);
1425          list($attemptobj, $messages) = self::validate_attempt($params);
1426  
1427          // Log action.
1428          $attemptobj->fire_attempt_viewed_event();
1429  
1430          // Update attempt page, throwing an exception if $page is not valid.
1431          if (!$attemptobj->set_currentpage($params['page'])) {
1432              throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access');
1433          }
1434  
1435          $result = array();
1436          $result['status'] = true;
1437          $result['warnings'] = $warnings;
1438          return $result;
1439      }
1440  
1441      /**
1442       * Describes the view_attempt return value.
1443       *
1444       * @return external_single_structure
1445       * @since Moodle 3.1
1446       */
1447      public static function view_attempt_returns() {
1448          return new external_single_structure(
1449              array(
1450                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1451                  'warnings' => new external_warnings(),
1452              )
1453          );
1454      }
1455  
1456      /**
1457       * Describes the parameters for view_attempt_summary.
1458       *
1459       * @return external_external_function_parameters
1460       * @since Moodle 3.1
1461       */
1462      public static function view_attempt_summary_parameters() {
1463          return new external_function_parameters (
1464              array(
1465                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1466                  'preflightdata' => new external_multiple_structure(
1467                      new external_single_structure(
1468                          array(
1469                              'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
1470                              'value' => new external_value(PARAM_RAW, 'data value'),
1471                          )
1472                      ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
1473                  )
1474              )
1475          );
1476      }
1477  
1478      /**
1479       * Trigger the attempt summary viewed event.
1480       *
1481       * @param int $attemptid attempt id
1482       * @param array $preflightdata preflight required data (like passwords)
1483       * @return array of warnings and status result
1484       * @since Moodle 3.1
1485       */
1486      public static function view_attempt_summary($attemptid, $preflightdata = array()) {
1487  
1488          $warnings = array();
1489  
1490          $params = array(
1491              'attemptid' => $attemptid,
1492              'preflightdata' => $preflightdata,
1493          );
1494          $params = self::validate_parameters(self::view_attempt_summary_parameters(), $params);
1495          list($attemptobj, $messages) = self::validate_attempt($params);
1496  
1497          // Log action.
1498          $attemptobj->fire_attempt_summary_viewed_event();
1499  
1500          $result = array();
1501          $result['status'] = true;
1502          $result['warnings'] = $warnings;
1503          return $result;
1504      }
1505  
1506      /**
1507       * Describes the view_attempt_summary return value.
1508       *
1509       * @return external_single_structure
1510       * @since Moodle 3.1
1511       */
1512      public static function view_attempt_summary_returns() {
1513          return new external_single_structure(
1514              array(
1515                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1516                  'warnings' => new external_warnings(),
1517              )
1518          );
1519      }
1520  
1521      /**
1522       * Describes the parameters for view_attempt_review.
1523       *
1524       * @return external_external_function_parameters
1525       * @since Moodle 3.1
1526       */
1527      public static function view_attempt_review_parameters() {
1528          return new external_function_parameters (
1529              array(
1530                  'attemptid' => new external_value(PARAM_INT, 'attempt id'),
1531              )
1532          );
1533      }
1534  
1535      /**
1536       * Trigger the attempt reviewed event.
1537       *
1538       * @param int $attemptid attempt id
1539       * @return array of warnings and status result
1540       * @since Moodle 3.1
1541       */
1542      public static function view_attempt_review($attemptid) {
1543  
1544          $warnings = array();
1545  
1546          $params = array(
1547              'attemptid' => $attemptid,
1548          );
1549          $params = self::validate_parameters(self::view_attempt_review_parameters(), $params);
1550          list($attemptobj, $displayoptions) = self::validate_attempt_review($params);
1551  
1552          // Log action.
1553          $attemptobj->fire_attempt_reviewed_event();
1554  
1555          $result = array();
1556          $result['status'] = true;
1557          $result['warnings'] = $warnings;
1558          return $result;
1559      }
1560  
1561      /**
1562       * Describes the view_attempt_review return value.
1563       *
1564       * @return external_single_structure
1565       * @since Moodle 3.1
1566       */
1567      public static function view_attempt_review_returns() {
1568          return new external_single_structure(
1569              array(
1570                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1571                  'warnings' => new external_warnings(),
1572              )
1573          );
1574      }
1575  
1576      /**
1577       * Describes the parameters for view_quiz.
1578       *
1579       * @return external_external_function_parameters
1580       * @since Moodle 3.1
1581       */
1582      public static function get_quiz_feedback_for_grade_parameters() {
1583          return new external_function_parameters (
1584              array(
1585                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
1586                  'grade' => new external_value(PARAM_FLOAT, 'the grade to check'),
1587              )
1588          );
1589      }
1590  
1591      /**
1592       * Get the feedback text that should be show to a student who got the given grade in the given quiz.
1593       *
1594       * @param int $quizid quiz instance id
1595       * @param float $grade the grade to check
1596       * @return array of warnings and status result
1597       * @since Moodle 3.1
1598       * @throws moodle_exception
1599       */
1600      public static function get_quiz_feedback_for_grade($quizid, $grade) {
1601          global $DB;
1602  
1603          $params = array(
1604              'quizid' => $quizid,
1605              'grade' => $grade,
1606          );
1607          $params = self::validate_parameters(self::get_quiz_feedback_for_grade_parameters(), $params);
1608          $warnings = array();
1609  
1610          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
1611  
1612          $result = array();
1613          $result['feedbacktext'] = '';
1614          $result['feedbacktextformat'] = FORMAT_MOODLE;
1615  
1616          $feedback = quiz_feedback_record_for_grade($params['grade'], $quiz);
1617          if (!empty($feedback->feedbacktext)) {
1618              list($text, $format) = external_format_text($feedback->feedbacktext, $feedback->feedbacktextformat, $context->id,
1619                                                          'mod_quiz', 'feedback', $feedback->id);
1620              $result['feedbacktext'] = $text;
1621              $result['feedbacktextformat'] = $format;
1622          }
1623  
1624          $result['warnings'] = $warnings;
1625          return $result;
1626      }
1627  
1628      /**
1629       * Describes the get_quiz_feedback_for_grade return value.
1630       *
1631       * @return external_single_structure
1632       * @since Moodle 3.1
1633       */
1634      public static function get_quiz_feedback_for_grade_returns() {
1635          return new external_single_structure(
1636              array(
1637                  'feedbacktext' => new external_value(PARAM_RAW, 'the comment that corresponds to this grade (empty for none)'),
1638                  'feedbacktextformat' => new external_format_value('feedbacktext', VALUE_OPTIONAL),
1639                  'warnings' => new external_warnings(),
1640              )
1641          );
1642      }
1643  
1644      /**
1645       * Describes the parameters for get_quiz_access_information.
1646       *
1647       * @return external_external_function_parameters
1648       * @since Moodle 3.1
1649       */
1650      public static function get_quiz_access_information_parameters() {
1651          return new external_function_parameters (
1652              array(
1653                  'quizid' => new external_value(PARAM_INT, 'quiz instance id')
1654              )
1655          );
1656      }
1657  
1658      /**
1659       * Return access information for a given quiz.
1660       *
1661       * @param int $quizid quiz instance id
1662       * @return array of warnings and the access information
1663       * @since Moodle 3.1
1664       * @throws  moodle_quiz_exception
1665       */
1666      public static function get_quiz_access_information($quizid) {
1667          global $DB, $USER;
1668  
1669          $warnings = array();
1670  
1671          $params = array(
1672              'quizid' => $quizid
1673          );
1674          $params = self::validate_parameters(self::get_quiz_access_information_parameters(), $params);
1675  
1676          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
1677  
1678          $result = array();
1679          // Capabilities first.
1680          $result['canattempt'] = has_capability('mod/quiz:attempt', $context);;
1681          $result['canmanage'] = has_capability('mod/quiz:manage', $context);;
1682          $result['canpreview'] = has_capability('mod/quiz:preview', $context);;
1683          $result['canreviewmyattempts'] = has_capability('mod/quiz:reviewmyattempts', $context);;
1684          $result['canviewreports'] = has_capability('mod/quiz:viewreports', $context);;
1685  
1686          // Access manager now.
1687          $quizobj = quiz::create($cm->instance, $USER->id);
1688          $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false);
1689          $timenow = time();
1690          $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits);
1691  
1692          $result['accessrules'] = $accessmanager->describe_rules();
1693          $result['activerulenames'] = $accessmanager->get_active_rule_names();
1694          $result['preventaccessreasons'] = $accessmanager->prevent_access();
1695  
1696          $result['warnings'] = $warnings;
1697          return $result;
1698      }
1699  
1700      /**
1701       * Describes the get_quiz_access_information return value.
1702       *
1703       * @return external_single_structure
1704       * @since Moodle 3.1
1705       */
1706      public static function get_quiz_access_information_returns() {
1707          return new external_single_structure(
1708              array(
1709                  'canattempt' => new external_value(PARAM_BOOL, 'Whether the user can do the quiz or not.'),
1710                  'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can edit the quiz settings or not.'),
1711                  'canpreview' => new external_value(PARAM_BOOL, 'Whether the user can preview the quiz or not.'),
1712                  'canreviewmyattempts' => new external_value(PARAM_BOOL, 'Whether the users can review their previous attempts
1713                                                                  or not.'),
1714                  'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the quiz reports or not.'),
1715                  'accessrules' => new external_multiple_structure(
1716                                      new external_value(PARAM_TEXT, 'rule description'), 'list of rules'),
1717                  'activerulenames' => new external_multiple_structure(
1718                                      new external_value(PARAM_PLUGIN, 'rule plugin names'), 'list of active rules'),
1719                  'preventaccessreasons' => new external_multiple_structure(
1720                                              new external_value(PARAM_TEXT, 'access restriction description'), 'list of reasons'),
1721                  'warnings' => new external_warnings(),
1722              )
1723          );
1724      }
1725  
1726      /**
1727       * Describes the parameters for get_attempt_access_information.
1728       *
1729       * @return external_external_function_parameters
1730       * @since Moodle 3.1
1731       */
1732      public static function get_attempt_access_information_parameters() {
1733          return new external_function_parameters (
1734              array(
1735                  'quizid' => new external_value(PARAM_INT, 'quiz instance id'),
1736                  'attemptid' => new external_value(PARAM_INT, 'attempt id, 0 for the user last attempt if exists', VALUE_DEFAULT, 0),
1737              )
1738          );
1739      }
1740  
1741      /**
1742       * Return access information for a given attempt in a quiz.
1743       *
1744       * @param int $quizid quiz instance id
1745       * @param int $attemptid attempt id, 0 for the user last attempt if exists
1746       * @return array of warnings and the access information
1747       * @since Moodle 3.1
1748       * @throws  moodle_quiz_exception
1749       */
1750      public static function get_attempt_access_information($quizid, $attemptid = 0) {
1751          global $DB, $USER;
1752  
1753          $warnings = array();
1754  
1755          $params = array(
1756              'quizid' => $quizid,
1757              'attemptid' => $attemptid,
1758          );
1759          $params = self::validate_parameters(self::get_attempt_access_information_parameters(), $params);
1760  
1761          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
1762  
1763          $attempttocheck = 0;
1764          if (!empty($params['attemptid'])) {
1765              $attemptobj = quiz_attempt::create($params['attemptid']);
1766              if ($attemptobj->get_userid() != $USER->id) {
1767                  throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
1768              }
1769              $attempttocheck = $attemptobj->get_attempt();
1770          }
1771  
1772          // Access manager now.
1773          $quizobj = quiz::create($cm->instance, $USER->id);
1774          $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false);
1775          $timenow = time();
1776          $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits);
1777  
1778          $attempts = quiz_get_user_attempts($quiz->id, $USER->id, 'finished', true);
1779          $lastfinishedattempt = end($attempts);
1780          if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
1781              $attempts[] = $unfinishedattempt;
1782  
1783              // Check if the attempt is now overdue. In that case the state will change.
1784              $quizobj->create_attempt_object($unfinishedattempt)->handle_if_time_expired(time(), false);
1785  
1786              if ($unfinishedattempt->state != quiz_attempt::IN_PROGRESS and $unfinishedattempt->state != quiz_attempt::OVERDUE) {
1787                  $lastfinishedattempt = $unfinishedattempt;
1788              }
1789          }
1790          $numattempts = count($attempts);
1791  
1792          if (!$attempttocheck) {
1793              $attempttocheck = $unfinishedattempt ? $unfinishedattempt : $lastfinishedattempt;
1794          }
1795  
1796          $result = array();
1797          $result['isfinished'] = $accessmanager->is_finished($numattempts, $lastfinishedattempt);
1798          $result['preventnewattemptreasons'] = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt);
1799  
1800          if ($attempttocheck) {
1801              $endtime = $accessmanager->get_end_time($attempttocheck);
1802              $result['endtime'] = ($endtime === false) ? 0 : $endtime;
1803              $attemptid = $unfinishedattempt ? $unfinishedattempt->id : null;
1804              $result['ispreflightcheckrequired'] = $accessmanager->is_preflight_check_required($attemptid);
1805          }
1806  
1807          $result['warnings'] = $warnings;
1808          return $result;
1809      }
1810  
1811      /**
1812       * Describes the get_attempt_access_information return value.
1813       *
1814       * @return external_single_structure
1815       * @since Moodle 3.1
1816       */
1817      public static function get_attempt_access_information_returns() {
1818          return new external_single_structure(
1819              array(
1820                  'endtime' => new external_value(PARAM_INT, 'When the attempt must be submitted (determined by rules).',
1821                                                  VALUE_OPTIONAL),
1822                  'isfinished' => new external_value(PARAM_BOOL, 'Whether there is no way the user will ever be allowed to attempt.'),
1823                  'ispreflightcheckrequired' => new external_value(PARAM_BOOL, 'whether a check is required before the user
1824                                                                      starts/continues his attempt.', VALUE_OPTIONAL),
1825                  'preventnewattemptreasons' => new external_multiple_structure(
1826                                                  new external_value(PARAM_TEXT, 'access restriction description'),
1827                                                                      'list of reasons'),
1828                  'warnings' => new external_warnings(),
1829              )
1830          );
1831      }
1832  
1833      /**
1834       * Describes the parameters for get_quiz_required_qtypes.
1835       *
1836       * @return external_external_function_parameters
1837       * @since Moodle 3.1
1838       */
1839      public static function get_quiz_required_qtypes_parameters() {
1840          return new external_function_parameters (
1841              array(
1842                  'quizid' => new external_value(PARAM_INT, 'quiz instance id')
1843              )
1844          );
1845      }
1846  
1847      /**
1848       * Return the potential question types that would be required for a given quiz.
1849       * Please note that for random question types we return the potential question types in the category choosen.
1850       *
1851       * @param int $quizid quiz instance id
1852       * @return array of warnings and the access information
1853       * @since Moodle 3.1
1854       * @throws  moodle_quiz_exception
1855       */
1856      public static function get_quiz_required_qtypes($quizid) {
1857          global $DB, $USER;
1858  
1859          $warnings = array();
1860  
1861          $params = array(
1862              'quizid' => $quizid
1863          );
1864          $params = self::validate_parameters(self::get_quiz_required_qtypes_parameters(), $params);
1865  
1866          list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']);
1867  
1868          $quizobj = quiz::create($cm->instance, $USER->id);
1869          $quizobj->preload_questions();
1870          $quizobj->load_questions();
1871  
1872          // Question types used.
1873          $result = array();
1874          $result['questiontypes'] = $quizobj->get_all_question_types_used(true);
1875          $result['warnings'] = $warnings;
1876          return $result;
1877      }
1878  
1879      /**
1880       * Describes the get_quiz_required_qtypes return value.
1881       *
1882       * @return external_single_structure
1883       * @since Moodle 3.1
1884       */
1885      public static function get_quiz_required_qtypes_returns() {
1886          return new external_single_structure(
1887              array(
1888                  'questiontypes' => new external_multiple_structure(
1889                                      new external_value(PARAM_PLUGIN, 'question type'), 'list of question types used in the quiz'),
1890                  'warnings' => new external_warnings(),
1891              )
1892          );
1893      }
1894  
1895  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1