Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install
[moodle.git] / mod / lesson / classes / external.php
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/>.
17 /**
18  * Lesson external API
19  *
20  * @package    mod_lesson
21  * @category   external
22  * @copyright  2017 Juan Leyva <juan@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  * @since      Moodle 3.3
25  */
27 defined('MOODLE_INTERNAL') || die;
29 require_once($CFG->libdir . '/externallib.php');
30 require_once($CFG->dirroot . '/mod/lesson/locallib.php');
32 use mod_lesson\external\lesson_summary_exporter;
34 /**
35  * Lesson external functions
36  *
37  * @package    mod_lesson
38  * @category   external
39  * @copyright  2017 Juan Leyva <juan@moodle.com>
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  * @since      Moodle 3.3
42  */
43 class mod_lesson_external extends external_api {
45     /**
46      * Return a lesson record ready for being exported.
47      *
48      * @param  stdClass $lessonrecord lesson record
49      * @param  string $password       lesson password
50      * @return stdClass the lesson record ready for exporting.
51      */
52     protected static function get_lesson_summary_for_exporter($lessonrecord, $password = '') {
53         global $USER;
55         $lesson = new lesson($lessonrecord);
56         $lesson->update_effective_access($USER->id);
57         $lessonavailable = $lesson->get_time_restriction_status() === false;
58         $lessonavailable = $lessonavailable && $lesson->get_password_restriction_status($password) === false;
59         $lessonavailable = $lessonavailable && $lesson->get_dependencies_restriction_status() === false;
60         $canmanage = $lesson->can_manage();
62         if (!$canmanage && !$lessonavailable) {
63             $fields = array('intro', 'introfiles', 'mediafiles', 'practice', 'modattempts', 'usepassword',
64                 'grade', 'custom', 'ongoing', 'usemaxgrade',
65                 'maxanswers', 'maxattempts', 'review', 'nextpagedefault', 'feedback', 'minquestions',
66                 'maxpages', 'timelimit', 'retake', 'mediafile', 'mediaheight', 'mediawidth',
67                 'mediaclose', 'slideshow', 'width', 'height', 'bgcolor', 'displayleft', 'displayleftif',
68                 'progressbar', 'allowofflineattempts');
70             foreach ($fields as $field) {
71                 unset($lessonrecord->{$field});
72             }
73         }
75         // Fields only for managers.
76         if (!$canmanage) {
77             $fields = array('password', 'dependency', 'conditions', 'activitylink', 'available', 'deadline',
78                             'timemodified', 'completionendreached', 'completiontimespent');
80             foreach ($fields as $field) {
81                 unset($lessonrecord->{$field});
82             }
83         }
84         return $lessonrecord;
85     }
87     /**
88      * Describes the parameters for get_lessons_by_courses.
89      *
90      * @return external_function_parameters
91      * @since Moodle 3.3
92      */
93     public static function get_lessons_by_courses_parameters() {
94         return new external_function_parameters (
95             array(
96                 'courseids' => new external_multiple_structure(
97                     new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
98                 ),
99             )
100         );
101     }
103     /**
104      * Returns a list of lessons in a provided list of courses,
105      * if no list is provided all lessons that the user can view will be returned.
106      *
107      * @param array $courseids Array of course ids
108      * @return array of lessons details
109      * @since Moodle 3.3
110      */
111     public static function get_lessons_by_courses($courseids = array()) {
112         global $PAGE;
114         $warnings = array();
115         $returnedlessons = array();
117         $params = array(
118             'courseids' => $courseids,
119         );
120         $params = self::validate_parameters(self::get_lessons_by_courses_parameters(), $params);
122         $mycourses = array();
123         if (empty($params['courseids'])) {
124             $mycourses = enrol_get_my_courses();
125             $params['courseids'] = array_keys($mycourses);
126         }
128         // Ensure there are courseids to loop through.
129         if (!empty($params['courseids'])) {
131             list($courses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses);
133             // Get the lessons in this course, this function checks users visibility permissions.
134             // We can avoid then additional validate_context calls.
135             $lessons = get_all_instances_in_courses("lesson", $courses);
136             foreach ($lessons as $lessonrecord) {
137                 $context = context_module::instance($lessonrecord->coursemodule);
139                 // Remove fields added by get_all_instances_in_courses.
140                 unset($lessonrecord->coursemodule, $lessonrecord->section, $lessonrecord->visible, $lessonrecord->groupmode,
141                     $lessonrecord->groupingid);
143                 $lessonrecord = self::get_lesson_summary_for_exporter($lessonrecord);
145                 $exporter = new lesson_summary_exporter($lessonrecord, array('context' => $context));
146                 $returnedlessons[] = $exporter->export($PAGE->get_renderer('core'));
147             }
148         }
149         $result = array();
150         $result['lessons'] = $returnedlessons;
151         $result['warnings'] = $warnings;
152         return $result;
153     }
155     /**
156      * Describes the get_lessons_by_courses return value.
157      *
158      * @return external_single_structure
159      * @since Moodle 3.3
160      */
161     public static function get_lessons_by_courses_returns() {
162         return new external_single_structure(
163             array(
164                 'lessons' => new external_multiple_structure(
165                     lesson_summary_exporter::get_read_structure()
166                 ),
167                 'warnings' => new external_warnings(),
168             )
169         );
170     }
172     /**
173      * Utility function for validating a lesson.
174      *
175      * @param int $lessonid lesson instance id
176      * @return array array containing the lesson, course, context and course module objects
177      * @since  Moodle 3.3
178      */
179     protected static function validate_lesson($lessonid) {
180         global $DB, $USER;
182         // Request and permission validation.
183         $lessonrecord = $DB->get_record('lesson', array('id' => $lessonid), '*', MUST_EXIST);
184         list($course, $cm) = get_course_and_cm_from_instance($lessonrecord, 'lesson');
186         $lesson = new lesson($lessonrecord, $cm, $course);
187         $lesson->update_effective_access($USER->id);
189         $context = $lesson->context;
190         self::validate_context($context);
192         return array($lesson, $course, $cm, $context, $lessonrecord);
193     }
195     /**
196      * Validates a new attempt.
197      *
198      * @param  lesson  $lesson lesson instance
199      * @param  array   $params request parameters
200      * @param  boolean $return whether to return the errors or throw exceptions
201      * @return array          the errors (if return set to true)
202      * @since  Moodle 3.3
203      */
204     protected static function validate_attempt(lesson $lesson, $params, $return = false) {
205         global $USER, $CFG;
207         $errors = array();
209         // Avoid checkings for managers.
210         if ($lesson->can_manage()) {
211             return [];
212         }
214         // Dead line.
215         if ($timerestriction = $lesson->get_time_restriction_status()) {
216             $error = ["$timerestriction->reason" => userdate($timerestriction->time)];
217             if (!$return) {
218                 throw new moodle_exception(key($error), 'lesson', '', current($error));
219             }
220             $errors[key($error)] = current($error);
221         }
223         // Password protected lesson code.
224         if ($passwordrestriction = $lesson->get_password_restriction_status($params['password'])) {
225             $error = ["passwordprotectedlesson" => external_format_string($lesson->name, $lesson->context->id)];
226             if (!$return) {
227                 throw new moodle_exception(key($error), 'lesson', '', current($error));
228             }
229             $errors[key($error)] = current($error);
230         }
232         // Check for dependencies.
233         if ($dependenciesrestriction = $lesson->get_dependencies_restriction_status()) {
234             $errorhtmllist = implode(get_string('and', 'lesson') . ', ', $dependenciesrestriction->errors);
235             $error = ["completethefollowingconditions" => $dependenciesrestriction->dependentlesson->name . $errorhtmllist];
236             if (!$return) {
237                 throw new moodle_exception(key($error), 'lesson', '', current($error));
238             }
239             $errors[key($error)] = current($error);
240         }
242         // To check only when no page is set (starting or continuing a lesson).
243         if (empty($params['pageid'])) {
244             // To avoid multiple calls, store the magic property firstpage.
245             $lessonfirstpage = $lesson->firstpage;
246             $lessonfirstpageid = $lessonfirstpage ? $lessonfirstpage->id : false;
248             // Check if the lesson does not have pages.
249             if (!$lessonfirstpageid) {
250                 $error = ["lessonnotready2" => null];
251                 if (!$return) {
252                     throw new moodle_exception(key($error), 'lesson');
253                 }
254                 $errors[key($error)] = current($error);
255             }
257             // Get the number of retries (also referenced as attempts), and the last page seen.
258             $attemptscount = $lesson->count_user_retries($USER->id);
259             $lastpageseen = $lesson->get_last_page_seen($attemptscount);
261             // Check if the user left a timed session with no retakes.
262             if ($lastpageseen !== false && $lastpageseen != LESSON_EOL) {
263                 if ($lesson->left_during_timed_session($attemptscount) && $lesson->timelimit && !$lesson->retake) {
264                     $error = ["leftduringtimednoretake" => null];
265                     if (!$return) {
266                         throw new moodle_exception(key($error), 'lesson');
267                     }
268                     $errors[key($error)] = current($error);
269                 }
270             } else if ($attemptscount > 0 && !$lesson->retake) {
271                 // The user finished the lesson and no retakes are allowed.
272                 $error = ["noretake" => null];
273                 if (!$return) {
274                     throw new moodle_exception(key($error), 'lesson');
275                 }
276                 $errors[key($error)] = current($error);
277             }
278         } else {
279             if (!$timers = $lesson->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1)) {
280                 $error = ["cannotfindtimer" => null];
281                 if (!$return) {
282                     throw new moodle_exception(key($error), 'lesson');
283                 }
284                 $errors[key($error)] = current($error);
285             } else {
286                 $timer = current($timers);
287                 if (!$lesson->check_time($timer)) {
288                     $error = ["eolstudentoutoftime" => null];
289                     if (!$return) {
290                         throw new moodle_exception(key($error), 'lesson');
291                     }
292                     $errors[key($error)] = current($error);
293                 }
295                 // Check if the user want to review an attempt he just finished.
296                 if (!empty($params['review'])) {
297                     // Allow review only for completed attempts during active session time.
298                     if ($timer->completed and ($timer->lessontime + $CFG->sessiontimeout > time()) ) {
299                         $ntries = $lesson->count_user_retries($USER->id);
300                         if ($attempts = $lesson->get_attempts($ntries)) {
301                             $lastattempt = end($attempts);
302                             $USER->modattempts[$lesson->id] = $lastattempt->pageid;
303                         }
304                     }
306                     if (!isset($USER->modattempts[$lesson->id])) {
307                         $error = ["studentoutoftimeforreview" => null];
308                         if (!$return) {
309                             throw new moodle_exception(key($error), 'lesson');
310                         }
311                         $errors[key($error)] = current($error);
312                     }
313                 }
314             }
315         }
317         return $errors;
318     }
320     /**
321      * Describes the parameters for get_lesson_access_information.
322      *
323      * @return external_external_function_parameters
324      * @since Moodle 3.3
325      */
326     public static function get_lesson_access_information_parameters() {
327         return new external_function_parameters (
328             array(
329                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id')
330             )
331         );
332     }
334     /**
335      * Return access information for a given lesson.
336      *
337      * @param int $lessonid lesson instance id
338      * @return array of warnings and the access information
339      * @since Moodle 3.3
340      * @throws  moodle_exception
341      */
342     public static function get_lesson_access_information($lessonid) {
343         global $DB, $USER;
345         $warnings = array();
347         $params = array(
348             'lessonid' => $lessonid
349         );
350         $params = self::validate_parameters(self::get_lesson_access_information_parameters(), $params);
352         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
354         $result = array();
355         // Capabilities first.
356         $result['canmanage'] = $lesson->can_manage();
357         $result['cangrade'] = has_capability('mod/lesson:grade', $context);
358         $result['canviewreports'] = has_capability('mod/lesson:viewreports', $context);
360         // Status information.
361         $result['reviewmode'] = $lesson->is_in_review_mode();
362         $result['attemptscount'] = $lesson->count_user_retries($USER->id);
363         $lastpageseen = $lesson->get_last_page_seen($result['attemptscount']);
364         $result['lastpageseen'] = ($lastpageseen !== false) ? $lastpageseen : 0;
365         $result['leftduringtimedsession'] = $lesson->left_during_timed_session($result['attemptscount']);
366         // To avoid multiple calls, store the magic property firstpage.
367         $lessonfirstpage = $lesson->firstpage;
368         $result['firstpageid'] = $lessonfirstpage ? $lessonfirstpage->id : 0;
370         // Access restrictions now, we emulate a new attempt access to get the possible warnings.
371         $result['preventaccessreasons'] = [];
372         $validationerrors = self::validate_attempt($lesson, ['password' => ''], true);
373         foreach ($validationerrors as $reason => $data) {
374             $result['preventaccessreasons'][] = [
375                 'reason' => $reason,
376                 'data' => $data,
377                 'message' => get_string($reason, 'lesson', $data),
378             ];
379         }
380         $result['warnings'] = $warnings;
381         return $result;
382     }
384     /**
385      * Describes the get_lesson_access_information return value.
386      *
387      * @return external_single_structure
388      * @since Moodle 3.3
389      */
390     public static function get_lesson_access_information_returns() {
391         return new external_single_structure(
392             array(
393                 'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can manage the lesson or not.'),
394                 'cangrade' => new external_value(PARAM_BOOL, 'Whether the user can grade the lesson or not.'),
395                 'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the lesson reports or not.'),
396                 'reviewmode' => new external_value(PARAM_BOOL, 'Whether the lesson is in review mode for the current user.'),
397                 'attemptscount' => new external_value(PARAM_INT, 'The number of attempts done by the user.'),
398                 'lastpageseen' => new external_value(PARAM_INT, 'The last page seen id.'),
399                 'leftduringtimedsession' => new external_value(PARAM_BOOL, 'Whether the user left during a timed session.'),
400                 'firstpageid' => new external_value(PARAM_INT, 'The lesson first page id.'),
401                 'preventaccessreasons' => new external_multiple_structure(
402                     new external_single_structure(
403                         array(
404                             'reason' => new external_value(PARAM_ALPHANUMEXT, 'Reason lang string code'),
405                             'data' => new external_value(PARAM_RAW, 'Additional data'),
406                             'message' => new external_value(PARAM_RAW, 'Complete html message'),
407                         ),
408                         'The reasons why the user cannot attempt the lesson'
409                     )
410                 ),
411                 'warnings' => new external_warnings(),
412             )
413         );
414     }
416     /**
417      * Describes the parameters for view_lesson.
418      *
419      * @return external_external_function_parameters
420      * @since Moodle 3.3
421      */
422     public static function view_lesson_parameters() {
423         return new external_function_parameters (
424             array(
425                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
426                 'password' => new external_value(PARAM_RAW, 'lesson password', VALUE_DEFAULT, ''),
427             )
428         );
429     }
431     /**
432      * Trigger the course module viewed event and update the module completion status.
433      *
434      * @param int $lessonid lesson instance id
435      * @param str $password optional password (the lesson may be protected)
436      * @return array of warnings and status result
437      * @since Moodle 3.3
438      * @throws moodle_exception
439      */
440     public static function view_lesson($lessonid, $password = '') {
441         global $DB;
443         $params = array('lessonid' => $lessonid, 'password' => $password);
444         $params = self::validate_parameters(self::view_lesson_parameters(), $params);
445         $warnings = array();
447         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
448         self::validate_attempt($lesson, $params);
450         $lesson->set_module_viewed();
452         $result = array();
453         $result['status'] = true;
454         $result['warnings'] = $warnings;
455         return $result;
456     }
458     /**
459      * Describes the view_lesson return value.
460      *
461      * @return external_single_structure
462      * @since Moodle 3.3
463      */
464     public static function view_lesson_returns() {
465         return new external_single_structure(
466             array(
467                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
468                 'warnings' => new external_warnings(),
469             )
470         );
471     }
473     /**
474      * Check if the current user can retrieve lesson information (grades, attempts) about the given user.
475      *
476      * @param int $userid the user to check
477      * @param stdClass $course course object
478      * @param stdClass $cm cm object
479      * @param stdClass $context context object
480      * @throws moodle_exception
481      * @since Moodle 3.3
482      */
483     protected static function check_can_view_user_data($userid, $course, $cm, $context) {
484         $user = core_user::get_user($userid, '*', MUST_EXIST);
485         core_user::require_active_user($user);
486         // Check permissions and that if users share group (if groups enabled).
487         require_capability('mod/lesson:viewreports', $context);
488         if (!groups_user_groups_visible($course, $user->id, $cm)) {
489             throw new moodle_exception('notingroup');
490         }
491     }
493     /**
494      * Describes the parameters for get_questions_attempts.
495      *
496      * @return external_external_function_parameters
497      * @since Moodle 3.3
498      */
499     public static function get_questions_attempts_parameters() {
500         return new external_function_parameters (
501             array(
502                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
503                 'attempt' => new external_value(PARAM_INT, 'lesson attempt number'),
504                 'correct' => new external_value(PARAM_BOOL, 'only fetch correct attempts', VALUE_DEFAULT, false),
505                 'pageid' => new external_value(PARAM_INT, 'only fetch attempts at the given page', VALUE_DEFAULT, null),
506                 'userid' => new external_value(PARAM_INT, 'only fetch attempts of the given user', VALUE_DEFAULT, null),
507             )
508         );
509     }
511     /**
512      * Return the list of page question attempts in a given lesson.
513      *
514      * @param int $lessonid lesson instance id
515      * @param int $attempt the lesson attempt number
516      * @param bool $correct only fetch correct attempts
517      * @param int $pageid only fetch attempts at the given page
518      * @param int $userid only fetch attempts of the given user
519      * @return array of warnings and page attempts
520      * @since Moodle 3.3
521      * @throws moodle_exception
522      */
523     public static function get_questions_attempts($lessonid, $attempt, $correct = false, $pageid = null, $userid = null) {
524         global $DB, $USER;
526         $params = array(
527             'lessonid' => $lessonid,
528             'attempt' => $attempt,
529             'correct' => $correct,
530             'pageid' => $pageid,
531             'userid' => $userid,
532         );
533         $params = self::validate_parameters(self::get_questions_attempts_parameters(), $params);
534         $warnings = array();
536         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
538         // Default value for userid.
539         if (empty($params['userid'])) {
540             $params['userid'] = $USER->id;
541         }
543         // Extra checks so only users with permissions can view other users attempts.
544         if ($USER->id != $params['userid']) {
545             self::check_can_view_user_data($params['userid'], $course, $cm, $context);
546         }
548         $result = array();
549         $result['attempts'] = $lesson->get_attempts($params['attempt'], $params['correct'], $params['pageid'], $params['userid']);
550         $result['warnings'] = $warnings;
551         return $result;
552     }
554     /**
555      * Describes the get_questions_attempts return value.
556      *
557      * @return external_single_structure
558      * @since Moodle 3.3
559      */
560     public static function get_questions_attempts_returns() {
561         return new external_single_structure(
562             array(
563                 'attempts' => new external_multiple_structure(
564                     new external_single_structure(
565                         array(
566                             'id' => new external_value(PARAM_INT, 'The attempt id'),
567                             'lessonid' => new external_value(PARAM_INT, 'The attempt lessonid'),
568                             'pageid' => new external_value(PARAM_INT, 'The attempt pageid'),
569                             'userid' => new external_value(PARAM_INT, 'The user who did the attempt'),
570                             'answerid' => new external_value(PARAM_INT, 'The attempt answerid'),
571                             'retry' => new external_value(PARAM_INT, 'The lesson attempt number'),
572                             'correct' => new external_value(PARAM_INT, 'If it was the correct answer'),
573                             'useranswer' => new external_value(PARAM_RAW, 'The complete user answer'),
574                             'timeseen' => new external_value(PARAM_INT, 'The time the question was seen'),
575                         ),
576                         'The question page attempts'
577                     )
578                 ),
579                 'warnings' => new external_warnings(),
580             )
581         );
582     }
584     /**
585      * Describes the parameters for get_user_grade.
586      *
587      * @return external_external_function_parameters
588      * @since Moodle 3.3
589      */
590     public static function get_user_grade_parameters() {
591         return new external_function_parameters (
592             array(
593                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
594                 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
595             )
596         );
597     }
599     /**
600      * Return the final grade in the lesson for the given user.
601      *
602      * @param int $lessonid lesson instance id
603      * @param int $userid only fetch grades of this user
604      * @return array of warnings and page attempts
605      * @since Moodle 3.3
606      * @throws moodle_exception
607      */
608     public static function get_user_grade($lessonid, $userid = null) {
609         global $CFG, $USER;
610         require_once($CFG->libdir . '/gradelib.php');
612         $params = array(
613             'lessonid' => $lessonid,
614             'userid' => $userid,
615         );
616         $params = self::validate_parameters(self::get_user_grade_parameters(), $params);
617         $warnings = array();
619         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
621         // Default value for userid.
622         if (empty($params['userid'])) {
623             $params['userid'] = $USER->id;
624         }
626         // Extra checks so only users with permissions can view other users attempts.
627         if ($USER->id != $params['userid']) {
628             self::check_can_view_user_data($params['userid'], $course, $cm, $context);
629         }
631         $grade = null;
632         $formattedgrade = null;
633         $grades = lesson_get_user_grades($lesson, $params['userid']);
634         if (!empty($grades)) {
635             $grade = $grades[$params['userid']]->rawgrade;
636             $params = array(
637                 'itemtype' => 'mod',
638                 'itemmodule' => 'lesson',
639                 'iteminstance' => $lesson->id,
640                 'courseid' => $course->id,
641                 'itemnumber' => 0
642             );
643             $gradeitem = grade_item::fetch($params);
644             $formattedgrade = grade_format_gradevalue($grade, $gradeitem);
645         }
647         $result = array();
648         $result['grade'] = $grade;
649         $result['formattedgrade'] = $formattedgrade;
650         $result['warnings'] = $warnings;
651         return $result;
652     }
654     /**
655      * Describes the get_user_grade return value.
656      *
657      * @return external_single_structure
658      * @since Moodle 3.3
659      */
660     public static function get_user_grade_returns() {
661         return new external_single_structure(
662             array(
663                 'grade' => new external_value(PARAM_FLOAT, 'The lesson final raw grade'),
664                 'formattedgrade' => new external_value(PARAM_RAW, 'The lesson final grade formatted'),
665                 'warnings' => new external_warnings(),
666             )
667         );
668     }
670     /**
671      * Describes an attempt grade structure.
672      *
673      * @param  int $required if the structure is required or optional
674      * @return external_single_structure the structure
675      * @since  Moodle 3.3
676      */
677     protected static function get_user_attempt_grade_structure($required = VALUE_REQUIRED) {
678         $data = array(
679             'nquestions' => new external_value(PARAM_INT, 'Number of questions answered'),
680             'attempts' => new external_value(PARAM_INT, 'Number of question attempts'),
681             'total' => new external_value(PARAM_FLOAT, 'Max points possible'),
682             'earned' => new external_value(PARAM_FLOAT, 'Points earned by student'),
683             'grade' => new external_value(PARAM_FLOAT, 'Calculated percentage grade'),
684             'nmanual' => new external_value(PARAM_INT, 'Number of manually graded questions'),
685             'manualpoints' => new external_value(PARAM_FLOAT, 'Point value for manually graded questions'),
686         );
687         return new external_single_structure(
688             $data, 'Attempt grade', $required
689         );
690     }
692     /**
693      * Describes the parameters for get_user_attempt_grade.
694      *
695      * @return external_external_function_parameters
696      * @since Moodle 3.3
697      */
698     public static function get_user_attempt_grade_parameters() {
699         return new external_function_parameters (
700             array(
701                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
702                 'lessonattempt' => new external_value(PARAM_INT, 'lesson attempt number'),
703                 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
704             )
705         );
706     }
708     /**
709      * Return grade information in the attempt for a given user.
710      *
711      * @param int $lessonid lesson instance id
712      * @param int $lessonattempt lesson attempt number
713      * @param int $userid only fetch attempts of the given user
714      * @return array of warnings and page attempts
715      * @since Moodle 3.3
716      * @throws moodle_exception
717      */
718     public static function get_user_attempt_grade($lessonid, $lessonattempt, $userid = null) {
719         global $CFG, $USER;
720         require_once($CFG->libdir . '/gradelib.php');
722         $params = array(
723             'lessonid' => $lessonid,
724             'lessonattempt' => $lessonattempt,
725             'userid' => $userid,
726         );
727         $params = self::validate_parameters(self::get_user_attempt_grade_parameters(), $params);
728         $warnings = array();
730         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
732         // Default value for userid.
733         if (empty($params['userid'])) {
734             $params['userid'] = $USER->id;
735         }
737         // Extra checks so only users with permissions can view other users attempts.
738         if ($USER->id != $params['userid']) {
739             self::check_can_view_user_data($params['userid'], $course, $cm, $context);
740         }
742         $result = array();
743         $result['grade'] = (array) lesson_grade($lesson, $params['lessonattempt'], $params['userid']);
744         $result['warnings'] = $warnings;
745         return $result;
746     }
748     /**
749      * Describes the get_user_attempt_grade return value.
750      *
751      * @return external_single_structure
752      * @since Moodle 3.3
753      */
754     public static function get_user_attempt_grade_returns() {
755         return new external_single_structure(
756             array(
757                 'grade' => self::get_user_attempt_grade_structure(),
758                 'warnings' => new external_warnings(),
759             )
760         );
761     }
763     /**
764      * Describes the parameters for get_content_pages_viewed.
765      *
766      * @return external_external_function_parameters
767      * @since Moodle 3.3
768      */
769     public static function get_content_pages_viewed_parameters() {
770         return new external_function_parameters (
771             array(
772                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
773                 'lessonattempt' => new external_value(PARAM_INT, 'lesson attempt number'),
774                 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
775             )
776         );
777     }
779     /**
780      * Return the list of content pages viewed by a user during a lesson attempt.
781      *
782      * @param int $lessonid lesson instance id
783      * @param int $lessonattempt lesson attempt number
784      * @param int $userid only fetch attempts of the given user
785      * @return array of warnings and page attempts
786      * @since Moodle 3.3
787      * @throws moodle_exception
788      */
789     public static function get_content_pages_viewed($lessonid, $lessonattempt, $userid = null) {
790         global $USER;
792         $params = array(
793             'lessonid' => $lessonid,
794             'lessonattempt' => $lessonattempt,
795             'userid' => $userid,
796         );
797         $params = self::validate_parameters(self::get_content_pages_viewed_parameters(), $params);
798         $warnings = array();
800         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
802         // Default value for userid.
803         if (empty($params['userid'])) {
804             $params['userid'] = $USER->id;
805         }
807         // Extra checks so only users with permissions can view other users attempts.
808         if ($USER->id != $params['userid']) {
809             self::check_can_view_user_data($params['userid'], $course, $cm, $context);
810         }
812         $pages = $lesson->get_content_pages_viewed($params['lessonattempt'], $params['userid']);
814         $result = array();
815         $result['pages'] = $pages;
816         $result['warnings'] = $warnings;
817         return $result;
818     }
820     /**
821      * Describes the get_content_pages_viewed return value.
822      *
823      * @return external_single_structure
824      * @since Moodle 3.3
825      */
826     public static function get_content_pages_viewed_returns() {
827         return new external_single_structure(
828             array(
829                 'pages' => new external_multiple_structure(
830                     new external_single_structure(
831                         array(
832                             'id' => new external_value(PARAM_INT, 'The attempt id.'),
833                             'lessonid' => new external_value(PARAM_INT, 'The lesson id.'),
834                             'pageid' => new external_value(PARAM_INT, 'The page id.'),
835                             'userid' => new external_value(PARAM_INT, 'The user who viewed the page.'),
836                             'retry' => new external_value(PARAM_INT, 'The lesson attempt number.'),
837                             'flag' => new external_value(PARAM_INT, '1 if the next page was calculated randomly.'),
838                             'timeseen' => new external_value(PARAM_INT, 'The time the page was seen.'),
839                             'nextpageid' => new external_value(PARAM_INT, 'The next page chosen id.'),
840                         ),
841                         'The content pages viewed.'
842                     )
843                 ),
844                 'warnings' => new external_warnings(),
845             )
846         );
847     }
849     /**
850      * Describes the parameters for get_user_timers.
851      *
852      * @return external_external_function_parameters
853      * @since Moodle 3.3
854      */
855     public static function get_user_timers_parameters() {
856         return new external_function_parameters (
857             array(
858                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
859                 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
860             )
861         );
862     }
864     /**
865      * Return the timers in the current lesson for the given user.
866      *
867      * @param int $lessonid lesson instance id
868      * @param int $userid only fetch timers of the given user
869      * @return array of warnings and timers
870      * @since Moodle 3.3
871      * @throws moodle_exception
872      */
873     public static function get_user_timers($lessonid, $userid = null) {
874         global $USER;
876         $params = array(
877             'lessonid' => $lessonid,
878             'userid' => $userid,
879         );
880         $params = self::validate_parameters(self::get_user_timers_parameters(), $params);
881         $warnings = array();
883         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
885         // Default value for userid.
886         if (empty($params['userid'])) {
887             $params['userid'] = $USER->id;
888         }
890         // Extra checks so only users with permissions can view other users attempts.
891         if ($USER->id != $params['userid']) {
892             self::check_can_view_user_data($params['userid'], $course, $cm, $context);
893         }
895         $timers = $lesson->get_user_timers($params['userid']);
897         $result = array();
898         $result['timers'] = $timers;
899         $result['warnings'] = $warnings;
900         return $result;
901     }
903     /**
904      * Describes the get_user_timers return value.
905      *
906      * @return external_single_structure
907      * @since Moodle 3.3
908      */
909     public static function get_user_timers_returns() {
910         return new external_single_structure(
911             array(
912                 'timers' => new external_multiple_structure(
913                     new external_single_structure(
914                         array(
915                             'id' => new external_value(PARAM_INT, 'The attempt id'),
916                             'lessonid' => new external_value(PARAM_INT, 'The lesson id'),
917                             'userid' => new external_value(PARAM_INT, 'The user id'),
918                             'starttime' => new external_value(PARAM_INT, 'First access time for a new timer session'),
919                             'lessontime' => new external_value(PARAM_INT, 'Last access time to the lesson during the timer session'),
920                             'completed' => new external_value(PARAM_INT, 'If the lesson for this timer was completed'),
921                             'timemodifiedoffline' => new external_value(PARAM_INT, 'Last modified time via webservices.'),
922                         ),
923                         'The timers'
924                     )
925                 ),
926                 'warnings' => new external_warnings(),
927             )
928         );
929     }
931     /**
932      * Describes the external structure for a lesson page.
933      *
934      * @return external_single_structure
935      * @since Moodle 3.3
936      */
937     protected static function get_page_structure() {
938         return new external_single_structure(
939             array(
940                 'id' => new external_value(PARAM_INT, 'The id of this lesson page'),
941                 'lessonid' => new external_value(PARAM_INT, 'The id of the lesson this page belongs to'),
942                 'prevpageid' => new external_value(PARAM_INT, 'The id of the page before this one'),
943                 'nextpageid' => new external_value(PARAM_INT, 'The id of the next page in the page sequence'),
944                 'qtype' => new external_value(PARAM_INT, 'Identifies the page type of this page'),
945                 'qoption' => new external_value(PARAM_INT, 'Used to record page type specific options'),
946                 'layout' => new external_value(PARAM_INT, 'Used to record page specific layout selections'),
947                 'display' => new external_value(PARAM_INT, 'Used to record page specific display selections'),
948                 'timecreated' => new external_value(PARAM_INT, 'Timestamp for when the page was created'),
949                 'timemodified' => new external_value(PARAM_INT, 'Timestamp for when the page was last modified'),
950                 'title' => new external_value(PARAM_RAW, 'The title of this page', VALUE_OPTIONAL),
951                 'contents' => new external_value(PARAM_RAW, 'The contents of this page', VALUE_OPTIONAL),
952                 'contentsformat' => new external_format_value('contents', VALUE_OPTIONAL),
953                 'displayinmenublock' => new external_value(PARAM_BOOL, 'Toggles display in the left menu block'),
954                 'type' => new external_value(PARAM_INT, 'The type of the page [question | structure]'),
955                 'typeid' => new external_value(PARAM_INT, 'The unique identifier for the page type'),
956                 'typestring' => new external_value(PARAM_RAW, 'The string that describes this page type'),
957             ),
958             'Page fields'
959         );
960     }
962     /**
963      * Returns the fields of a page object
964      * @param lesson_page $page the lesson page
965      * @param bool $returncontents whether to return the page title and contents
966      * @return stdClass          the fields matching the external page structure
967      * @since Moodle 3.3
968      */
969     protected static function get_page_fields(lesson_page $page, $returncontents = false) {
970         $lesson = $page->lesson;
971         $context = $lesson->context;
973         $pagedata = new stdClass; // Contains the data that will be returned by the WS.
975         // Return the visible data.
976         $visibleproperties = array('id', 'lessonid', 'prevpageid', 'nextpageid', 'qtype', 'qoption', 'layout', 'display',
977                                     'displayinmenublock', 'type', 'typeid', 'typestring', 'timecreated', 'timemodified');
978         foreach ($visibleproperties as $prop) {
979             $pagedata->{$prop} = $page->{$prop};
980         }
982         // Check if we can see title (contents required custom rendering, we won't returning it here @see get_page_data).
983         $canmanage = $lesson->can_manage();
984         // If we are managers or the menu block is enabled and is a content page visible always return contents.
985         if ($returncontents || $canmanage || (lesson_displayleftif($lesson) && $page->displayinmenublock && $page->display)) {
986             $pagedata->title = external_format_string($page->title, $context->id);
988             list($pagedata->contents, $pagedata->contentsformat) =
989                 external_format_text($page->contents, $page->contentsformat, $context->id, 'mod_lesson', 'page_contents', $page->id);
991         }
992         return $pagedata;
993     }
995     /**
996      * Describes the parameters for get_pages.
997      *
998      * @return external_external_function_parameters
999      * @since Moodle 3.3
1000      */
1001     public static function get_pages_parameters() {
1002         return new external_function_parameters (
1003             array(
1004                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1005                 'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
1006             )
1007         );
1008     }
1010     /**
1011      * Return the list of pages in a lesson (based on the user permissions).
1012      *
1013      * @param int $lessonid lesson instance id
1014      * @param str $password optional password (the lesson may be protected)
1015      * @return array of warnings and status result
1016      * @since Moodle 3.3
1017      * @throws moodle_exception
1018      */
1019     public static function get_pages($lessonid, $password = '') {
1021         $params = array('lessonid' => $lessonid, 'password' => $password);
1022         $params = self::validate_parameters(self::get_pages_parameters(), $params);
1023         $warnings = array();
1025         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1026         self::validate_attempt($lesson, $params);
1028         $lessonpages = $lesson->load_all_pages();
1029         $pages = array();
1031         foreach ($lessonpages as $page) {
1032             $pagedata = new stdClass();
1034             // Get the page object fields.
1035             $pagedata->page = self::get_page_fields($page);
1037             // Now, calculate the file area files (maybe we need to download a lesson for offline usage).
1038             $pagedata->filescount = 0;
1039             $pagedata->filessizetotal = 0;
1040             $files = $page->get_files(false);   // Get files excluding directories.
1041             foreach ($files as $file) {
1042                 $pagedata->filescount++;
1043                 $pagedata->filessizetotal += $file->get_filesize();
1044             }
1046             // Now the possible answers and page jumps ids.
1047             $pagedata->answerids = array();
1048             $pagedata->jumps = array();
1049             $answers = $page->get_answers();
1050             foreach ($answers as $answer) {
1051                 $pagedata->answerids[] = $answer->id;
1052                 $pagedata->jumps[] = $answer->jumpto;
1053                 $files = $answer->get_files(false);   // Get files excluding directories.
1054                 foreach ($files as $file) {
1055                     $pagedata->filescount++;
1056                     $pagedata->filessizetotal += $file->get_filesize();
1057                 }
1058             }
1059             $pages[] = $pagedata;
1060         }
1062         $result = array();
1063         $result['pages'] = $pages;
1064         $result['warnings'] = $warnings;
1065         return $result;
1066     }
1068     /**
1069      * Describes the get_pages return value.
1070      *
1071      * @return external_single_structure
1072      * @since Moodle 3.3
1073      */
1074     public static function get_pages_returns() {
1075         return new external_single_structure(
1076             array(
1077                 'pages' => new external_multiple_structure(
1078                     new external_single_structure(
1079                         array(
1080                             'page' => self::get_page_structure(),
1081                             'answerids' => new external_multiple_structure(
1082                                 new external_value(PARAM_INT, 'Answer id'), 'List of answers ids (empty for content pages in  Moodle 1.9)'
1083                             ),
1084                             'jumps' => new external_multiple_structure(
1085                                 new external_value(PARAM_INT, 'Page to jump id'), 'List of possible page jumps'
1086                             ),
1087                             'filescount' => new external_value(PARAM_INT, 'The total number of files attached to the page'),
1088                             'filessizetotal' => new external_value(PARAM_INT, 'The total size of the files'),
1089                         ),
1090                         'The lesson pages'
1091                     )
1092                 ),
1093                 'warnings' => new external_warnings(),
1094             )
1095         );
1096     }
1098     /**
1099      * Describes the parameters for launch_attempt.
1100      *
1101      * @return external_external_function_parameters
1102      * @since Moodle 3.3
1103      */
1104     public static function launch_attempt_parameters() {
1105         return new external_function_parameters (
1106             array(
1107                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1108                 'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
1109                 'pageid' => new external_value(PARAM_INT, 'page id to continue from (only when continuing an attempt)', VALUE_DEFAULT, 0),
1110                 'review' => new external_value(PARAM_BOOL, 'if we want to review just after finishing', VALUE_DEFAULT, false),
1111             )
1112         );
1113     }
1115     /**
1116      * Return lesson messages formatted according the external_messages structure
1117      *
1118      * @param  lesson $lesson lesson instance
1119      * @return array          messages formatted
1120      * @since Moodle 3.3
1121      */
1122     protected static function format_lesson_messages($lesson) {
1123         $messages = array();
1124         foreach ($lesson->messages as $message) {
1125             $messages[] = array(
1126                 'message' => $message[0],
1127                 'type' => $message[1],
1128             );
1129         }
1130         return $messages;
1131     }
1133     /**
1134      * Return a external structure representing messages.
1135      *
1136      * @return external_multiple_structure messages structure
1137      * @since Moodle 3.3
1138      */
1139     protected static function external_messages() {
1140         return new external_multiple_structure(
1141             new external_single_structure(
1142                 array(
1143                     'message' => new external_value(PARAM_RAW, 'Message.'),
1144                     'type' => new external_value(PARAM_ALPHANUMEXT, 'Message type: usually a CSS identifier like:
1145                                 success, info, warning, error, notifyproblem, notifyerror, notifytiny, notifysuccess')
1146                 ), 'The lesson generated messages'
1147             )
1148         );
1149     }
1151     /**
1152      * Starts a new attempt or continues an existing one.
1153      *
1154      * @param int $lessonid lesson instance id
1155      * @param str $password optional password (the lesson may be protected)
1156      * @param int $pageid page id to continue from (only when continuing an attempt)
1157      * @param bool $review if we want to review just after finishing
1158      * @return array of warnings and status result
1159      * @since Moodle 3.3
1160      * @throws moodle_exception
1161      */
1162     public static function launch_attempt($lessonid, $password = '', $pageid = 0, $review = false) {
1163         global $CFG, $USER;
1165         $params = array('lessonid' => $lessonid, 'password' => $password, 'pageid' => $pageid, 'review' => $review);
1166         $params = self::validate_parameters(self::launch_attempt_parameters(), $params);
1167         $warnings = array();
1169         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1170         self::validate_attempt($lesson, $params);
1172         $newpageid = 0;
1173         // Starting a new lesson attempt.
1174         if (empty($params['pageid'])) {
1175             // Check if there is a recent timer created during the active session.
1176             $alreadystarted = false;
1177             if ($timers = $lesson->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1)) {
1178                 $timer = array_shift($timers);
1179                 $endtime = $lesson->timelimit > 0 ? min($CFG->sessiontimeout, $lesson->timelimit) : $CFG->sessiontimeout;
1180                 if (!$timer->completed && $timer->starttime > time() - $endtime) {
1181                     $alreadystarted = true;
1182                 }
1183             }
1184             if (!$alreadystarted && !$lesson->can_manage()) {
1185                 $lesson->start_timer();
1186             }
1187         } else {
1188             if ($params['pageid'] == LESSON_EOL) {
1189                 throw new moodle_exception('endoflesson', 'lesson');
1190             }
1191             $timer = $lesson->update_timer(true, true);
1192             if (!$lesson->check_time($timer)) {
1193                 throw new moodle_exception('eolstudentoutoftime', 'lesson');
1194             }
1195         }
1196         $messages = self::format_lesson_messages($lesson);
1198         $result = array(
1199             'status' => true,
1200             'messages' => $messages,
1201             'warnings' => $warnings,
1202         );
1203         return $result;
1204     }
1206     /**
1207      * Describes the launch_attempt return value.
1208      *
1209      * @return external_single_structure
1210      * @since Moodle 3.3
1211      */
1212     public static function launch_attempt_returns() {
1213         return new external_single_structure(
1214             array(
1215                 'messages' => self::external_messages(),
1216                 'warnings' => new external_warnings(),
1217             )
1218         );
1219     }
1221     /**
1222      * Describes the parameters for get_page_data.
1223      *
1224      * @return external_external_function_parameters
1225      * @since Moodle 3.3
1226      */
1227     public static function get_page_data_parameters() {
1228         return new external_function_parameters (
1229             array(
1230                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1231                 'pageid' => new external_value(PARAM_INT, 'the page id'),
1232                 'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
1233                 'review' => new external_value(PARAM_BOOL, 'if we want to review just after finishing (1 hour margin)',
1234                     VALUE_DEFAULT, false),
1235                 'returncontents' => new external_value(PARAM_BOOL, 'if we must return the complete page contents once rendered',
1236                     VALUE_DEFAULT, false),
1237             )
1238         );
1239     }
1241     /**
1242      * Return information of a given page, including its contents.
1243      *
1244      * @param int $lessonid lesson instance id
1245      * @param int $pageid page id
1246      * @param str $password optional password (the lesson may be protected)
1247      * @param bool $review if we want to review just after finishing (1 hour margin)
1248      * @param bool $returncontents if we must return the complete page contents once rendered
1249      * @return array of warnings and status result
1250      * @since Moodle 3.3
1251      * @throws moodle_exception
1252      */
1253     public static function get_page_data($lessonid, $pageid,  $password = '', $review = false, $returncontents = false) {
1254         global $PAGE;
1256         $params = array('lessonid' => $lessonid, 'password' => $password, 'pageid' => $pageid, 'review' => $review,
1257             'returncontents' => $returncontents);
1258         $params = self::validate_parameters(self::get_page_data_parameters(), $params);
1260         $warnings = $contentfiles = $answerfiles = $responsefiles = array();
1261         $pagecontent = $ongoingscore = '';
1262         $progress = null;
1264         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1265         self::validate_attempt($lesson, $params);
1267         $pageid = $params['pageid'];
1269         // This is called if a student leaves during a lesson.
1270         if ($pageid == LESSON_UNSEENBRANCHPAGE) {
1271             $pageid = lesson_unseen_question_jump($lesson, $USER->id, $pageid);
1272         }
1274         if ($pageid != LESSON_EOL) {
1275             $reviewmode = $lesson->is_in_review_mode();
1276             $lessonoutput = $PAGE->get_renderer('mod_lesson');
1277             list($page, $pagecontent) = $lesson->prepare_page_and_contents($pageid, $lessonoutput, $reviewmode);
1278             // Page may have changed.
1279             $pageid = $page->id;
1281             $pagedata = self::get_page_fields($page, true);
1283             // Files.
1284             $contentfiles = external_util::get_area_files($context->id, 'mod_lesson', 'page_contents', $page->id);
1286             // Answers.
1287             $answers = array();
1288             $pageanswers = $page->get_answers();
1289             foreach ($pageanswers as $a) {
1290                 $answer = array(
1291                     'id' => $a->id,
1292                     'answerfiles' => external_util::get_area_files($context->id, 'mod_lesson', 'page_answers', $a->id),
1293                     'responsefiles' => external_util::get_area_files($context->id, 'mod_lesson', 'page_responses', $a->id),
1294                 );
1295                 // For managers, return all the information (including correct answers, jumps).
1296                 // If the teacher enabled offline attempts, this information will be downloaded too.
1297                 if ($lesson->can_manage() || $lesson->allowofflineattempts) {
1298                     $extraproperties = array('jumpto', 'grade', 'score', 'flags', 'timecreated', 'timemodified');
1299                     foreach ($extraproperties as $prop) {
1300                         $answer[$prop] = $a->{$prop};
1301                     }
1302                 }
1303                 $answers[] = $answer;
1304             }
1306             // Additional lesson information.
1307             if (!$lesson->can_manage()) {
1308                 if ($lesson->ongoing && !$reviewmode) {
1309                     $ongoingscore = $lesson->get_ongoing_score_message();
1310                 }
1311                 if ($lesson->progressbar) {
1312                     $progress = $lesson->calculate_progress();
1313                 }
1314             }
1315         }
1317         $messages = self::format_lesson_messages($lesson);
1319         $result = array(
1320             'page' => $pagedata,
1321             'newpageid' => $pageid,
1322             'ongoingscore' => $ongoingscore,
1323             'progress' => $progress,
1324             'contentfiles' => $contentfiles,
1325             'answers' => $answers,
1326             'messages' => $messages,
1327             'warnings' => $warnings,
1328             'displaymenu' => !empty(lesson_displayleftif($lesson)),
1329         );
1331         if ($params['returncontents']) {
1332             $result['pagecontent'] = $pagecontent;  // Return the complete page contents rendered.
1333         }
1335         return $result;
1336     }
1338     /**
1339      * Describes the get_page_data return value.
1340      *
1341      * @return external_single_structure
1342      * @since Moodle 3.3
1343      */
1344     public static function get_page_data_returns() {
1345         return new external_single_structure(
1346             array(
1347                 'page' => self::get_page_structure(),
1348                 'newpageid' => new external_value(PARAM_INT, 'New page id (if a jump was made)'),
1349                 'pagecontent' => new external_value(PARAM_RAW, 'Page html content', VALUE_OPTIONAL),
1350                 'ongoingscore' => new external_value(PARAM_TEXT, 'The ongoing score message'),
1351                 'progress' => new external_value(PARAM_INT, 'Progress percentage in the lesson'),
1352                 'contentfiles' => new external_files(),
1353                 'answers' => new external_multiple_structure(
1354                     new external_single_structure(
1355                         array(
1356                             'id' => new external_value(PARAM_INT, 'The ID of this answer in the database'),
1357                             'answerfiles' => new external_files(),
1358                             'responsefiles' => new external_files(),
1359                             'jumpto' => new external_value(PARAM_INT, 'Identifies where the user goes upon completing a page with this answer',
1360                                                             VALUE_OPTIONAL),
1361                             'grade' => new external_value(PARAM_INT, 'The grade this answer is worth', VALUE_OPTIONAL),
1362                             'score' => new external_value(PARAM_INT, 'The score this answer will give', VALUE_OPTIONAL),
1363                             'flags' => new external_value(PARAM_INT, 'Used to store options for the answer', VALUE_OPTIONAL),
1364                             'timecreated' => new external_value(PARAM_INT, 'A timestamp of when the answer was created', VALUE_OPTIONAL),
1365                             'timemodified' => new external_value(PARAM_INT, 'A timestamp of when the answer was modified', VALUE_OPTIONAL),
1366                         ), 'The page answers'
1368                     )
1369                 ),
1370                 'messages' => self::external_messages(),
1371                 'displaymenu' => new external_value(PARAM_BOOL, 'Whether we should display the menu or not in this page.'),
1372                 'warnings' => new external_warnings(),
1373             )
1374         );
1375     }
1377     /**
1378      * Describes the parameters for process_page.
1379      *
1380      * @return external_external_function_parameters
1381      * @since Moodle 3.3
1382      */
1383     public static function process_page_parameters() {
1384         return new external_function_parameters (
1385             array(
1386                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1387                 'pageid' => new external_value(PARAM_INT, 'the page id'),
1388                 'data' => new external_multiple_structure(
1389                     new external_single_structure(
1390                         array(
1391                             'name' => new external_value(PARAM_RAW, 'data name'),
1392                             'value' => new external_value(PARAM_RAW, 'data value'),
1393                         )
1394                     ), 'the data to be saved'
1395                 ),
1396                 'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
1397                 'review' => new external_value(PARAM_BOOL, 'if we want to review just after finishing (1 hour margin)',
1398                     VALUE_DEFAULT, false),
1399             )
1400         );
1401     }
1403     /**
1404      * Processes page responses
1405      *
1406      * @param int $lessonid lesson instance id
1407      * @param int $pageid page id
1408      * @param array $data the data to be saved
1409      * @param str $password optional password (the lesson may be protected)
1410      * @param bool $review if we want to review just after finishing (1 hour margin)
1411      * @return array of warnings and status result
1412      * @since Moodle 3.3
1413      * @throws moodle_exception
1414      */
1415     public static function process_page($lessonid, $pageid,  $data, $password = '', $review = false) {
1416         global $USER;
1418         $params = array('lessonid' => $lessonid, 'pageid' => $pageid, 'data' => $data, 'password' => $password,
1419             'review' => $review);
1420         $params = self::validate_parameters(self::process_page_parameters(), $params);
1422         $warnings = array();
1423         $pagecontent = $ongoingscore = '';
1424         $progress = null;
1426         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1428         // Update timer so the validation can check the time restrictions.
1429         $timer = $lesson->update_timer();
1430         self::validate_attempt($lesson, $params);
1432         // Create the $_POST object required by the lesson question engine.
1433         $_POST = array();
1434         foreach ($data as $element) {
1435             // First check if we are handling editor fields like answer[text].
1436             if (preg_match('/(.+)\[(.+)\]$/', $element['name'], $matches)) {
1437                 $_POST[$matches[1]][$matches[2]] = $element['value'];
1438             } else {
1439                 $_POST[$element['name']] = $element['value'];
1440             }
1441         }
1443         // Ignore sesskey (deep in some APIs), the request is already validated.
1444         $USER->ignoresesskey = true;
1446         // Process page.
1447         $page = $lesson->load_page($params['pageid']);
1448         $result = $lesson->process_page_responses($page);
1450         // Prepare messages.
1451         $reviewmode = $lesson->is_in_review_mode();
1452         $lesson->add_messages_on_page_process($page, $result, $reviewmode);
1454         // Additional lesson information.
1455         if (!$lesson->can_manage()) {
1456             if ($lesson->ongoing && !$reviewmode) {
1457                 $ongoingscore = $lesson->get_ongoing_score_message();
1458             }
1459             if ($lesson->progressbar) {
1460                 $progress = $lesson->calculate_progress();
1461             }
1462         }
1464         // Check conditionally everything coming from result (except newpageid because is always set).
1465         $result = array(
1466             'newpageid'         => (int) $result->newpageid,
1467             'inmediatejump'     => $result->inmediatejump,
1468             'nodefaultresponse' => !empty($result->nodefaultresponse),
1469             'feedback'          => (isset($result->feedback)) ? $result->feedback : '',
1470             'attemptsremaining' => (isset($result->attemptsremaining)) ? $result->attemptsremaining : null,
1471             'correctanswer'     => !empty($result->correctanswer),
1472             'noanswer'          => !empty($result->noanswer),
1473             'isessayquestion'   => !empty($result->isessayquestion),
1474             'maxattemptsreached' => !empty($result->maxattemptsreached),
1475             'response'          => (isset($result->response)) ? $result->response : '',
1476             'studentanswer'     => (isset($result->studentanswer)) ? $result->studentanswer : '',
1477             'userresponse'      => (isset($result->userresponse)) ? $result->userresponse : '',
1478             'reviewmode'        => $reviewmode,
1479             'ongoingscore'      => $ongoingscore,
1480             'progress'          => $progress,
1481             'displaymenu'       => !empty(lesson_displayleftif($lesson)),
1482             'messages'          => self::format_lesson_messages($lesson),
1483             'warnings'          => $warnings,
1484         );
1485         return $result;
1486     }
1488     /**
1489      * Describes the process_page return value.
1490      *
1491      * @return external_single_structure
1492      * @since Moodle 3.3
1493      */
1494     public static function process_page_returns() {
1495         return new external_single_structure(
1496             array(
1497                 'newpageid' => new external_value(PARAM_INT, 'New page id (if a jump was made).'),
1498                 'inmediatejump' => new external_value(PARAM_BOOL, 'Whether the page processing redirect directly to anoter page.'),
1499                 'nodefaultresponse' => new external_value(PARAM_BOOL, 'Whether there is not a default response.'),
1500                 'feedback' => new external_value(PARAM_RAW, 'The response feedback.'),
1501                 'attemptsremaining' => new external_value(PARAM_INT, 'Number of attempts remaining.'),
1502                 'correctanswer' => new external_value(PARAM_BOOL, 'Whether the answer is correct.'),
1503                 'noanswer' => new external_value(PARAM_BOOL, 'Whether there aren\'t answers.'),
1504                 'isessayquestion' => new external_value(PARAM_BOOL, 'Whether is a essay question.'),
1505                 'maxattemptsreached' => new external_value(PARAM_BOOL, 'Whether we reachered the max number of attempts.'),
1506                 'response' => new external_value(PARAM_RAW, 'The response.'),
1507                 'studentanswer' => new external_value(PARAM_RAW, 'The student answer.'),
1508                 'userresponse' => new external_value(PARAM_RAW, 'The user response.'),
1509                 'reviewmode' => new external_value(PARAM_BOOL, 'Whether the user is reviewing.'),
1510                 'ongoingscore' => new external_value(PARAM_TEXT, 'The ongoing message.'),
1511                 'progress' => new external_value(PARAM_INT, 'Progress percentage in the lesson.'),
1512                 'displaymenu' => new external_value(PARAM_BOOL, 'Whether we should display the menu or not in this page.'),
1513                 'messages' => self::external_messages(),
1514                 'warnings' => new external_warnings(),
1515             )
1516         );
1517     }
1519     /**
1520      * Describes the parameters for finish_attempt.
1521      *
1522      * @return external_external_function_parameters
1523      * @since Moodle 3.3
1524      */
1525     public static function finish_attempt_parameters() {
1526         return new external_function_parameters (
1527             array(
1528                 'lessonid' => new external_value(PARAM_INT, 'Lesson instance id.'),
1529                 'password' => new external_value(PARAM_RAW, 'Optional password (the lesson may be protected).', VALUE_DEFAULT, ''),
1530                 'outoftime' => new external_value(PARAM_BOOL, 'If the user run out of time.', VALUE_DEFAULT, false),
1531                 'review' => new external_value(PARAM_BOOL, 'If we want to review just after finishing (1 hour margin).',
1532                     VALUE_DEFAULT, false),
1533             )
1534         );
1535     }
1537     /**
1538      * Finishes the current attempt.
1539      *
1540      * @param int $lessonid lesson instance id
1541      * @param str $password optional password (the lesson may be protected)
1542      * @param bool $outoftime optional if the user run out of time
1543      * @param bool $review if we want to review just after finishing (1 hour margin)
1544      * @return array of warnings and information about the finished attempt
1545      * @since Moodle 3.3
1546      * @throws moodle_exception
1547      */
1548     public static function finish_attempt($lessonid, $password = '', $outoftime = false, $review = false) {
1550         $params = array('lessonid' => $lessonid, 'password' => $password, 'outoftime' => $outoftime, 'review' => $review);
1551         $params = self::validate_parameters(self::finish_attempt_parameters(), $params);
1553         $warnings = array();
1555         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1557         // Update timer so the validation can check the time restrictions.
1558         $timer = $lesson->update_timer();
1560         // Return the validation to avoid exceptions in case the user is out of time.
1561         $params['pageid'] = LESSON_EOL;
1562         $validation = self::validate_attempt($lesson, $params, true);
1564         if (array_key_exists('eolstudentoutoftime', $validation)) {
1565             // Maybe we run out of time just now.
1566             $params['outoftime'] = true;
1567             unset($validation['eolstudentoutoftime']);
1568         }
1569         // Check if there are more errors.
1570         if (!empty($validation)) {
1571             reset($validation);
1572             throw new moodle_exception(key($validation), 'lesson', '', current($validation));   // Throw first error.
1573         }
1575         $result = $lesson->process_eol_page($params['outoftime']);
1577         // Return the data.
1578          $validmessages = array(
1579             'notenoughtimespent', 'numberofpagesviewed', 'youshouldview', 'numberofcorrectanswers',
1580             'displayscorewithessays', 'displayscorewithoutessays', 'yourcurrentgradeisoutof', 'eolstudentoutoftimenoanswers',
1581             'welldone', 'displayofgrade', 'reviewlesson', 'modattemptsnoteacher', 'progresscompleted');
1583         $data = array();
1584         foreach ($result as $el => $value) {
1585             if ($value !== false) {
1586                 $message = '';
1587                 if (in_array($el, $validmessages)) { // Check if the data comes with an informative message.
1588                     $a = (is_bool($value)) ? null : $value;
1589                     $message = get_string($el, 'lesson', $a);
1590                 }
1591                 // Return the data.
1592                 $data[] = array(
1593                     'name' => $el,
1594                     'value' => (is_bool($value)) ? 1 : json_encode($value), // The data can be a php object.
1595                     'message' => $message
1596                 );
1597             }
1598         }
1600         $result = array(
1601             'data'     => $data,
1602             'messages' => self::format_lesson_messages($lesson),
1603             'warnings' => $warnings,
1604         );
1605         return $result;
1606     }
1608     /**
1609      * Describes the finish_attempt return value.
1610      *
1611      * @return external_single_structure
1612      * @since Moodle 3.3
1613      */
1614     public static function finish_attempt_returns() {
1615         return new external_single_structure(
1616             array(
1617                 'data' => new external_multiple_structure(
1618                     new external_single_structure(
1619                         array(
1620                             'name' => new external_value(PARAM_ALPHANUMEXT, 'Data name.'),
1621                             'value' => new external_value(PARAM_RAW, 'Data value.'),
1622                             'message' => new external_value(PARAM_RAW, 'Data message (translated string).'),
1623                         )
1624                     ), 'The EOL page information data.'
1625                 ),
1626                 'messages' => self::external_messages(),
1627                 'warnings' => new external_warnings(),
1628             )
1629         );
1630     }
1632     /**
1633      * Describes the parameters for get_attempts_overview.
1634      *
1635      * @return external_external_function_parameters
1636      * @since Moodle 3.3
1637      */
1638     public static function get_attempts_overview_parameters() {
1639         return new external_function_parameters (
1640             array(
1641                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1642                 'groupid' => new external_value(PARAM_INT, 'group id, 0 means that the function will determine the user group',
1643                                                 VALUE_DEFAULT, 0),
1644             )
1645         );
1646     }
1648     /**
1649      * Get a list of all the attempts made by users in a lesson.
1650      *
1651      * @param int $lessonid lesson instance id
1652      * @param int $groupid group id, 0 means that the function will determine the user group
1653      * @return array of warnings and status result
1654      * @since Moodle 3.3
1655      * @throws moodle_exception
1656      */
1657     public static function get_attempts_overview($lessonid, $groupid = 0) {
1659         $params = array('lessonid' => $lessonid, 'groupid' => $groupid);
1660         $params = self::validate_parameters(self::get_attempts_overview_parameters(), $params);
1661         $studentsdata = $warnings = array();
1663         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1664         require_capability('mod/lesson:viewreports', $context);
1666         if (!empty($params['groupid'])) {
1667             $groupid = $params['groupid'];
1668             // Determine is the group is visible to user.
1669             if (!groups_group_visible($groupid, $course, $cm)) {
1670                 throw new moodle_exception('notingroup');
1671             }
1672         } else {
1673             // Check to see if groups are being used here.
1674             if ($groupmode = groups_get_activity_groupmode($cm)) {
1675                 $groupid = groups_get_activity_group($cm);
1676                 // Determine is the group is visible to user (this is particullary for the group 0 -> all groups).
1677                 if (!groups_group_visible($groupid, $course, $cm)) {
1678                     throw new moodle_exception('notingroup');
1679                 }
1680             } else {
1681                 $groupid = 0;
1682             }
1683         }
1685         list($table, $data) = lesson_get_overview_report_table_and_data($lesson, $groupid);
1686         if ($data !== false) {
1687             $studentsdata = $data;
1688         }
1690         $result = array(
1691             'data' => $studentsdata,
1692             'warnings' => $warnings
1693         );
1694         return $result;
1695     }
1697     /**
1698      * Describes the get_attempts_overview return value.
1699      *
1700      * @return external_single_structure
1701      * @since Moodle 3.3
1702      */
1703     public static function get_attempts_overview_returns() {
1704         return new external_single_structure(
1705             array(
1706                 'data' => new external_single_structure(
1707                     array(
1708                         'lessonscored' => new external_value(PARAM_BOOL, 'True if the lesson was scored.'),
1709                         'numofattempts' => new external_value(PARAM_INT, 'Number of attempts.'),
1710                         'avescore' => new external_value(PARAM_FLOAT, 'Average score.'),
1711                         'highscore' => new external_value(PARAM_FLOAT, 'High score.'),
1712                         'lowscore' => new external_value(PARAM_FLOAT, 'Low score.'),
1713                         'avetime' => new external_value(PARAM_INT, 'Average time (spent in taking the lesson).'),
1714                         'hightime' => new external_value(PARAM_INT, 'High time.'),
1715                         'lowtime' => new external_value(PARAM_INT, 'Low time.'),
1716                         'students' => new external_multiple_structure(
1717                             new external_single_structure(
1718                                 array(
1719                                     'id' => new external_value(PARAM_INT, 'User id.'),
1720                                     'fullname' => new external_value(PARAM_TEXT, 'User full name.'),
1721                                     'bestgrade' => new external_value(PARAM_FLOAT, 'Best grade.'),
1722                                     'attempts' => new external_multiple_structure(
1723                                         new external_single_structure(
1724                                             array(
1725                                                 'try' => new external_value(PARAM_INT, 'Attempt number.'),
1726                                                 'grade' => new external_value(PARAM_FLOAT, 'Attempt grade.'),
1727                                                 'timestart' => new external_value(PARAM_INT, 'Attempt time started.'),
1728                                                 'timeend' => new external_value(PARAM_INT, 'Attempt last time continued.'),
1729                                                 'end' => new external_value(PARAM_INT, 'Attempt time ended.'),
1730                                             )
1731                                         )
1732                                     )
1733                                 )
1734                             ), 'Students data, including attempts.', VALUE_OPTIONAL
1735                         ),
1736                     )
1737                 ),
1738                 'warnings' => new external_warnings(),
1739             )
1740         );
1741     }
1743     /**
1744      * Describes the parameters for get_user_attempt.
1745      *
1746      * @return external_external_function_parameters
1747      * @since Moodle 3.3
1748      */
1749     public static function get_user_attempt_parameters() {
1750         return new external_function_parameters (
1751             array(
1752                 'lessonid' => new external_value(PARAM_INT, 'Lesson instance id.'),
1753                 'userid' => new external_value(PARAM_INT, 'The user id. 0 for current user.'),
1754                 'lessonattempt' => new external_value(PARAM_INT, 'The attempt number.'),
1755             )
1756         );
1757     }
1759     /**
1760      * Return information about the given user attempt (including answers).
1761      *
1762      * @param int $lessonid lesson instance id
1763      * @param int $userid the user id
1764      * @param int $lessonattempt the attempt number
1765      * @return array of warnings and page attempts
1766      * @since Moodle 3.3
1767      * @throws moodle_exception
1768      */
1769     public static function get_user_attempt($lessonid, $userid, $lessonattempt) {
1770         global $USER;
1772         $params = array(
1773             'lessonid' => $lessonid,
1774             'userid' => $userid,
1775             'lessonattempt' => $lessonattempt,
1776         );
1777         $params = self::validate_parameters(self::get_user_attempt_parameters(), $params);
1778         $warnings = array();
1780         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1782         // Default value for userid.
1783         if (empty($params['userid'])) {
1784             $params['userid'] = $USER->id;
1785         }
1787         // Extra checks so only users with permissions can view other users attempts.
1788         if ($USER->id != $params['userid']) {
1789             self::check_can_view_user_data($params['userid'], $course, $cm, $context);
1790         }
1792         list($answerpages, $userstats) = lesson_get_user_detailed_report_data($lesson, $userid, $params['lessonattempt']);
1794         $result = array(
1795             'answerpages' => $answerpages,
1796             'userstats' => $userstats,
1797             'warnings' => $warnings,
1798         );
1799         return $result;
1800     }
1802     /**
1803      * Describes the get_user_attempt return value.
1804      *
1805      * @return external_single_structure
1806      * @since Moodle 3.3
1807      */
1808     public static function get_user_attempt_returns() {
1809         return new external_single_structure(
1810             array(
1811                 'answerpages' => new external_multiple_structure(
1812                     new external_single_structure(
1813                         array(
1814                             'title' => new external_value(PARAM_RAW, 'Page title.'),
1815                             'contents' => new external_value(PARAM_RAW, 'Page contents.'),
1816                             'qtype' => new external_value(PARAM_TEXT, 'Identifies the page type of this page.'),
1817                             'grayout' => new external_value(PARAM_INT, 'If is required to apply a grayout.'),
1818                             'answerdata' => new external_single_structure(
1819                                 array(
1820                                     'score' => new external_value(PARAM_TEXT, 'The score (text version).'),
1821                                     'response' => new external_value(PARAM_RAW, 'The response text.'),
1822                                     'responseformat' => new external_format_value('response.'),
1823                                     'answers' => new external_multiple_structure(
1824                                         new external_multiple_structure(new external_value(PARAM_RAW, 'Possible answers and info.')),
1825                                         'User answers',
1826                                         VALUE_OPTIONAL
1827                                     ),
1828                                 ), 'Answer data (empty in content pages created in Moodle 1.x).', VALUE_OPTIONAL
1829                             )
1830                         )
1831                     )
1832                 ),
1833                 'userstats' => new external_single_structure(
1834                     array(
1835                         'grade' => new external_value(PARAM_FLOAT, 'Attempt final grade.'),
1836                         'completed' => new external_value(PARAM_INT, 'Time completed.'),
1837                         'timetotake' => new external_value(PARAM_INT, 'Time taken.'),
1838                         'gradeinfo' => self::get_user_attempt_grade_structure(VALUE_OPTIONAL)
1839                     )
1840                 ),
1841                 'warnings' => new external_warnings(),
1842             )
1843         );
1844     }
1846     /**
1847      * Describes the parameters for get_pages_possible_jumps.
1848      *
1849      * @return external_external_function_parameters
1850      * @since Moodle 3.3
1851      */
1852     public static function get_pages_possible_jumps_parameters() {
1853         return new external_function_parameters (
1854             array(
1855                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1856             )
1857         );
1858     }
1860     /**
1861      * Return all the possible jumps for the pages in a given lesson.
1862      *
1863      * You may expect different results on consecutive executions due to the random nature of the lesson module.
1864      *
1865      * @param int $lessonid lesson instance id
1866      * @return array of warnings and possible jumps
1867      * @since Moodle 3.3
1868      * @throws moodle_exception
1869      */
1870     public static function get_pages_possible_jumps($lessonid) {
1871         global $USER;
1873         $params = array('lessonid' => $lessonid);
1874         $params = self::validate_parameters(self::get_pages_possible_jumps_parameters(), $params);
1876         $warnings = $jumps = array();
1878         list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
1880         // Only return for managers or if offline attempts are enabled.
1881         if ($lesson->can_manage() || $lesson->allowofflineattempts) {
1883             $lessonpages = $lesson->load_all_pages();
1884             foreach ($lessonpages as $page) {
1885                 $jump = array();
1886                 $jump['pageid'] = $page->id;
1888                 $answers = $page->get_answers();
1889                 if (count($answers) > 0) {
1890                     foreach ($answers as $answer) {
1891                         $jump['answerid'] = $answer->id;
1892                         $jump['jumpto'] = $answer->jumpto;
1893                         $jump['calculatedjump'] = $lesson->calculate_new_page_on_jump($page, $answer->jumpto);
1894                         // Special case, only applies to branch/end of branch.
1895                         if ($jump['calculatedjump'] == LESSON_RANDOMBRANCH) {
1896                             $jump['calculatedjump'] = lesson_unseen_branch_jump($lesson, $USER->id);
1897                         }
1898                         $jumps[] = $jump;
1899                     }
1900                 } else {
1901                     // Imported lessons from 1.x.
1902                     $jump['answerid'] = 0;
1903                     $jump['jumpto'] = $page->nextpageid;
1904                     $jump['calculatedjump'] = $lesson->calculate_new_page_on_jump($page, $page->nextpageid);
1905                     $jumps[] = $jump;
1906                 }
1907             }
1908         }
1910         $result = array(
1911             'jumps' => $jumps,
1912             'warnings' => $warnings,
1913         );
1914         return $result;
1915     }
1917     /**
1918      * Describes the get_pages_possible_jumps return value.
1919      *
1920      * @return external_single_structure
1921      * @since Moodle 3.3
1922      */
1923     public static function get_pages_possible_jumps_returns() {
1924         return new external_single_structure(
1925             array(
1926                 'jumps' => new external_multiple_structure(
1927                     new external_single_structure(
1928                         array(
1929                             'pageid' => new external_value(PARAM_INT, 'The page id'),
1930                             'answerid' => new external_value(PARAM_INT, 'The answer id'),
1931                             'jumpto' => new external_value(PARAM_INT, 'The jump (page id or type of jump)'),
1932                             'calculatedjump' => new external_value(PARAM_INT, 'The real page id (or EOL) to jump'),
1933                         ), 'Jump for a page answer'
1934                     )
1935                 ),
1936                 'warnings' => new external_warnings(),
1937             )
1938         );
1939     }
1941     /**
1942      * Describes the parameters for get_lesson.
1943      *
1944      * @return external_external_function_parameters
1945      * @since Moodle 3.3
1946      */
1947     public static function get_lesson_parameters() {
1948         return new external_function_parameters (
1949             array(
1950                 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1951                 'password' => new external_value(PARAM_RAW, 'lesson password', VALUE_DEFAULT, ''),
1952             )
1953         );
1954     }
1956     /**
1957      * Return information of a given lesson.
1958      *
1959      * @param int $lessonid lesson instance id
1960      * @param str $password optional password (the lesson may be protected)
1961      * @return array of warnings and status result
1962      * @since Moodle 3.3
1963      * @throws moodle_exception
1964      */
1965     public static function get_lesson($lessonid, $password = '') {
1966         global $PAGE;
1968         $params = array('lessonid' => $lessonid, 'password' => $password);
1969         $params = self::validate_parameters(self::get_lesson_parameters(), $params);
1970         $warnings = array();
1972         list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
1974         $lessonrecord = self::get_lesson_summary_for_exporter($lessonrecord, $params['password']);
1975         $exporter = new lesson_summary_exporter($lessonrecord, array('context' => $context));
1977         $result = array();
1978         $result['lesson'] = $exporter->export($PAGE->get_renderer('core'));
1979         $result['warnings'] = $warnings;
1980         return $result;
1981     }
1983     /**
1984      * Describes the get_lesson return value.
1985      *
1986      * @return external_single_structure
1987      * @since Moodle 3.3
1988      */
1989     public static function get_lesson_returns() {
1990         return new external_single_structure(
1991             array(
1992                 'lesson' => lesson_summary_exporter::get_read_structure(),
1993                 'warnings' => new external_warnings(),
1994             )
1995         );
1996     }