Commit 630f0e3b authored by Juan Leyva's avatar Juan Leyva
Browse files

MDL-57643 mod_lesson: New WS mod_lesson_get_access_information

parent 37029e46
......@@ -209,4 +209,211 @@ class mod_lesson_external extends external_api {
)
);
}
/**
* Utility function for validating a lesson.
*
* @param int $lessonid lesson instance id
* @return array array containing the lesson, course, context and course module objects
* @since Moodle 3.3
*/
protected static function validate_lesson($lessonid) {
global $DB, $USER;
// Request and permission validation.
$lesson = $DB->get_record('lesson', array('id' => $lessonid), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($lesson, 'lesson');
$lesson = new lesson($lesson, $cm);
$lesson->update_effective_access($USER->id);
$context = $lesson->context;
self::validate_context($context);
return array($lesson, $course, $cm, $context);
}
/**
* Validates a new attempt.
*
* @param lesson $lesson lesson instance
* @param array $params request parameters
* @param boolean $return whether to return the errors or throw exceptions
* @return array the errors (if return set to true)
* @since Moodle 3.3
*/
protected static function validate_attempt(lesson $lesson, $params, $return = false) {
global $USER;
$errors = array();
// Avoid checkings for managers.
if ($lesson->can_manage()) {
return [];
}
// Dead line.
if ($timerestriction = $lesson->get_time_restriction_status()) {
$error = ["$timerestriction->reason" => userdate($timerestriction->time)];
if (!$return) {
throw new moodle_exception(key($error), 'lesson', '', current($error));
}
$errors[key($error)] = current($error);
}
// Password protected lesson code.
if ($passwordrestriction = $lesson->get_password_restriction_status($params['password'])) {
$error = ["passwordprotectedlesson" => external_format_string($lesson->name, $lesson->context->id)];
if (!$return) {
throw new moodle_exception(key($error), 'lesson', '', current($error));
}
$errors[key($error)] = current($error);
}
// Check for dependencies.
if ($dependenciesrestriction = $lesson->get_dependencies_restriction_status()) {
$errorhtmllist = implode(get_string('and', 'lesson') . ', ', $dependenciesrestriction->errors);
$error = ["completethefollowingconditions" => $dependenciesrestriction->dependentlesson->name . $errorhtmllist];
if (!$return) {
throw new moodle_exception(key($error), 'lesson', '', current($error));
}
$errors[key($error)] = current($error);
}
// To check only when no page is set (starting or continuing a lesson).
if (empty($params['pageid'])) {
// To avoid multiple calls, store the magic property firstpage.
$lessonfirstpage = $lesson->firstpage;
$lessonfirstpageid = $lessonfirstpage ? $lessonfirstpage->id : false;
// Check if the lesson does not have pages.
if (!$lessonfirstpageid) {
$error = ["lessonnotready2" => null];
if (!$return) {
throw new moodle_exception(key($error), 'lesson');
}
$errors[key($error)] = current($error);
}
// Get the number of retries (also referenced as attempts), and the last page seen.
$attemptscount = $lesson->count_user_retries($USER->id);
$lastpageseen = $lesson->get_last_page_seen($attemptscount);
// Check if the user left a timed session with no retakes.
if ($lastpageseen !== false && $lastpageseen != LESSON_EOL) {
if ($lesson->left_during_timed_session($attemptscount) && $lesson->timelimit && !$lesson->retake) {
$error = ["leftduringtimednoretake" => null];
if (!$return) {
throw new moodle_exception(key($error), 'lesson');
}
$errors[key($error)] = current($error);
}
} else if ($attemptscount > 0 && !$lesson->retake) {
// The user finished the lesson and no retakes are allowed.
$error = ["noretake" => null];
if (!$return) {
throw new moodle_exception(key($error), 'lesson');
}
$errors[key($error)] = current($error);
}
}
return $errors;
}
/**
* Describes the parameters for get_lesson_access_information.
*
* @return external_external_function_parameters
* @since Moodle 3.3
*/
public static function get_lesson_access_information_parameters() {
return new external_function_parameters (
array(
'lessonid' => new external_value(PARAM_INT, 'lesson instance id')
)
);
}
/**
* Return access information for a given lesson.
*
* @param int $lessonid lesson instance id
* @return array of warnings and the access information
* @since Moodle 3.3
* @throws moodle_exception
*/
public static function get_lesson_access_information($lessonid) {
global $DB, $USER;
$warnings = array();
$params = array(
'lessonid' => $lessonid
);
$params = self::validate_parameters(self::get_lesson_access_information_parameters(), $params);
list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
$result = array();
// Capabilities first.
$result['canmanage'] = $lesson->can_manage();
$result['cangrade'] = has_capability('mod/lesson:grade', $context);
$result['canviewreports'] = has_capability('mod/lesson:viewreports', $context);
// Status information.
$result['reviewmode'] = $lesson->is_in_review_mode();
$result['attemptscount'] = $lesson->count_user_retries($USER->id);
$lastpageseen = $lesson->get_last_page_seen($result['attemptscount']);
$result['lastpageseen'] = ($lastpageseen !== false) ? $lastpageseen : 0;
$result['leftduringtimedsession'] = $lesson->left_during_timed_session($result['attemptscount']);
// To avoid multiple calls, store the magic property firstpage.
$lessonfirstpage = $lesson->firstpage;
$result['firstpageid'] = $lessonfirstpage ? $lessonfirstpage->id : 0;
// Access restrictions now, we emulate a new attempt access to get the possible warnings.
$result['preventaccessreasons'] = [];
$validationerrors = self::validate_attempt($lesson, ['password' => ''], true);
foreach ($validationerrors as $reason => $data) {
$result['preventaccessreasons'][] = [
'reason' => $reason,
'data' => $data,
'message' => get_string($reason, 'lesson', $data),
];
}
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_lesson_access_information return value.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function get_lesson_access_information_returns() {
return new external_single_structure(
array(
'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can manage the lesson or not.'),
'cangrade' => new external_value(PARAM_BOOL, 'Whether the user can grade the lesson or not.'),
'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the lesson reports or not.'),
'reviewmode' => new external_value(PARAM_BOOL, 'Whether the lesson is in review mode for the current user.'),
'attemptscount' => new external_value(PARAM_INT, 'The number of attempts done by the user.'),
'lastpageseen' => new external_value(PARAM_INT, 'The last page seen id.'),
'leftduringtimedsession' => new external_value(PARAM_BOOL, 'Whether the user left during a timed session.'),
'firstpageid' => new external_value(PARAM_INT, 'The lesson first page id.'),
'preventaccessreasons' => new external_multiple_structure(
new external_single_structure(
array(
'reason' => new external_value(PARAM_ALPHANUMEXT, 'Reason lang string code'),
'data' => new external_value(PARAM_RAW, 'Additional data'),
'message' => new external_value(PARAM_RAW, 'Complete html message'),
),
'The reasons why the user cannot attempt the lesson'
)
),
'warnings' => new external_warnings(),
)
);
}
}
......@@ -30,9 +30,18 @@ $functions = array(
'mod_lesson_get_lessons_by_courses' => array(
'classname' => 'mod_lesson_external',
'methodname' => 'get_lessons_by_courses',
'description' => 'Returns a list of lessons in a provided list of courses, if no list is provided all lessons that the user can view will be returned.',
'description' => 'Returns a list of lessons in a provided list of courses,
if no list is provided all lessons that the user can view will be returned.',
'type' => 'read',
'capabilities' => 'mod/lesson:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'mod_lesson_get_lesson_access_information' => array(
'classname' => 'mod_lesson_external',
'methodname' => 'get_lesson_access_information',
'description' => 'Return access information for a given lesson.',
'type' => 'read',
'capabilities' => 'mod/lesson:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
);
......@@ -31,6 +31,30 @@ global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/mod/lesson/locallib.php');
/**
* Silly class to access mod_lesson_external internal methods.
*
* @package mod_lesson
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.3
*/
class testable_mod_lesson_external extends mod_lesson_external {
/**
* Validates a new attempt.
*
* @param lesson $lesson lesson instance
* @param array $params request parameters
* @param boolean $return whether to return the errors or throw exceptions
* @return [array the errors (if return set to true)
* @since Moodle 3.3
*/
public static function validate_attempt(lesson $lesson, $params, $return = false) {
return parent::validate_attempt($lesson, $params, $return);
}
}
/**
* Lesson module external functions tests
*
......@@ -53,6 +77,9 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
// Setup test data.
$this->course = $this->getDataGenerator()->create_course();
$this->lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $this->course->id));
$lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
$this->page1 = $lessongenerator->create_content($this->lesson);
$this->page2 = $lessongenerator->create_question_truefalse($this->lesson);
$this->context = context_module::instance($this->lesson->cmid);
$this->cm = get_coursemodule_from_instance('lesson', $this->lesson->id);
......@@ -192,4 +219,124 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
$this->assertFalse(isset($lessons['lessons'][0]['intro']));
}
/**
* Test the validate_attempt function.
*/
public function test_validate_attempt() {
global $DB;
$this->setUser($this->student);
// Test deadline.
$oldtime = time() - DAYSECS;
$DB->set_field('lesson', 'deadline', $oldtime, array('id' => $this->lesson->id));
$lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
$this->assertEquals('lessonclosed', key($validation));
$this->assertCount(1, $validation);
// Test not available yet.
$futuretime = time() + DAYSECS;
$DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'available', $futuretime, array('id' => $this->lesson->id));
$lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
$this->assertEquals('lessonopen', key($validation));
$this->assertCount(1, $validation);
// Test password.
$DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'available', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'password', 'abc', array('id' => $this->lesson->id));
$lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
$this->assertEquals('passwordprotectedlesson', key($validation));
$this->assertCount(1, $validation);
$lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => 'abc'], true);
$this->assertCount(0, $validation);
// Dependencies.
$record = new stdClass();
$record->course = $this->course->id;
$lesson2 = self::getDataGenerator()->create_module('lesson', $record);
$DB->set_field('lesson', 'usepassword', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'password', '', array('id' => $this->lesson->id));
$DB->set_field('lesson', 'dependency', $lesson->id, array('id' => $this->lesson->id));
$lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
$lesson->conditions = serialize((object) ['completed' => true, 'timespent' => 0, 'gradebetterthan' => 0]);
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
$this->assertEquals('completethefollowingconditions', key($validation));
$this->assertCount(1, $validation);
// Lesson withou pages.
$lesson = new lesson($lesson2);
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
$this->assertEquals('lessonnotready2', key($validation));
$this->assertCount(1, $validation);
// Test retakes.
$DB->set_field('lesson', 'dependency', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'retake', 0, array('id' => $this->lesson->id));
$record = [
'lessonid' => $this->lesson->id,
'userid' => $this->student->id,
'grade' => 100,
'late' => 0,
'completed' => 1,
];
$DB->insert_record('lesson_grades', (object) $record);
$lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
$validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
$this->assertEquals('noretake', key($validation));
$this->assertCount(1, $validation);
}
/**
* Test the get_lesson_access_information function.
*/
public function test_get_lesson_access_information() {
global $DB;
$this->setUser($this->student);
// Add previous attempt.
$record = [
'lessonid' => $this->lesson->id,
'userid' => $this->student->id,
'grade' => 100,
'late' => 0,
'completed' => 1,
];
$DB->insert_record('lesson_grades', (object) $record);
$result = mod_lesson_external::get_lesson_access_information($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result);
$this->assertFalse($result['canmanage']);
$this->assertFalse($result['cangrade']);
$this->assertFalse($result['canviewreports']);
$this->assertFalse($result['leftduringtimedsession']);
$this->assertEquals(1, $result['reviewmode']);
$this->assertEquals(1, $result['attemptscount']);
$this->assertEquals(0, $result['lastpageseen']);
$this->assertEquals($this->page2->id, $result['firstpageid']);
$this->assertCount(1, $result['preventaccessreasons']);
$this->assertEquals('noretake', $result['preventaccessreasons'][0]['reason']);
$this->assertEquals(null, $result['preventaccessreasons'][0]['data']);
$this->assertEquals(get_string('noretake', 'lesson'), $result['preventaccessreasons'][0]['message']);
// Now check permissions as admin.
$this->setAdminUser();
$result = mod_lesson_external::get_lesson_access_information($this->lesson->id);
$result = external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result);
$this->assertTrue($result['canmanage']);
$this->assertTrue($result['cangrade']);
$this->assertTrue($result['canviewreports']);
}
}
......@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016120501; // The current module version (Date: YYYYMMDDXX)
$plugin->version = 2016120502; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2016112900; // Requires this Moodle version
$plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment