MDL-57688 mod_lesson: New WS mod_lesson_launch_attempt
[moodle.git] / mod / lesson / classes / external.php
blobdea75c528086b11e891eea1711a374fb871bb3a9
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.
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
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
27 defined('MOODLE_INTERNAL') || die;
29 require_once($CFG->libdir . '/externallib.php');
30 require_once($CFG->dirroot . '/mod/lesson/locallib.php');
32 /**
33 * Lesson external functions
35 * @package mod_lesson
36 * @category external
37 * @copyright 2017 Juan Leyva <juan@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 * @since Moodle 3.3
41 class mod_lesson_external extends external_api {
43 /**
44 * Describes the parameters for get_lessons_by_courses.
46 * @return external_function_parameters
47 * @since Moodle 3.3
49 public static function get_lessons_by_courses_parameters() {
50 return new external_function_parameters (
51 array(
52 'courseids' => new external_multiple_structure(
53 new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
59 /**
60 * Returns a list of lessons in a provided list of courses,
61 * if no list is provided all lessons that the user can view will be returned.
63 * @param array $courseids Array of course ids
64 * @return array of lessons details
65 * @since Moodle 3.3
67 public static function get_lessons_by_courses($courseids = array()) {
68 global $USER;
70 $warnings = array();
71 $returnedlessons = array();
73 $params = array(
74 'courseids' => $courseids,
76 $params = self::validate_parameters(self::get_lessons_by_courses_parameters(), $params);
78 $mycourses = array();
79 if (empty($params['courseids'])) {
80 $mycourses = enrol_get_my_courses();
81 $params['courseids'] = array_keys($mycourses);
84 // Ensure there are courseids to loop through.
85 if (!empty($params['courseids'])) {
87 list($courses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses);
89 // Get the lessons in this course, this function checks users visibility permissions.
90 // We can avoid then additional validate_context calls.
91 $lessons = get_all_instances_in_courses("lesson", $courses);
92 foreach ($lessons as $lesson) {
93 $context = context_module::instance($lesson->coursemodule);
95 $lesson = new lesson($lesson);
96 $lesson->update_effective_access($USER->id);
98 // Entry to return.
99 $lessondetails = array();
100 // First, we return information that any user can see in the web interface.
101 $lessondetails['id'] = $lesson->id;
102 $lessondetails['coursemodule'] = $lesson->coursemodule;
103 $lessondetails['course'] = $lesson->course;
104 $lessondetails['name'] = external_format_string($lesson->name, $context->id);
106 $lessonavailable = $lesson->get_time_restriction_status() === false;
107 $lessonavailable = $lessonavailable && $lesson->get_password_restriction_status('') === false;
108 $lessonavailable = $lessonavailable && $lesson->get_dependencies_restriction_status() === false;
110 if ($lessonavailable) {
111 // Format intro.
112 list($lessondetails['intro'], $lessondetails['introformat']) = external_format_text($lesson->intro,
113 $lesson->introformat, $context->id, 'mod_lesson', 'intro', null);
115 $lessondetails['introfiles'] = external_util::get_area_files($context->id, 'mod_lesson', 'intro', false, false);
116 $lessondetails['mediafiles'] = external_util::get_area_files($context->id, 'mod_lesson', 'mediafile', 0);
117 $viewablefields = array('practice', 'modattempts', 'usepassword', 'grade', 'custom', 'ongoing', 'usemaxgrade',
118 'maxanswers', 'maxattempts', 'review', 'nextpagedefault', 'feedback', 'minquestions',
119 'maxpages', 'timelimit', 'retake', 'mediafile', 'mediaheight', 'mediawidth',
120 'mediaclose', 'slideshow', 'width', 'height', 'bgcolor', 'displayleft', 'displayleftif',
121 'progressbar');
123 // Fields only for managers.
124 if ($lesson->can_manage()) {
125 $additionalfields = array('password', 'dependency', 'conditions', 'activitylink', 'available', 'deadline',
126 'timemodified', 'completionendreached', 'completiontimespent');
127 $viewablefields = array_merge($viewablefields, $additionalfields);
130 foreach ($viewablefields as $field) {
131 $lessondetails[$field] = $lesson->{$field};
134 $returnedlessons[] = $lessondetails;
137 $result = array();
138 $result['lessons'] = $returnedlessons;
139 $result['warnings'] = $warnings;
140 return $result;
144 * Describes the get_lessons_by_courses return value.
146 * @return external_single_structure
147 * @since Moodle 3.3
149 public static function get_lessons_by_courses_returns() {
150 return new external_single_structure(
151 array(
152 'lessons' => new external_multiple_structure(
153 new external_single_structure(
154 array(
155 'id' => new external_value(PARAM_INT, 'Standard Moodle primary key.'),
156 'course' => new external_value(PARAM_INT, 'Foreign key reference to the course this lesson is part of.'),
157 'coursemodule' => new external_value(PARAM_INT, 'Course module id.'),
158 'name' => new external_value(PARAM_RAW, 'Lesson name.'),
159 'intro' => new external_value(PARAM_RAW, 'Lesson introduction text.', VALUE_OPTIONAL),
160 'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
161 'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
162 'practice' => new external_value(PARAM_INT, 'Practice lesson?', VALUE_OPTIONAL),
163 'modattempts' => new external_value(PARAM_INT, 'Allow student review?', VALUE_OPTIONAL),
164 'usepassword' => new external_value(PARAM_INT, 'Password protected lesson?', VALUE_OPTIONAL),
165 'password' => new external_value(PARAM_RAW, 'Password', VALUE_OPTIONAL),
166 'dependency' => new external_value(PARAM_INT, 'Dependent on (another lesson id)', VALUE_OPTIONAL),
167 'conditions' => new external_value(PARAM_RAW, 'Conditions to enable the lesson', VALUE_OPTIONAL),
168 'grade' => new external_value(PARAM_INT, 'The total that the grade is scaled to be out of',
169 VALUE_OPTIONAL),
170 'custom' => new external_value(PARAM_INT, 'Custom scoring?', VALUE_OPTIONAL),
171 'ongoing' => new external_value(PARAM_INT, 'Display ongoing score?', VALUE_OPTIONAL),
172 'usemaxgrade' => new external_value(PARAM_INT, 'How to calculate the final grade', VALUE_OPTIONAL),
173 'maxanswers' => new external_value(PARAM_INT, 'Maximum answers per page', VALUE_OPTIONAL),
174 'maxattempts' => new external_value(PARAM_INT, 'Maximum attempts', VALUE_OPTIONAL),
175 'review' => new external_value(PARAM_INT, 'Provide option to try a question again', VALUE_OPTIONAL),
176 'nextpagedefault' => new external_value(PARAM_INT, 'Action for a correct answer', VALUE_OPTIONAL),
177 'feedback' => new external_value(PARAM_INT, 'Display default feedback', VALUE_OPTIONAL),
178 'minquestions' => new external_value(PARAM_INT, 'Minimum number of questions', VALUE_OPTIONAL),
179 'maxpages' => new external_value(PARAM_INT, 'Number of pages to show', VALUE_OPTIONAL),
180 'timelimit' => new external_value(PARAM_INT, 'Time limit', VALUE_OPTIONAL),
181 'retake' => new external_value(PARAM_INT, 'Re-takes allowed', VALUE_OPTIONAL),
182 'activitylink' => new external_value(PARAM_INT, 'Link to next activity', VALUE_OPTIONAL),
183 'mediafile' => new external_value(PARAM_RAW, 'Local file path or full external URL', VALUE_OPTIONAL),
184 'mediafiles' => new external_files('Media files', VALUE_OPTIONAL),
185 'mediaheight' => new external_value(PARAM_INT, 'Popup for media file height', VALUE_OPTIONAL),
186 'mediawidth' => new external_value(PARAM_INT, 'Popup for media with', VALUE_OPTIONAL),
187 'mediaclose' => new external_value(PARAM_INT, 'Display a close button in the popup?', VALUE_OPTIONAL),
188 'slideshow' => new external_value(PARAM_INT, 'Display lesson as slideshow', VALUE_OPTIONAL),
189 'width' => new external_value(PARAM_INT, 'Slideshow width', VALUE_OPTIONAL),
190 'height' => new external_value(PARAM_INT, 'Slideshow height', VALUE_OPTIONAL),
191 'bgcolor' => new external_value(PARAM_TEXT, 'Slideshow bgcolor', VALUE_OPTIONAL),
192 'displayleft' => new external_value(PARAM_INT, 'Display left pages menu?', VALUE_OPTIONAL),
193 'displayleftif' => new external_value(PARAM_INT, 'Minimum grade to display menu', VALUE_OPTIONAL),
194 'progressbar' => new external_value(PARAM_INT, 'Display progress bar?', VALUE_OPTIONAL),
195 'available' => new external_value(PARAM_INT, 'Available from', VALUE_OPTIONAL),
196 'deadline' => new external_value(PARAM_INT, 'Available until', VALUE_OPTIONAL),
197 'timemodified' => new external_value(PARAM_INT, 'Last time settings were updated', VALUE_OPTIONAL),
198 'completionendreached' => new external_value(PARAM_INT, 'Require end reached for completion?',
199 VALUE_OPTIONAL),
200 'completiontimespent' => new external_value(PARAM_INT, 'Student must do this activity at least for',
201 VALUE_OPTIONAL),
202 'visible' => new external_value(PARAM_INT, 'Visible?', VALUE_OPTIONAL),
203 'groupmode' => new external_value(PARAM_INT, 'Group mode', VALUE_OPTIONAL),
204 'groupingid' => new external_value(PARAM_INT, 'Grouping id', VALUE_OPTIONAL),
208 'warnings' => new external_warnings(),
214 * Utility function for validating a lesson.
216 * @param int $lessonid lesson instance id
217 * @return array array containing the lesson, course, context and course module objects
218 * @since Moodle 3.3
220 protected static function validate_lesson($lessonid) {
221 global $DB, $USER;
223 // Request and permission validation.
224 $lesson = $DB->get_record('lesson', array('id' => $lessonid), '*', MUST_EXIST);
225 list($course, $cm) = get_course_and_cm_from_instance($lesson, 'lesson');
227 $lesson = new lesson($lesson, $cm, $course);
228 $lesson->update_effective_access($USER->id);
230 $context = $lesson->context;
231 self::validate_context($context);
233 return array($lesson, $course, $cm, $context);
237 * Validates a new attempt.
239 * @param lesson $lesson lesson instance
240 * @param array $params request parameters
241 * @param boolean $return whether to return the errors or throw exceptions
242 * @return array the errors (if return set to true)
243 * @since Moodle 3.3
245 protected static function validate_attempt(lesson $lesson, $params, $return = false) {
246 global $USER, $CFG;
248 $errors = array();
250 // Avoid checkings for managers.
251 if ($lesson->can_manage()) {
252 return [];
255 // Dead line.
256 if ($timerestriction = $lesson->get_time_restriction_status()) {
257 $error = ["$timerestriction->reason" => userdate($timerestriction->time)];
258 if (!$return) {
259 throw new moodle_exception(key($error), 'lesson', '', current($error));
261 $errors[key($error)] = current($error);
264 // Password protected lesson code.
265 if ($passwordrestriction = $lesson->get_password_restriction_status($params['password'])) {
266 $error = ["passwordprotectedlesson" => external_format_string($lesson->name, $lesson->context->id)];
267 if (!$return) {
268 throw new moodle_exception(key($error), 'lesson', '', current($error));
270 $errors[key($error)] = current($error);
273 // Check for dependencies.
274 if ($dependenciesrestriction = $lesson->get_dependencies_restriction_status()) {
275 $errorhtmllist = implode(get_string('and', 'lesson') . ', ', $dependenciesrestriction->errors);
276 $error = ["completethefollowingconditions" => $dependenciesrestriction->dependentlesson->name . $errorhtmllist];
277 if (!$return) {
278 throw new moodle_exception(key($error), 'lesson', '', current($error));
280 $errors[key($error)] = current($error);
283 // To check only when no page is set (starting or continuing a lesson).
284 if (empty($params['pageid'])) {
285 // To avoid multiple calls, store the magic property firstpage.
286 $lessonfirstpage = $lesson->firstpage;
287 $lessonfirstpageid = $lessonfirstpage ? $lessonfirstpage->id : false;
289 // Check if the lesson does not have pages.
290 if (!$lessonfirstpageid) {
291 $error = ["lessonnotready2" => null];
292 if (!$return) {
293 throw new moodle_exception(key($error), 'lesson');
295 $errors[key($error)] = current($error);
298 // Get the number of retries (also referenced as attempts), and the last page seen.
299 $attemptscount = $lesson->count_user_retries($USER->id);
300 $lastpageseen = $lesson->get_last_page_seen($attemptscount);
302 // Check if the user left a timed session with no retakes.
303 if ($lastpageseen !== false && $lastpageseen != LESSON_EOL) {
304 if ($lesson->left_during_timed_session($attemptscount) && $lesson->timelimit && !$lesson->retake) {
305 $error = ["leftduringtimednoretake" => null];
306 if (!$return) {
307 throw new moodle_exception(key($error), 'lesson');
309 $errors[key($error)] = current($error);
311 } else if ($attemptscount > 0 && !$lesson->retake) {
312 // The user finished the lesson and no retakes are allowed.
313 $error = ["noretake" => null];
314 if (!$return) {
315 throw new moodle_exception(key($error), 'lesson');
317 $errors[key($error)] = current($error);
319 } else {
320 if (!$timers = $lesson->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1)) {
321 $error = ["cannotfindtimer" => null];
322 if (!$return) {
323 throw new moodle_exception(key($error), 'lesson');
325 $errors[key($error)] = current($error);
326 } else {
327 $timer = current($timers);
328 if (!$lesson->check_time($timer)) {
329 $error = ["eolstudentoutoftime" => null];
330 if (!$return) {
331 throw new moodle_exception(key($error), 'lesson');
333 $errors[key($error)] = current($error);
336 // Check if the user want to review an attempt he just finished.
337 if (!empty($params['review'])) {
338 // Allow review only for completed attempts during active session time.
339 if ($timer->completed and ($timer->lessontime + $CFG->sessiontimeout > time()) ) {
340 $ntries = $lesson->count_user_retries($USER->id);
341 if ($attempts = $lesson->get_attempts($ntries)) {
342 $lastattempt = end($attempts);
343 $USER->modattempts[$lesson->id] = $lastattempt->pageid;
347 if (!isset($USER->modattempts[$lesson->id])) {
348 $error = ["studentoutoftimeforreview" => null];
349 if (!$return) {
350 throw new moodle_exception(key($error), 'lesson');
352 $errors[key($error)] = current($error);
358 return $errors;
362 * Describes the parameters for get_lesson_access_information.
364 * @return external_external_function_parameters
365 * @since Moodle 3.3
367 public static function get_lesson_access_information_parameters() {
368 return new external_function_parameters (
369 array(
370 'lessonid' => new external_value(PARAM_INT, 'lesson instance id')
376 * Return access information for a given lesson.
378 * @param int $lessonid lesson instance id
379 * @return array of warnings and the access information
380 * @since Moodle 3.3
381 * @throws moodle_exception
383 public static function get_lesson_access_information($lessonid) {
384 global $DB, $USER;
386 $warnings = array();
388 $params = array(
389 'lessonid' => $lessonid
391 $params = self::validate_parameters(self::get_lesson_access_information_parameters(), $params);
393 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
395 $result = array();
396 // Capabilities first.
397 $result['canmanage'] = $lesson->can_manage();
398 $result['cangrade'] = has_capability('mod/lesson:grade', $context);
399 $result['canviewreports'] = has_capability('mod/lesson:viewreports', $context);
401 // Status information.
402 $result['reviewmode'] = $lesson->is_in_review_mode();
403 $result['attemptscount'] = $lesson->count_user_retries($USER->id);
404 $lastpageseen = $lesson->get_last_page_seen($result['attemptscount']);
405 $result['lastpageseen'] = ($lastpageseen !== false) ? $lastpageseen : 0;
406 $result['leftduringtimedsession'] = $lesson->left_during_timed_session($result['attemptscount']);
407 // To avoid multiple calls, store the magic property firstpage.
408 $lessonfirstpage = $lesson->firstpage;
409 $result['firstpageid'] = $lessonfirstpage ? $lessonfirstpage->id : 0;
411 // Access restrictions now, we emulate a new attempt access to get the possible warnings.
412 $result['preventaccessreasons'] = [];
413 $validationerrors = self::validate_attempt($lesson, ['password' => ''], true);
414 foreach ($validationerrors as $reason => $data) {
415 $result['preventaccessreasons'][] = [
416 'reason' => $reason,
417 'data' => $data,
418 'message' => get_string($reason, 'lesson', $data),
421 $result['warnings'] = $warnings;
422 return $result;
426 * Describes the get_lesson_access_information return value.
428 * @return external_single_structure
429 * @since Moodle 3.3
431 public static function get_lesson_access_information_returns() {
432 return new external_single_structure(
433 array(
434 'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can manage the lesson or not.'),
435 'cangrade' => new external_value(PARAM_BOOL, 'Whether the user can grade the lesson or not.'),
436 'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the lesson reports or not.'),
437 'reviewmode' => new external_value(PARAM_BOOL, 'Whether the lesson is in review mode for the current user.'),
438 'attemptscount' => new external_value(PARAM_INT, 'The number of attempts done by the user.'),
439 'lastpageseen' => new external_value(PARAM_INT, 'The last page seen id.'),
440 'leftduringtimedsession' => new external_value(PARAM_BOOL, 'Whether the user left during a timed session.'),
441 'firstpageid' => new external_value(PARAM_INT, 'The lesson first page id.'),
442 'preventaccessreasons' => new external_multiple_structure(
443 new external_single_structure(
444 array(
445 'reason' => new external_value(PARAM_ALPHANUMEXT, 'Reason lang string code'),
446 'data' => new external_value(PARAM_RAW, 'Additional data'),
447 'message' => new external_value(PARAM_RAW, 'Complete html message'),
449 'The reasons why the user cannot attempt the lesson'
452 'warnings' => new external_warnings(),
458 * Describes the parameters for view_lesson.
460 * @return external_external_function_parameters
461 * @since Moodle 3.3
463 public static function view_lesson_parameters() {
464 return new external_function_parameters (
465 array(
466 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
467 'password' => new external_value(PARAM_RAW, 'lesson password', VALUE_DEFAULT, ''),
473 * Trigger the course module viewed event and update the module completion status.
475 * @param int $lessonid lesson instance id
476 * @param str $password optional password (the lesson may be protected)
477 * @return array of warnings and status result
478 * @since Moodle 3.3
479 * @throws moodle_exception
481 public static function view_lesson($lessonid, $password = '') {
482 global $DB;
484 $params = array('lessonid' => $lessonid, 'password' => $password);
485 $params = self::validate_parameters(self::view_lesson_parameters(), $params);
486 $warnings = array();
488 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
489 self::validate_attempt($lesson, $params);
491 $lesson->set_module_viewed();
493 $result = array();
494 $result['status'] = true;
495 $result['warnings'] = $warnings;
496 return $result;
500 * Describes the view_lesson return value.
502 * @return external_single_structure
503 * @since Moodle 3.3
505 public static function view_lesson_returns() {
506 return new external_single_structure(
507 array(
508 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
509 'warnings' => new external_warnings(),
515 * Check if the current user can retrieve lesson information (grades, attempts) about the given user.
517 * @param int $userid the user to check
518 * @param stdClass $course course object
519 * @param stdClass $cm cm object
520 * @param stdClass $context context object
521 * @throws moodle_exception
522 * @since Moodle 3.3
524 protected static function check_can_view_user_data($userid, $course, $cm, $context) {
525 $user = core_user::get_user($userid, '*', MUST_EXIST);
526 core_user::require_active_user($user);
527 // Check permissions and that if users share group (if groups enabled).
528 require_capability('mod/lesson:viewreports', $context);
529 if (!groups_user_groups_visible($course, $user->id, $cm)) {
530 throw new moodle_exception('notingroup');
535 * Describes the parameters for get_questions_attempts.
537 * @return external_external_function_parameters
538 * @since Moodle 3.3
540 public static function get_questions_attempts_parameters() {
541 return new external_function_parameters (
542 array(
543 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
544 'attempt' => new external_value(PARAM_INT, 'lesson attempt number'),
545 'correct' => new external_value(PARAM_BOOL, 'only fetch correct attempts', VALUE_DEFAULT, false),
546 'pageid' => new external_value(PARAM_INT, 'only fetch attempts at the given page', VALUE_DEFAULT, null),
547 'userid' => new external_value(PARAM_INT, 'only fetch attempts of the given user', VALUE_DEFAULT, null),
553 * Return the list of page question attempts in a given lesson.
555 * @param int $lessonid lesson instance id
556 * @param int $attempt the lesson attempt number
557 * @param bool $correct only fetch correct attempts
558 * @param int $pageid only fetch attempts at the given page
559 * @param int $userid only fetch attempts of the given user
560 * @return array of warnings and page attempts
561 * @since Moodle 3.3
562 * @throws moodle_exception
564 public static function get_questions_attempts($lessonid, $attempt, $correct = false, $pageid = null, $userid = null) {
565 global $DB, $USER;
567 $params = array(
568 'lessonid' => $lessonid,
569 'attempt' => $attempt,
570 'correct' => $correct,
571 'pageid' => $pageid,
572 'userid' => $userid,
574 $params = self::validate_parameters(self::get_questions_attempts_parameters(), $params);
575 $warnings = array();
577 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
579 // Default value for userid.
580 if (empty($params['userid'])) {
581 $params['userid'] = $USER->id;
584 // Extra checks so only users with permissions can view other users attempts.
585 if ($USER->id != $params['userid']) {
586 self::check_can_view_user_data($params['userid'], $course, $cm, $context);
589 $result = array();
590 $result['attempts'] = $lesson->get_attempts($params['attempt'], $params['correct'], $params['pageid'], $params['userid']);
591 $result['warnings'] = $warnings;
592 return $result;
596 * Describes the get_questions_attempts return value.
598 * @return external_single_structure
599 * @since Moodle 3.3
601 public static function get_questions_attempts_returns() {
602 return new external_single_structure(
603 array(
604 'attempts' => new external_multiple_structure(
605 new external_single_structure(
606 array(
607 'id' => new external_value(PARAM_INT, 'The attempt id'),
608 'lessonid' => new external_value(PARAM_INT, 'The attempt lessonid'),
609 'pageid' => new external_value(PARAM_INT, 'The attempt pageid'),
610 'userid' => new external_value(PARAM_INT, 'The user who did the attempt'),
611 'answerid' => new external_value(PARAM_INT, 'The attempt answerid'),
612 'retry' => new external_value(PARAM_INT, 'The lesson attempt number'),
613 'correct' => new external_value(PARAM_INT, 'If it was the correct answer'),
614 'useranswer' => new external_value(PARAM_RAW, 'The complete user answer'),
615 'timeseen' => new external_value(PARAM_INT, 'The time the question was seen'),
617 'The question page attempts'
620 'warnings' => new external_warnings(),
626 * Describes the parameters for get_user_grade.
628 * @return external_external_function_parameters
629 * @since Moodle 3.3
631 public static function get_user_grade_parameters() {
632 return new external_function_parameters (
633 array(
634 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
635 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
641 * Return the final grade in the lesson for the given user.
643 * @param int $lessonid lesson instance id
644 * @param int $userid only fetch grades of this user
645 * @return array of warnings and page attempts
646 * @since Moodle 3.3
647 * @throws moodle_exception
649 public static function get_user_grade($lessonid, $userid = null) {
650 global $CFG, $USER;
651 require_once($CFG->libdir . '/gradelib.php');
653 $params = array(
654 'lessonid' => $lessonid,
655 'userid' => $userid,
657 $params = self::validate_parameters(self::get_user_grade_parameters(), $params);
658 $warnings = array();
660 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
662 // Default value for userid.
663 if (empty($params['userid'])) {
664 $params['userid'] = $USER->id;
667 // Extra checks so only users with permissions can view other users attempts.
668 if ($USER->id != $params['userid']) {
669 self::check_can_view_user_data($params['userid'], $course, $cm, $context);
672 $grade = null;
673 $formattedgrade = null;
674 $grades = lesson_get_user_grades($lesson, $params['userid']);
675 if (!empty($grades)) {
676 $grade = $grades[$params['userid']]->rawgrade;
677 $params = array(
678 'itemtype' => 'mod',
679 'itemmodule' => 'lesson',
680 'iteminstance' => $lesson->id,
681 'courseid' => $course->id,
682 'itemnumber' => 0
684 $gradeitem = grade_item::fetch($params);
685 $formattedgrade = grade_format_gradevalue($grade, $gradeitem);
688 $result = array();
689 $result['grade'] = $grade;
690 $result['formattedgrade'] = $formattedgrade;
691 $result['warnings'] = $warnings;
692 return $result;
696 * Describes the get_user_grade return value.
698 * @return external_single_structure
699 * @since Moodle 3.3
701 public static function get_user_grade_returns() {
702 return new external_single_structure(
703 array(
704 'grade' => new external_value(PARAM_FLOAT, 'The lesson final raw grade'),
705 'formattedgrade' => new external_value(PARAM_RAW, 'The lesson final grade formatted'),
706 'warnings' => new external_warnings(),
712 * Describes the parameters for get_user_attempt_grade.
714 * @return external_external_function_parameters
715 * @since Moodle 3.3
717 public static function get_user_attempt_grade_parameters() {
718 return new external_function_parameters (
719 array(
720 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
721 'lessonattempt' => new external_value(PARAM_INT, 'lesson attempt number'),
722 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
728 * Return grade information in the attempt for a given user.
730 * @param int $lessonid lesson instance id
731 * @param int $lessonattempt lesson attempt number
732 * @param int $userid only fetch attempts of the given user
733 * @return array of warnings and page attempts
734 * @since Moodle 3.3
735 * @throws moodle_exception
737 public static function get_user_attempt_grade($lessonid, $lessonattempt, $userid = null) {
738 global $CFG, $USER;
739 require_once($CFG->libdir . '/gradelib.php');
741 $params = array(
742 'lessonid' => $lessonid,
743 'lessonattempt' => $lessonattempt,
744 'userid' => $userid,
746 $params = self::validate_parameters(self::get_user_attempt_grade_parameters(), $params);
747 $warnings = array();
749 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
751 // Default value for userid.
752 if (empty($params['userid'])) {
753 $params['userid'] = $USER->id;
756 // Extra checks so only users with permissions can view other users attempts.
757 if ($USER->id != $params['userid']) {
758 self::check_can_view_user_data($params['userid'], $course, $cm, $context);
761 $result = (array) lesson_grade($lesson, $params['lessonattempt'], $params['userid']);
762 $result['warnings'] = $warnings;
763 return $result;
767 * Describes the get_user_attempt_grade return value.
769 * @return external_single_structure
770 * @since Moodle 3.3
772 public static function get_user_attempt_grade_returns() {
773 return new external_single_structure(
774 array(
775 'nquestions' => new external_value(PARAM_INT, 'Number of questions answered'),
776 'attempts' => new external_value(PARAM_INT, 'Number of question attempts'),
777 'total' => new external_value(PARAM_FLOAT, 'Max points possible'),
778 'earned' => new external_value(PARAM_FLOAT, 'Points earned by student'),
779 'grade' => new external_value(PARAM_FLOAT, 'Calculated percentage grade'),
780 'nmanual' => new external_value(PARAM_INT, 'Number of manually graded questions'),
781 'manualpoints' => new external_value(PARAM_FLOAT, 'Point value for manually graded questions'),
782 'warnings' => new external_warnings(),
788 * Describes the parameters for get_content_pages_viewed.
790 * @return external_external_function_parameters
791 * @since Moodle 3.3
793 public static function get_content_pages_viewed_parameters() {
794 return new external_function_parameters (
795 array(
796 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
797 'lessonattempt' => new external_value(PARAM_INT, 'lesson attempt number'),
798 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
804 * Return the list of content pages viewed by a user during a lesson attempt.
806 * @param int $lessonid lesson instance id
807 * @param int $lessonattempt lesson attempt number
808 * @param int $userid only fetch attempts of the given user
809 * @return array of warnings and page attempts
810 * @since Moodle 3.3
811 * @throws moodle_exception
813 public static function get_content_pages_viewed($lessonid, $lessonattempt, $userid = null) {
814 global $USER;
816 $params = array(
817 'lessonid' => $lessonid,
818 'lessonattempt' => $lessonattempt,
819 'userid' => $userid,
821 $params = self::validate_parameters(self::get_content_pages_viewed_parameters(), $params);
822 $warnings = array();
824 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
826 // Default value for userid.
827 if (empty($params['userid'])) {
828 $params['userid'] = $USER->id;
831 // Extra checks so only users with permissions can view other users attempts.
832 if ($USER->id != $params['userid']) {
833 self::check_can_view_user_data($params['userid'], $course, $cm, $context);
836 $pages = $lesson->get_content_pages_viewed($params['lessonattempt'], $params['userid']);
838 $result = array();
839 $result['pages'] = $pages;
840 $result['warnings'] = $warnings;
841 return $result;
845 * Describes the get_content_pages_viewed return value.
847 * @return external_single_structure
848 * @since Moodle 3.3
850 public static function get_content_pages_viewed_returns() {
851 return new external_single_structure(
852 array(
853 'pages' => new external_multiple_structure(
854 new external_single_structure(
855 array(
856 'id' => new external_value(PARAM_INT, 'The attempt id.'),
857 'lessonid' => new external_value(PARAM_INT, 'The lesson id.'),
858 'pageid' => new external_value(PARAM_INT, 'The page id.'),
859 'userid' => new external_value(PARAM_INT, 'The user who viewed the page.'),
860 'retry' => new external_value(PARAM_INT, 'The lesson attempt number.'),
861 'flag' => new external_value(PARAM_INT, '1 if the next page was calculated randomly.'),
862 'timeseen' => new external_value(PARAM_INT, 'The time the page was seen.'),
863 'nextpageid' => new external_value(PARAM_INT, 'The next page chosen id.'),
865 'The content pages viewed.'
868 'warnings' => new external_warnings(),
874 * Describes the parameters for get_user_timers.
876 * @return external_external_function_parameters
877 * @since Moodle 3.3
879 public static function get_user_timers_parameters() {
880 return new external_function_parameters (
881 array(
882 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
883 'userid' => new external_value(PARAM_INT, 'the user id (empty for current user)', VALUE_DEFAULT, null),
889 * Return the timers in the current lesson for the given user.
891 * @param int $lessonid lesson instance id
892 * @param int $userid only fetch timers of the given user
893 * @return array of warnings and timers
894 * @since Moodle 3.3
895 * @throws moodle_exception
897 public static function get_user_timers($lessonid, $userid = null) {
898 global $USER;
900 $params = array(
901 'lessonid' => $lessonid,
902 'userid' => $userid,
904 $params = self::validate_parameters(self::get_user_timers_parameters(), $params);
905 $warnings = array();
907 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
909 // Default value for userid.
910 if (empty($params['userid'])) {
911 $params['userid'] = $USER->id;
914 // Extra checks so only users with permissions can view other users attempts.
915 if ($USER->id != $params['userid']) {
916 self::check_can_view_user_data($params['userid'], $course, $cm, $context);
919 $timers = $lesson->get_user_timers($params['userid']);
921 $result = array();
922 $result['timers'] = $timers;
923 $result['warnings'] = $warnings;
924 return $result;
928 * Describes the get_user_timers return value.
930 * @return external_single_structure
931 * @since Moodle 3.3
933 public static function get_user_timers_returns() {
934 return new external_single_structure(
935 array(
936 'timers' => new external_multiple_structure(
937 new external_single_structure(
938 array(
939 'id' => new external_value(PARAM_INT, 'The attempt id'),
940 'lessonid' => new external_value(PARAM_INT, 'The lesson id'),
941 'userid' => new external_value(PARAM_INT, 'The user id'),
942 'starttime' => new external_value(PARAM_INT, 'First access time for a new timer session'),
943 'lessontime' => new external_value(PARAM_INT, 'Last access time to the lesson during the timer session'),
944 'completed' => new external_value(PARAM_INT, 'If the lesson for this timer was completed'),
946 'The timers'
949 'warnings' => new external_warnings(),
955 * Describes the parameters for get_pages.
957 * @return external_external_function_parameters
958 * @since Moodle 3.3
960 public static function get_pages_parameters() {
961 return new external_function_parameters (
962 array(
963 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
964 'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
970 * Return the list of pages in a lesson (based on the user permissions).
972 * @param int $lessonid lesson instance id
973 * @param str $password optional password (the lesson may be protected)
974 * @return array of warnings and status result
975 * @since Moodle 3.3
976 * @throws moodle_exception
978 public static function get_pages($lessonid, $password = '') {
980 $params = array('lessonid' => $lessonid, 'password' => $password);
981 $params = self::validate_parameters(self::get_pages_parameters(), $params);
982 $warnings = array();
984 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
985 self::validate_attempt($lesson, $params);
987 $lessonpages = $lesson->load_all_pages();
988 $pages = array();
990 foreach ($lessonpages as $page) {
991 $pagedata = new stdClass; // Contains the data that will be returned by the WS
993 // Return the visible data.
994 $visibleproperties = array('id', 'lessonid', 'prevpageid', 'nextpageid', 'qtype', 'qoption', 'layout', 'display',
995 'displayinmenublock', 'type', 'typeid', 'typestring', 'timecreated', 'timemodified');
996 foreach ($visibleproperties as $prop) {
997 $pagedata->{$prop} = $page->{$prop};
1000 // Check if we can see title (contents required custom rendering, we won't returning it here @see get_page_data).
1001 $canmanage = $lesson->can_manage();
1002 // If we are managers or the menu block is enabled and is a content page visible.
1003 if ($canmanage || (lesson_displayleftif($lesson) && $page->displayinmenublock && $page->display)) {
1004 $pagedata->title = external_format_string($page->title, $context->id);
1007 // Now, calculate the file area files (maybe we need to download a lesson for offline usage).
1008 $pagedata->filescount = 0;
1009 $pagedata->filessizetotal = 0;
1010 $files = $page->get_files(false); // Get files excluding directories.
1011 foreach ($files as $file) {
1012 $pagedata->filescount++;
1013 $pagedata->filessizetotal += $file->get_filesize();
1016 // Now the possible answers and page jumps ids.
1017 $pagedata->answerids = array();
1018 $pagedata->jumps = array();
1019 $answers = $page->get_answers();
1020 foreach ($answers as $answer) {
1021 $pagedata->answerids[] = $answer->id;
1022 $pagedata->jumps[] = $answer->jumpto;
1023 $files = $answer->get_files(false); // Get files excluding directories.
1024 foreach ($files as $file) {
1025 $pagedata->filescount++;
1026 $pagedata->filessizetotal += $file->get_filesize();
1029 $pages[] = $pagedata;
1032 $result = array();
1033 $result['pages'] = $pages;
1034 $result['warnings'] = $warnings;
1035 return $result;
1039 * Describes the get_pages return value.
1041 * @return external_single_structure
1042 * @since Moodle 3.3
1044 public static function get_pages_returns() {
1045 return new external_single_structure(
1046 array(
1047 'pages' => new external_multiple_structure(
1048 new external_single_structure(
1049 array(
1050 'id' => new external_value(PARAM_INT, 'The id of this lesson page'),
1051 'lessonid' => new external_value(PARAM_INT, 'The id of the lesson this page belongs to'),
1052 'prevpageid' => new external_value(PARAM_INT, 'The id of the page before this one'),
1053 'nextpageid' => new external_value(PARAM_INT, 'The id of the next page in the page sequence'),
1054 'qtype' => new external_value(PARAM_INT, 'Identifies the page type of this page'),
1055 'qoption' => new external_value(PARAM_INT, 'Used to record page type specific options'),
1056 'layout' => new external_value(PARAM_INT, 'Used to record page specific layout selections'),
1057 'display' => new external_value(PARAM_INT, 'Used to record page specific display selections'),
1058 'timecreated' => new external_value(PARAM_INT, 'Timestamp for when the page was created'),
1059 'timemodified' => new external_value(PARAM_INT, 'Timestamp for when the page was last modified'),
1060 'title' => new external_value(PARAM_RAW, 'The title of this page', VALUE_OPTIONAL),
1061 'displayinmenublock' => new external_value(PARAM_BOOL, 'Toggles display in the left menu block'),
1062 'type' => new external_value(PARAM_INT, 'The type of the page [question | structure]'),
1063 'typeid' => new external_value(PARAM_INT, 'The unique identifier for the page type'),
1064 'typestring' => new external_value(PARAM_RAW, 'The string that describes this page type'),
1065 'answerids' => new external_multiple_structure(
1066 new external_value(PARAM_INT, 'Answer id'), 'List of answers ids (empty for content pages in Moodle 1.9)'
1068 'jumps' => new external_multiple_structure(
1069 new external_value(PARAM_INT, 'Page to jump id'), 'List of possible page jumps'
1071 'filescount' => new external_value(PARAM_INT, 'The total number of files attached to the page'),
1072 'filessizetotal' => new external_value(PARAM_INT, 'The total size of the files'),
1074 'The lesson pages'
1077 'warnings' => new external_warnings(),
1083 * Describes the parameters for launch_attempt.
1085 * @return external_external_function_parameters
1086 * @since Moodle 3.3
1088 public static function launch_attempt_parameters() {
1089 return new external_function_parameters (
1090 array(
1091 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
1092 'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
1093 'pageid' => new external_value(PARAM_RAW, 'page id to continue from (only when continuing an attempt)', VALUE_DEFAULT, 0),
1094 'review' => new external_value(PARAM_RAW, 'if we want to review just after finishing', VALUE_DEFAULT, false),
1100 * Starts a new attempt or continues an existing one.
1102 * @param int $lessonid lesson instance id
1103 * @param str $password optional password (the lesson may be protected)
1104 * @param int $pageid page id to continue from (only when continuing an attempt)
1105 * @param bool $review if we want to review just after finishing
1106 * @return array of warnings and status result
1107 * @since Moodle 3.3
1108 * @throws moodle_exception
1110 public static function launch_attempt($lessonid, $password = '', $pageid = 0, $review = false) {
1111 global $CFG, $USER;
1113 $params = array('lessonid' => $lessonid, 'password' => $password, 'pageid' => $pageid, 'review' => $review);
1114 $params = self::validate_parameters(self::launch_attempt_parameters(), $params);
1115 $warnings = $messages = array();
1117 list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
1118 self::validate_attempt($lesson, $params);
1120 $newpageid = 0;
1121 // Starting a new lesson attempt.
1122 if (empty($params['pageid'])) {
1123 // Check if there is a recent timer created during the active session.
1124 $alreadystarted = false;
1125 if ($timers = $lesson->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1)) {
1126 $timer = array_shift($timers);
1127 $endtime = $lesson->timelimit > 0 ? min($CFG->sessiontimeout, $lesson->timelimit) : $CFG->sessiontimeout;
1128 if (!$timer->completed && $timer->starttime > time() - $endtime) {
1129 $alreadystarted = true;
1132 if (!$alreadystarted && !$lesson->can_manage()) {
1133 $lesson->start_timer();
1135 } else {
1136 if ($params['pageid'] == LESSON_EOL) {
1137 throw new moodle_exception('endoflesson', 'lesson');
1139 $timer = $lesson->update_timer(true, true);
1140 if (!$lesson->check_time($timer)) {
1141 throw new moodle_exception('eolstudentoutoftime', 'lesson');
1144 foreach ($lesson->messages as $message) {
1145 $messages[] = array(
1146 'message' => $message[0],
1147 'type' => $message[1],
1151 $result = array(
1152 'status' => true,
1153 'messages' => $messages,
1154 'warnings' => $warnings,
1156 return $result;
1160 * Describes the launch_attempt return value.
1162 * @return external_single_structure
1163 * @since Moodle 3.3
1165 public static function launch_attempt_returns() {
1166 return new external_single_structure(
1167 array(
1168 'messages' => new external_multiple_structure(
1169 new external_single_structure(
1170 array(
1171 'message' => new external_value(PARAM_RAW, 'Message'),
1172 'type' => new external_value(PARAM_ALPHANUMEXT, 'Message type: usually a CSS identifier like:
1173 success, info, warning, error, notifyproblem, notifyerror, notifytiny, notifysuccess')
1174 ), 'The lesson generated messages'
1177 'warnings' => new external_warnings(),