2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
22 * @copyright 2017 Juan Leyva <juan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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;
35 * Lesson external functions
39 * @copyright 2017 Juan Leyva <juan@moodle.com>
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 class mod_lesson_external extends external_api {
46 * Return a lesson record ready for being exported.
48 * @param stdClass $lessonrecord lesson record
49 * @param string $password lesson password
50 * @return stdClass the lesson record ready for exporting.
52 protected static function get_lesson_summary_for_exporter($lessonrecord, $password = '') {
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});
75 // Fields only for managers.
77 $fields = array('password', 'dependency', 'conditions', 'activitylink', 'available', 'deadline',
78 'timemodified', 'completionendreached', 'completiontimespent');
80 foreach ($fields as $field) {
81 unset($lessonrecord->{$field});
88 * Describes the parameters for get_lessons_by_courses.
90 * @return external_function_parameters
93 public static function get_lessons_by_courses_parameters() {
94 return new external_function_parameters (
96 'courseids' => new external_multiple_structure(
97 new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
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.
107 * @param array $courseids Array of course ids
108 * @return array of lessons details
111 public static function get_lessons_by_courses($courseids = array()) {
115 $returnedlessons = array();
118 'courseids' => $courseids,
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);
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'));
150 $result['lessons'] = $returnedlessons;
151 $result['warnings'] = $warnings;
156 * Describes the get_lessons_by_courses return value.
158 * @return external_single_structure
161 public static function get_lessons_by_courses_returns() {
162 return new external_single_structure(
164 'lessons' => new external_multiple_structure(
165 lesson_summary_exporter::get_read_structure()
167 'warnings' => new external_warnings(),
173 * Utility function for validating a lesson.
175 * @param int $lessonid lesson instance id
176 * @return array array containing the lesson, course, context and course module objects
179 protected static function validate_lesson($lessonid) {
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);
196 * Validates a new attempt.
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)
204 protected static function validate_attempt(lesson $lesson, $params, $return = false) {
209 // Avoid checkings for managers.
210 if ($lesson->can_manage()) {
215 if ($timerestriction = $lesson->get_time_restriction_status()) {
216 $error = ["$timerestriction->reason" => userdate($timerestriction->time)];
218 throw new moodle_exception(key($error), 'lesson', '', current($error));
220 $errors[key($error)] = current($error);
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)];
227 throw new moodle_exception(key($error), 'lesson', '', current($error));
229 $errors[key($error)] = current($error);
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];
237 throw new moodle_exception(key($error), 'lesson', '', current($error));
239 $errors[key($error)] = current($error);
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];
252 throw new moodle_exception(key($error), 'lesson');
254 $errors[key($error)] = current($error);
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];
266 throw new moodle_exception(key($error), 'lesson');
268 $errors[key($error)] = current($error);
270 } else if ($attemptscount > 0 && !$lesson->retake) {
271 // The user finished the lesson and no retakes are allowed.
272 $error = ["noretake" => null];
274 throw new moodle_exception(key($error), 'lesson');
276 $errors[key($error)] = current($error);
279 if (!$timers = $lesson->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1)) {
280 $error = ["cannotfindtimer" => null];
282 throw new moodle_exception(key($error), 'lesson');
284 $errors[key($error)] = current($error);
286 $timer = current($timers);
287 if (!$lesson->check_time($timer)) {
288 $error = ["eolstudentoutoftime" => null];
290 throw new moodle_exception(key($error), 'lesson');
292 $errors[key($error)] = current($error);
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;
306 if (!isset($USER->modattempts[$lesson->id])) {
307 $error = ["studentoutoftimeforreview" => null];
309 throw new moodle_exception(key($error), 'lesson');
311 $errors[key($error)] = current($error);
321 * Describes the parameters for get_lesson_access_information.
323 * @return external_external_function_parameters
326 public static function get_lesson_access_information_parameters() {
327 return new external_function_parameters (
329 'lessonid' => new external_value(PARAM_INT, 'lesson instance id')
335 * Return access information for a given lesson.
337 * @param int $lessonid lesson instance id
338 * @return array of warnings and the access information
340 * @throws moodle_exception
342 public static function get_lesson_access_information($lessonid) {
348 'lessonid' => $lessonid
350 $params = self::validate_parameters(self::get_lesson_access_information_parameters(), $params);
352 list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
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'][] = [
377 'message' => get_string($reason, 'lesson', $data),
380 $result['warnings'] = $warnings;
385 * Describes the get_lesson_access_information return value.
387 * @return external_single_structure
390 public static function get_lesson_access_information_returns() {
391 return new external_single_structure(
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(
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'),
408 'The reasons why the user cannot attempt the lesson'
411 'warnings' => new external_warnings(),
417 * Describes the parameters for view_lesson.
419 * @return external_external_function_parameters
422 public static function view_lesson_parameters() {
423 return new external_function_parameters (
425 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
426 'password' => new external_value(PARAM_RAW, 'lesson password', VALUE_DEFAULT, ''),
432 * Trigger the course module viewed event and update the module completion status.
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
438 * @throws moodle_exception
440 public static function view_lesson($lessonid, $password = '') {
443 $params = array('lessonid' => $lessonid, 'password' => $password);
444 $params = self::validate_parameters(self::view_lesson_parameters(), $params);
447 list($lesson, $course, $cm, $context, $lessonrecord) = self::validate_lesson($params['lessonid']);
448 self::validate_attempt($lesson, $params);
450 $lesson->set_module_viewed();
453 $result['status'] = true;
454 $result['warnings'] = $warnings;
459 * Describes the view_lesson return value.
461 * @return external_single_structure
464 public static function view_lesson_returns() {
465 return new external_single_structure(
467 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
468 'warnings' => new external_warnings(),
474 * Check if the current user can retrieve lesson information (grades, attempts) about the given user.
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
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');
494 * Describes the parameters for get_questions_attempts.
496 * @return external_external_function_parameters
499 public static function get_questions_attempts_parameters() {
500 return new external_function_parameters (
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),
512 * Return the list of page question attempts in a given lesson.
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
521 * @throws moodle_exception
523 public static function get_questions_attempts($lessonid, $attempt, $correct = false, $pageid = null, $userid = null) {
527 'lessonid' => $lessonid,
528 'attempt' => $attempt,
529 'correct' => $correct,
533 $params = self::validate_parameters(self::get_questions_attempts_parameters(), $params);
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;
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);
549 $result['attempts'] = $lesson->get_attempts($params['attempt'], $params['correct'], $params['pageid'], $params['userid']);
550 $result['warnings'] = $warnings;
555 * Describes the get_questions_attempts return value.
557 * @return external_single_structure
560 public static function get_questions_attempts_returns() {
561 return new external_single_structure(
563 'attempts' => new external_multiple_structure(
564 new external_single_structure(
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'),
576 'The question page attempts'
579 'warnings' => new external_warnings(),
585 * Describes the parameters for get_user_grade.
587 * @return external_external_function_parameters
590 public static function get_user_grade_parameters() {
591 return new external_function_parameters (
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),
600 * Return the final grade in the lesson for the given user.
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
606 * @throws moodle_exception
608 public static function get_user_grade($lessonid, $userid = null) {
610 require_once($CFG->libdir . '/gradelib.php');
613 'lessonid' => $lessonid,
616 $params = self::validate_parameters(self::get_user_grade_parameters(), $params);
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;
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);
632 $formattedgrade = null;
633 $grades = lesson_get_user_grades($lesson, $params['userid']);
634 if (!empty($grades)) {
635 $grade = $grades[$params['userid']]->rawgrade;
638 'itemmodule' => 'lesson',
639 'iteminstance' => $lesson->id,
640 'courseid' => $course->id,
643 $gradeitem = grade_item::fetch($params);
644 $formattedgrade = grade_format_gradevalue($grade, $gradeitem);
648 $result['grade'] = $grade;
649 $result['formattedgrade'] = $formattedgrade;
650 $result['warnings'] = $warnings;
655 * Describes the get_user_grade return value.
657 * @return external_single_structure
660 public static function get_user_grade_returns() {
661 return new external_single_structure(
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(),
671 * Describes an attempt grade structure.
673 * @param int $required if the structure is required or optional
674 * @return external_single_structure the structure
677 protected static function get_user_attempt_grade_structure($required = VALUE_REQUIRED) {
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'),
687 return new external_single_structure(
688 $data, 'Attempt grade', $required
693 * Describes the parameters for get_user_attempt_grade.
695 * @return external_external_function_parameters
698 public static function get_user_attempt_grade_parameters() {
699 return new external_function_parameters (
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),
709 * Return grade information in the attempt for a given user.
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
716 * @throws moodle_exception
718 public static function get_user_attempt_grade($lessonid, $lessonattempt, $userid = null) {
720 require_once($CFG->libdir . '/gradelib.php');
723 'lessonid' => $lessonid,
724 'lessonattempt' => $lessonattempt,
727 $params = self::validate_parameters(self::get_user_attempt_grade_parameters(), $params);
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;
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);
743 $result['grade'] = (array) lesson_grade($lesson, $params['lessonattempt'], $params['userid']);
744 $result['warnings'] = $warnings;
749 * Describes the get_user_attempt_grade return value.
751 * @return external_single_structure
754 public static function get_user_attempt_grade_returns() {
755 return new external_single_structure(
757 'grade' => self::get_user_attempt_grade_structure(),
758 'warnings' => new external_warnings(),
764 * Describes the parameters for get_content_pages_viewed.
766 * @return external_external_function_parameters
769 public static function get_content_pages_viewed_parameters() {
770 return new external_function_parameters (
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),
780 * Return the list of content pages viewed by a user during a lesson attempt.
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
787 * @throws moodle_exception
789 public static function get_content_pages_viewed($lessonid, $lessonattempt, $userid = null) {
793 'lessonid' => $lessonid,
794 'lessonattempt' => $lessonattempt,
797 $params = self::validate_parameters(self::get_content_pages_viewed_parameters(), $params);
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;
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);
812 $pages = $lesson->get_content_pages_viewed($params['lessonattempt'], $params['userid']);
815 $result['pages'] = $pages;
816 $result['warnings'] = $warnings;
821 * Describes the get_content_pages_viewed return value.
823 * @return external_single_structure
826 public static function get_content_pages_viewed_returns() {
827 return new external_single_structure(
829 'pages' => new external_multiple_structure(
830 new external_single_structure(
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.'),
841 'The content pages viewed.'
844 'warnings' => new external_warnings(),
850 * Describes the parameters for get_user_timers.
852 * @return external_external_function_parameters
855 public static function get_user_timers_parameters() {
856 return new external_function_parameters (
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),
865 * Return the timers in the current lesson for the given user.
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
871 * @throws moodle_exception
873 public static function get_user_timers($lessonid, $userid = null) {
877 'lessonid' => $lessonid,
880 $params = self::validate_parameters(self::get_user_timers_parameters(), $params);
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;
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);
895 $timers = $lesson->get_user_timers($params['userid']);
898 $result['timers'] = $timers;
899 $result['warnings'] = $warnings;
904 * Describes the get_user_timers return value.
906 * @return external_single_structure
909 public static function get_user_timers_returns() {
910 return new external_single_structure(
912 'timers' => new external_multiple_structure(
913 new external_single_structure(
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.'),
926 'warnings' => new external_warnings(),
932 * Describes the external structure for a lesson page.
934 * @return external_single_structure
937 protected static function get_page_structure() {
938 return new external_single_structure(
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'),
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
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};
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);
996 * Describes the parameters for get_pages.
998 * @return external_external_function_parameters
1001 public static function get_pages_parameters() {
1002 return new external_function_parameters (
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, ''),
1011 * Return the list of pages in a lesson (based on the user permissions).
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
1017 * @throws moodle_exception
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();
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();
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();
1059 $pages[] = $pagedata;
1063 $result['pages'] = $pages;
1064 $result['warnings'] = $warnings;
1069 * Describes the get_pages return value.
1071 * @return external_single_structure
1074 public static function get_pages_returns() {
1075 return new external_single_structure(
1077 'pages' => new external_multiple_structure(
1078 new external_single_structure(
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)'
1084 'jumps' => new external_multiple_structure(
1085 new external_value(PARAM_INT, 'Page to jump id'), 'List of possible page jumps'
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'),
1093 'warnings' => new external_warnings(),
1099 * Describes the parameters for launch_attempt.
1101 * @return external_external_function_parameters
1104 public static function launch_attempt_parameters() {
1105 return new external_function_parameters (
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),
1116 * Return lesson messages formatted according the external_messages structure
1118 * @param lesson $lesson lesson instance
1119 * @return array messages formatted
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],
1134 * Return a external structure representing messages.
1136 * @return external_multiple_structure messages structure
1139 protected static function external_messages() {
1140 return new external_multiple_structure(
1141 new external_single_structure(
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'
1152 * Starts a new attempt or continues an existing one.
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
1160 * @throws moodle_exception
1162 public static function launch_attempt($lessonid, $password = '', $pageid = 0, $review = false) {
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);
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;
1184 if (!$alreadystarted && !$lesson->can_manage()) {
1185 $lesson->start_timer();
1188 if ($params['pageid'] == LESSON_EOL) {
1189 throw new moodle_exception('endoflesson', 'lesson');
1191 $timer = $lesson->update_timer(true, true);
1192 if (!$lesson->check_time($timer)) {
1193 throw new moodle_exception('eolstudentoutoftime', 'lesson');
1196 $messages = self::format_lesson_messages($lesson);
1200 'messages' => $messages,
1201 'warnings' => $warnings,
1207 * Describes the launch_attempt return value.
1209 * @return external_single_structure
1212 public static function launch_attempt_returns() {
1213 return new external_single_structure(
1215 'messages' => self::external_messages(),
1216 'warnings' => new external_warnings(),
1222 * Describes the parameters for get_page_data.
1224 * @return external_external_function_parameters
1227 public static function get_page_data_parameters() {
1228 return new external_function_parameters (
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),
1242 * Return information of a given page, including its contents.
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
1251 * @throws moodle_exception
1253 public static function get_page_data($lessonid, $pageid, $password = '', $review = false, $returncontents = false) {
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 = '';
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);
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);
1284 $contentfiles = external_util::get_area_files($context->id, 'mod_lesson', 'page_contents', $page->id);
1288 $pageanswers = $page->get_answers();
1289 foreach ($pageanswers as $a) {
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),
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};
1303 $answers[] = $answer;
1306 // Additional lesson information.
1307 if (!$lesson->can_manage()) {
1308 if ($lesson->ongoing && !$reviewmode) {
1309 $ongoingscore = $lesson->get_ongoing_score_message();
1311 if ($lesson->progressbar) {
1312 $progress = $lesson->calculate_progress();
1317 $messages = self::format_lesson_messages($lesson);
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)),
1331 if ($params['returncontents']) {
1332 $result['pagecontent'] = $pagecontent; // Return the complete page contents rendered.
1339 * Describes the get_page_data return value.
1341 * @return external_single_structure
1344 public static function get_page_data_returns() {
1345 return new external_single_structure(
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(
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',
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'
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(),
1378 * Describes the parameters for process_page.
1380 * @return external_external_function_parameters
1383 public static function process_page_parameters() {
1384 return new external_function_parameters (
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(
1391 'name' => new external_value(PARAM_RAW, 'data name'),
1392 'value' => new external_value(PARAM_RAW, 'data value'),
1394 ), 'the data to be saved'
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),
1404 * Processes page responses
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
1413 * @throws moodle_exception
1415 public static function process_page($lessonid, $pageid, $data, $password = '', $review = false) {
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 = '';
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.
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'];
1439 $_POST[$element['name']] = $element['value'];
1443 // Ignore sesskey (deep in some APIs), the request is already validated.
1444 $USER->ignoresesskey = true;
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();
1459 if ($lesson->progressbar) {
1460 $progress = $lesson->calculate_progress();
1464 // Check conditionally everything coming from result (except newpageid because is always set).
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,
1489 * Describes the process_page return value.
1491 * @return external_single_structure
1494 public static function process_page_returns() {
1495 return new external_single_structure(
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(),
1520 * Describes the parameters for finish_attempt.
1522 * @return external_external_function_parameters
1525 public static function finish_attempt_parameters() {
1526 return new external_function_parameters (
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),
1538 * Finishes the current attempt.
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
1546 * @throws moodle_exception
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']);
1569 // Check if there are more errors.
1570 if (!empty($validation)) {
1572 throw new moodle_exception(key($validation), 'lesson', '', current($validation)); // Throw first error.
1575 $result = $lesson->process_eol_page($params['outoftime']);
1578 $validmessages = array(
1579 'notenoughtimespent', 'numberofpagesviewed', 'youshouldview', 'numberofcorrectanswers',
1580 'displayscorewithessays', 'displayscorewithoutessays', 'yourcurrentgradeisoutof', 'eolstudentoutoftimenoanswers',
1581 'welldone', 'displayofgrade', 'reviewlesson', 'modattemptsnoteacher', 'progresscompleted');
1584 foreach ($result as $el => $value) {
1585 if ($value !== false) {
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);
1594 'value' => (is_bool($value)) ? 1 : json_encode($value), // The data can be a php object.
1595 'message' => $message
1602 'messages' => self::format_lesson_messages($lesson),
1603 'warnings' => $warnings,
1609 * Describes the finish_attempt return value.
1611 * @return external_single_structure
1614 public static function finish_attempt_returns() {
1615 return new external_single_structure(
1617 'data' => new external_multiple_structure(
1618 new external_single_structure(
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).'),
1624 ), 'The EOL page information data.'
1626 'messages' => self::external_messages(),
1627 'warnings' => new external_warnings(),
1633 * Describes the parameters for get_attempts_overview.
1635 * @return external_external_function_parameters
1638 public static function get_attempts_overview_parameters() {
1639 return new external_function_parameters (
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',
1649 * Get a list of all the attempts made by users in a lesson.
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
1655 * @throws moodle_exception
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');
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');
1685 list($table, $data) = lesson_get_overview_report_table_and_data($lesson, $groupid);
1686 if ($data !== false) {
1687 $studentsdata = $data;
1691 'data' => $studentsdata,
1692 'warnings' => $warnings
1698 * Describes the get_attempts_overview return value.
1700 * @return external_single_structure
1703 public static function get_attempts_overview_returns() {
1704 return new external_single_structure(
1706 'data' => new external_single_structure(
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(
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(
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.'),
1734 ), 'Students data, including attempts.', VALUE_OPTIONAL
1738 'warnings' => new external_warnings(),
1744 * Describes the parameters for get_user_attempt.
1746 * @return external_external_function_parameters
1749 public static function get_user_attempt_parameters() {
1750 return new external_function_parameters (
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.'),
1760 * Return information about the given user attempt (including answers).
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
1767 * @throws moodle_exception
1769 public static function get_user_attempt($lessonid, $userid, $lessonattempt) {
1773 'lessonid' => $lessonid,
1774 'userid' => $userid,
1775 'lessonattempt' => $lessonattempt,
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;
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);
1792 list($answerpages, $userstats) = lesson_get_user_detailed_report_data($lesson, $userid, $params['lessonattempt']);
1795 'answerpages' => $answerpages,
1796 'userstats' => $userstats,
1797 'warnings' => $warnings,
1803 * Describes the get_user_attempt return value.
1805 * @return external_single_structure
1808 public static function get_user_attempt_returns() {
1809 return new external_single_structure(
1811 'answerpages' => new external_multiple_structure(
1812 new external_single_structure(
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(
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.')),
1828 ), 'Answer data (empty in content pages created in Moodle 1.x).', VALUE_OPTIONAL
1833 'userstats' => new external_single_structure(
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)
1841 'warnings' => new external_warnings(),
1847 * Describes the parameters for get_pages_possible_jumps.
1849 * @return external_external_function_parameters
1852 public static function get_pages_possible_jumps_parameters() {
1853 return new external_function_parameters (
1855 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1861 * Return all the possible jumps for the pages in a given lesson.
1863 * You may expect different results on consecutive executions due to the random nature of the lesson module.
1865 * @param int $lessonid lesson instance id
1866 * @return array of warnings and possible jumps
1868 * @throws moodle_exception
1870 public static function get_pages_possible_jumps($lessonid) {
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) {
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);
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);
1912 'warnings' => $warnings,
1918 * Describes the get_pages_possible_jumps return value.
1920 * @return external_single_structure
1923 public static function get_pages_possible_jumps_returns() {
1924 return new external_single_structure(
1926 'jumps' => new external_multiple_structure(
1927 new external_single_structure(
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'
1936 'warnings' => new external_warnings(),
1942 * Describes the parameters for get_lesson.
1944 * @return external_external_function_parameters
1947 public static function get_lesson_parameters() {
1948 return new external_function_parameters (
1950 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1951 'password' => new external_value(PARAM_RAW, 'lesson password', VALUE_DEFAULT, ''),
1957 * Return information of a given lesson.
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
1963 * @throws moodle_exception
1965 public static function get_lesson($lessonid, $password = '') {
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));
1978 $result['lesson'] = $exporter->export($PAGE->get_renderer('core'));
1979 $result['warnings'] = $warnings;
1984 * Describes the get_lesson return value.
1986 * @return external_single_structure
1989 public static function get_lesson_returns() {
1990 return new external_single_structure(
1992 'lesson' => lesson_summary_exporter::get_read_structure(),
1993 'warnings' => new external_warnings(),