From 7730d89d482146783fc4e8cd816cdea2e48dc331 Mon Sep 17 00:00:00 2001 From: Ruslan Kabalin Date: Fri, 26 Jul 2019 02:23:17 +0100 Subject: [PATCH] More webservice functions. (#409) * Add add_session webservice function skeleton. * Implement add_session. * Refactor add_session to return created session id. * Implement add_attendance * Minor improvements to add_session. * Fix add_attendance and add_session. * Implement remove_session. * Add params and context validation. * Implement remove_attendance. * Missing permissions check for get_session. * Add coverage for remove_attendance. * Refactor unittest and add coverage for add_attendance. * Set default grade if not specified. * Add test coverage for add_session and remove_session. * Add basic validation to all remaining methods. * Rename new WS functions to match Moodle naming convention. * Style changes to address CI test outcomes. --- classes/structure.php | 129 ++++---- db/services.php | 32 ++ externallib.php | 345 ++++++++++++++++++++- lib.php | 6 + tests/attendance_webservices_test.php | 223 -------------- tests/externallib_test.php | 422 ++++++++++++++++++++++++++ tests/generator/lib.php | 5 +- version.php | 2 +- 8 files changed, 875 insertions(+), 289 deletions(-) delete mode 100644 tests/attendance_webservices_test.php create mode 100644 tests/externallib_test.php diff --git a/classes/structure.php b/classes/structure.php index b1a3bc7..52f4c39 100644 --- a/classes/structure.php +++ b/classes/structure.php @@ -452,73 +452,84 @@ class mod_attendance_structure { * @param array $sessions */ public function add_sessions($sessions) { - global $DB; + foreach ($sessions as $sess) { + $this->add_session($sess); + } + } + /** + * Add single session. + * + * @param stdClass $sess + * @return int $sessionid + */ + public function add_session($sess) { + global $DB; $config = get_config('attendance'); - foreach ($sessions as $sess) { - $sess->attendanceid = $this->id; - $sess->automarkcompleted = 0; - if (!isset($sess->automark)) { - $sess->automark = 0; - } - if (empty($config->enablecalendar)) { - // If calendard disabled at site level, don't use it. - $sess->calendarevent = 0; - } - $sess->id = $DB->insert_record('attendance_sessions', $sess); - $description = file_save_draft_area_files($sess->descriptionitemid, - $this->context->id, 'mod_attendance', 'session', $sess->id, - array('subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0), - $sess->description); - $DB->set_field('attendance_sessions', 'description', $description, array('id' => $sess->id)); - - $sess->caleventid = 0; - attendance_create_calendar_event($sess); + $sess->attendanceid = $this->id; + $sess->automarkcompleted = 0; + if (!isset($sess->automark)) { + $sess->automark = 0; + } + if (empty($config->enablecalendar)) { + // If calendard disabled at site level, don't use it. + $sess->calendarevent = 0; + } + $sess->id = $DB->insert_record('attendance_sessions', $sess); + $description = file_save_draft_area_files($sess->descriptionitemid, + $this->context->id, 'mod_attendance', 'session', $sess->id, + array('subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0), + $sess->description); + $DB->set_field('attendance_sessions', 'description', $description, array('id' => $sess->id)); - $infoarray = array(); - $infoarray[] = construct_session_full_date_time($sess->sessdate, $sess->duration); + $sess->caleventid = 0; + attendance_create_calendar_event($sess); - // Trigger a session added event. - $event = \mod_attendance\event\session_added::create(array( - 'objectid' => $this->id, - 'context' => $this->context, - 'other' => array('info' => implode(',', $infoarray)) - )); - $event->add_record_snapshot('course_modules', $this->cm); - $sess->description = $description; - $sess->lasttaken = 0; - $sess->lasttakenby = 0; - if (!isset($sess->studentscanmark)) { - $sess->studentscanmark = 0; - } - if (!isset($sess->autoassignstatus)) { - $sess->autoassignstatus = 0; - } - if (!isset($sess->studentpassword)) { - $sess->studentpassword = ''; - } - if (!isset($sess->subnet)) { - $sess->subnet = ''; - } + $infoarray = array(); + $infoarray[] = construct_session_full_date_time($sess->sessdate, $sess->duration); - if (!isset($sess->preventsharedip)) { - $sess->preventsharedip = 0; - } + // Trigger a session added event. + $event = \mod_attendance\event\session_added::create(array( + 'objectid' => $this->id, + 'context' => $this->context, + 'other' => array('info' => implode(',', $infoarray)) + )); + $event->add_record_snapshot('course_modules', $this->cm); + $sess->description = $description; + $sess->lasttaken = 0; + $sess->lasttakenby = 0; + if (!isset($sess->studentscanmark)) { + $sess->studentscanmark = 0; + } + if (!isset($sess->autoassignstatus)) { + $sess->autoassignstatus = 0; + } + if (!isset($sess->studentpassword)) { + $sess->studentpassword = ''; + } + if (!isset($sess->subnet)) { + $sess->subnet = ''; + } - if (!isset($sess->preventsharediptime)) { - $sess->preventsharediptime = ''; - } - if (!isset($sess->includeqrcode)) { - $sess->includeqrcode = 0; - } - if (!isset($sess->rotateqrcode)) { - $sess->rotateqrcode = 0; - $sess->rotateqrcodesecret = ''; - } - $event->add_record_snapshot('attendance_sessions', $sess); - $event->trigger(); + if (!isset($sess->preventsharedip)) { + $sess->preventsharedip = 0; + } + + if (!isset($sess->preventsharediptime)) { + $sess->preventsharediptime = ''; } + if (!isset($sess->includeqrcode)) { + $sess->includeqrcode = 0; + } + if (!isset($sess->rotateqrcode)) { + $sess->rotateqrcode = 0; + $sess->rotateqrcodesecret = ''; + } + $event->add_record_snapshot('attendance_sessions', $sess); + $event->trigger(); + + return $sess->id; } /** diff --git a/db/services.php b/db/services.php index e860d6c..50fd480 100644 --- a/db/services.php +++ b/db/services.php @@ -25,6 +25,34 @@ defined('MOODLE_INTERNAL') || die(); $functions = array( + 'mod_attendance_add_attendance' => array( + 'classname' => 'mod_wsattendance_external', + 'methodname' => 'add_attendance', + 'classpath' => 'mod/attendance/externallib.php', + 'description' => 'Add attendance instance to course.', + 'type' => 'write', + ), + 'mod_attendance_remove_attendance' => array( + 'classname' => 'mod_wsattendance_external', + 'methodname' => 'remove_attendance', + 'classpath' => 'mod/attendance/externallib.php', + 'description' => 'Delete attendance instance.', + 'type' => 'write', + ), + 'mod_attendance_add_session' => array( + 'classname' => 'mod_wsattendance_external', + 'methodname' => 'add_session', + 'classpath' => 'mod/attendance/externallib.php', + 'description' => 'Add a new session.', + 'type' => 'write', + ), + 'mod_attendance_remove_session' => array( + 'classname' => 'mod_wsattendance_external', + 'methodname' => 'remove_session', + 'classpath' => 'mod/attendance/externallib.php', + 'description' => 'Delete a session.', + 'type' => 'write', + ), 'mod_wsattendance_get_courses_with_today_sessions' => array( 'classname' => 'mod_wsattendance_external', 'methodname' => 'get_courses_with_today_sessions', @@ -54,6 +82,10 @@ $functions = array( $services = array( 'Attendance' => array( 'functions' => array( + 'mod_attendance_add_attendance', + 'mod_attendance_remove_attendance', + 'mod_attendance_add_session', + 'mod_attendance_remove_session', 'mod_wsattendance_get_courses_with_today_sessions', 'mod_wsattendance_get_session', 'mod_wsattendance_update_user_status' diff --git a/externallib.php b/externallib.php index e5a3de5..1877ae2 100644 --- a/externallib.php +++ b/externallib.php @@ -23,7 +23,8 @@ defined('MOODLE_INTERNAL') || die; -require_once("$CFG->libdir/externallib.php"); +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->libdir . '/filelib.php'); require_once(dirname(__FILE__).'/classes/attendance_webservices_handler.php'); /** @@ -33,6 +34,290 @@ require_once(dirname(__FILE__).'/classes/attendance_webservices_handler.php'); */ class mod_wsattendance_external extends external_api { + /** + * Describes the parameters for add_attendance. + * + * @return external_function_parameters + */ + public static function add_attendance_parameters() { + return new external_function_parameters( + array( + 'courseid' => new external_value(PARAM_INT, 'course id'), + 'name' => new external_value(PARAM_TEXT, 'attendance name'), + 'intro' => new external_value(PARAM_RAW, 'attendance description', VALUE_DEFAULT, ''), + 'groupmode' => new external_value(PARAM_INT, 'group mode (0 - no groups, 1 - separate groups, 2 - visible groups)', VALUE_DEFAULT, 0), + ) + ); + } + + /** + * Adds attendance instance to course. + * + * @param int $courseid + * @param string $name + * @param string $intro + * @param int $groupmode + * @return array + */ + public static function add_attendance(int $courseid, $name, $intro, int $groupmode) { + global $CFG, $DB; + require_once($CFG->dirroot.'/course/modlib.php'); + + $params = self::validate_parameters(self::add_attendance_parameters(), array( + 'courseid' => $courseid, + 'name' => $name, + 'intro' => $intro, + 'groupmode' => $groupmode, + )); + + // Get course. + $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST); + + // Verify permissions. + list($module, $context) = can_add_moduleinfo($course, 'attendance', 0); + self::validate_context($context); + require_capability('mod/attendance:addinstance', $context); + + // Verify group mode. + if (!in_array($params['groupmode'], array(NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS))) { + throw new invalid_parameter_exception('Group mode is invalid.'); + } + + // Populate modinfo object. + $moduleinfo = new stdClass(); + $moduleinfo->modulename = 'attendance'; + $moduleinfo->module = $module->id; + + $moduleinfo->name = $params['name']; + $moduleinfo->intro = $params['intro']; + $moduleinfo->introformat = FORMAT_HTML; + + $moduleinfo->section = 0; + $moduleinfo->visible = 1; + $moduleinfo->visibleoncoursepage = 1; + $moduleinfo->cmidnumber = ''; + $moduleinfo->groupmode = $params['groupmode']; + $moduleinfo->groupingid = 0; + + // Add the module to the course. + $moduleinfo = add_moduleinfo($moduleinfo, $course); + + return array('attendanceid' => $moduleinfo->instance); + } + + /** + * Describes add_attendance return values. + * + * @return external_multiple_structure + */ + public static function add_attendance_returns() { + return new external_single_structure(array( + 'attendanceid' => new external_value(PARAM_INT, 'instance id of the created attendance'), + )); + } + + /** + * Describes the parameters for remove_attendance. + * + * @return external_function_parameters + */ + public static function remove_attendance_parameters() { + return new external_function_parameters( + array( + 'attendanceid' => new external_value(PARAM_INT, 'attendance instance id'), + ) + ); + } + + /** + * Remove attendance instance. + * + * @param int $attendanceid + */ + public static function remove_attendance(int $attendanceid) { + $params = self::validate_parameters(self::remove_attendance_parameters(), array( + 'attendanceid' => $attendanceid, + )); + + $cm = get_coursemodule_from_instance('attendance', $params['attendanceid'], 0, false, MUST_EXIST); + + // Check permissions. + $context = context_module::instance($cm->id); + self::validate_context($context); + require_capability('mod/attendance:manageattendances', $context); + + // Delete attendance instance. + attendance_delete_instance($params['attendanceid']); + rebuild_course_cache($cm->course, true); + } + + /** + * Describes remove_attendance return values. + * + * @return void + */ + public static function remove_attendance_returns() { + } + + /** + * Describes the parameters for add_session. + * + * @return external_function_parameters + */ + public static function add_session_parameters() { + return new external_function_parameters( + array( + 'attendanceid' => new external_value(PARAM_INT, 'attendance instance id'), + 'description' => new external_value(PARAM_RAW, 'description', VALUE_DEFAULT, ''), + 'sessiontime' => new external_value(PARAM_INT, 'session start timestamp'), + 'duration' => new external_value(PARAM_INT, 'session duration (seconds)', VALUE_DEFAULT, 0), + 'groupid' => new external_value(PARAM_INT, 'group id', VALUE_DEFAULT, 0), + 'addcalendarevent' => new external_value(PARAM_BOOL, 'add calendar event', VALUE_DEFAULT, true), + ) + ); + } + + /** + * Adds session to attendance instance. + * + * @param int $attendanceid + * @param string $description + * @param int $sessiontime + * @param int $duration + * @param int $groupid + * @param bool $addcalendarevent + * @return array + */ + public static function add_session(int $attendanceid, $description, int $sessiontime, int $duration, int $groupid, bool $addcalendarevent) { + global $USER, $DB; + + $params = self::validate_parameters(self::add_session_parameters(), array( + 'attendanceid' => $attendanceid, + 'description' => $description, + 'sessiontime' => $sessiontime, + 'duration' => $duration, + 'groupid' => $groupid, + 'addcalendarevent' => $addcalendarevent, + )); + + $cm = get_coursemodule_from_instance('attendance', $params['attendanceid'], 0, false, MUST_EXIST); + $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + $attendance = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST); + + // Check permissions. + $context = context_module::instance($cm->id); + self::validate_context($context); + require_capability('mod/attendance:manageattendances', $context); + + // Validate group. + $groupid = $params['groupid']; + $groupmode = (int)groups_get_activity_groupmode($cm); + if ($groupmode === NOGROUPS && $groupid > 0) { + throw new invalid_parameter_exception('Group id is specified, but group mode is disabled for activity'); + } else if ($groupmode === SEPARATEGROUPS && $groupid === 0) { + throw new invalid_parameter_exception('Group id is not specified (or 0) in separate groups mode.'); + } + if ($groupmode === SEPARATEGROUPS || ($groupmode === VISIBLEGROUPS && $groupid > 0)) { + // Determine valid groups + $userid = has_capability('moodle/site:accessallgroups', $context) ? 0 : $USER->id; + $validgroupids = array_map(function($group) { + return $group->id; + }, groups_get_all_groups($course->id, $userid, $cm->groupingid)); + if (!in_array($groupid, $validgroupids)) { + throw new invalid_parameter_exception('Invalid group id'); + } + } + + // Get attendance. + $attendance = new mod_attendance_structure($attendance, $cm, $course, $context); + + // Create session. + $sess = new stdClass(); + $sess->sessdate = $params['sessiontime']; + $sess->duration = $params['duration']; + $sess->descriptionitemid = 0; + $sess->description = $params['description']; + $sess->descriptionformat = FORMAT_HTML; + $sess->calendarevent = (int) $params['addcalendarevent']; + $sess->timemodified = time(); + $sess->studentscanmark = 0; + $sess->autoassignstatus = 0; + $sess->subnet = ''; + $sess->studentpassword = ''; + $sess->automark = 0; + $sess->automarkcompleted = 0; + $sess->absenteereport = get_config('attendance', 'absenteereport_default'); + $sess->includeqrcode = 0; + $sess->subnet = $attendance->subnet; + $sess->statusset = 0; + $sess->groupid = $groupid; + + $sessionid = $attendance->add_session($sess); + return array('sessionid' => $sessionid); + } + + /** + * Describes add_session return values. + * + * @return external_multiple_structure + */ + public static function add_session_returns() { + return new external_single_structure(array( + 'sessionid' => new external_value(PARAM_INT, 'id of the created session'), + )); + } + + /** + * Describes the parameters for remove_session. + * + * @return external_function_parameters + */ + public static function remove_session_parameters() { + return new external_function_parameters( + array( + 'sessionid' => new external_value(PARAM_INT, 'session id'), + ) + ); + } + + /** + * Delete session from attendance instance. + * + * @param int $sessionid + * @return int $sessionid + */ + public static function remove_session(int $sessionid) { + global $DB; + + $params = self::validate_parameters(self::remove_session_parameters(), + array('sessionid' => $sessionid)); + + $session = $DB->get_record('attendance_sessions', array('id' => $params['sessionid']), '*', MUST_EXIST); + $attendance = $DB->get_record('attendance', array('id' => $session->attendanceid), '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('attendance', $attendance->id, 0, false, MUST_EXIST); + $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + + // Check permissions. + $context = context_module::instance($cm->id); + self::validate_context($context); + require_capability('mod/attendance:manageattendances', $context); + + // Get attendance. + $attendance = new mod_attendance_structure($attendance, $cm, $course, $context); + + // Delete session. + $attendance->delete_sessions(array($sessionid)); + attendance_update_users_grade($attendance); + } + + /** + * Describes remove_session return values. + * + * @return void + */ + public static function remove_session_returns() { + } + /** * Get parameter list. * @return external_function_parameters @@ -48,7 +333,18 @@ class mod_wsattendance_external extends external_api { * @return array */ public static function get_courses_with_today_sessions($userid) { - return attendance_handler::get_courses_with_today_sessions($userid); + global $DB; + + $params = self::validate_parameters(self::get_courses_with_today_sessions_parameters(), array( + 'userid' => $userid, + )); + + // Check user id is valid. + $user = $DB->get_record('user', array('id' => $params['userid']), '*', MUST_EXIST); + + // Capability check is done in get_courses_with_today_sessions + // as it switches contexts in loop for each course. + return attendance_handler::get_courses_with_today_sessions($params['userid']); } /** @@ -114,6 +410,28 @@ class mod_wsattendance_external extends external_api { * @return mixed */ public static function get_session($sessionid) { + global $DB; + + $params = self::validate_parameters(self::get_session_parameters(), array( + 'sessionid' => $sessionid, + )); + + $session = $DB->get_record('attendance_sessions', array('id' => $params['sessionid']), '*', MUST_EXIST); + $attendance = $DB->get_record('attendance', array('id' => $session->attendanceid), '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('attendance', $attendance->id, 0, false, MUST_EXIST); + + // Check permissions. + $context = context_module::instance($cm->id); + self::validate_context($context); + $capabilities = array( + 'mod/attendance:manageattendances', + 'mod/attendance:takeattendances', + 'mod/attendance:changeattendances' + ); + if (!has_any_capability($capabilities, $context)) { + throw new invalid_parameter_exception('Invalid session id or no permissions.'); + } + return attendance_handler::get_session($sessionid); } @@ -174,7 +492,28 @@ class mod_wsattendance_external extends external_api { * @param int $statusset */ public static function update_user_status($sessionid, $studentid, $takenbyid, $statusid, $statusset) { - return attendance_handler::update_user_status($sessionid, $studentid, $takenbyid, $statusid, $statusset); + $params = self::validate_parameters(self::update_user_status_parameters(), array( + 'sessionid' => $sessionid, + 'studentid' => $studentid, + 'takenbyid' => $takenbyid, + 'statusid' => $statusid, + 'statusset' => $statusset, + )); + + // Make sure session is open for marking. + $session = $DB->get_record('attendance_sessions', array('id' => $params['sessionid']), '*', MUST_EXIST); + list($canmark, $reason) = attendance_can_student_mark($attforsession); + if (!$canmark) { + throw new invalid_parameter_exception($reason); + } + + // Check user id is valid. + $student = $DB->get_record('user', array('id' => $params['studentid']), '*', MUST_EXIST); + $takenby = $DB->get_record('user', array('id' => $params['takenbyid']), '*', MUST_EXIST); + + // TODO: Verify statusset and statusid. + + return attendance_handler::update_user_status($params['sessionid'], $params['studentid'], $params['takenbyid'], $params['statusid'], $params['statusset']); } /** diff --git a/lib.php b/lib.php index e90558c..6c3f3a5 100644 --- a/lib.php +++ b/lib.php @@ -101,6 +101,12 @@ function attendance_add_instance($attendance) { $attendance->timemodified = time(); + // Default grade (similar to what db fields defaults if no grade attribute is passed), + // but we need it in object for grading update. + if (!isset($attendance->grade)) { + $attendance->grade = 100; + } + $attendance->id = $DB->insert_record('attendance', $attendance); att_add_default_statuses($attendance->id); diff --git a/tests/attendance_webservices_test.php b/tests/attendance_webservices_test.php deleted file mode 100644 index c9a137a..0000000 --- a/tests/attendance_webservices_test.php +++ /dev/null @@ -1,223 +0,0 @@ -. -/** - * Webservices test for attendance plugin. - * - * @package mod_attendance - * @copyright 2015 Caio Bressan Doneda - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); -} - -global $CFG; - -// Include the code to test. -require_once($CFG->dirroot . '/mod/attendance/classes/attendance_webservices_handler.php'); -require_once($CFG->dirroot . '/mod/attendance/classes/structure.php'); - -/** - * This class contains the test cases for the functions in attendance_webservices_handler.php. - * @copyright 2015 Caio Bressan Doneda - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class attendance_webservices_tests extends advanced_testcase { - /** @var coursecat */ - protected $category; - /** @var stdClass */ - protected $course; - /** @var stdClass */ - protected $attendance; - /** @var stdClass */ - protected $teacher; - /** @var array */ - protected $students; - /** @var array */ - protected $sessions; - - /** - * Setup class. - */ - public function setUp() { - $this->category = $this->getDataGenerator()->create_category(); - $this->course = $this->getDataGenerator()->create_course(array('category' => $this->category->id)); - - $this->attendance = $this->create_attendance(); - - $this->create_and_enrol_users(); - - $this->setUser($this->teacher); - - $session = new stdClass(); - $session->sessdate = time(); - $session->duration = 6000; - $session->description = ""; - $session->descriptionformat = 1; - $session->descriptionitemid = 0; - $session->timemodified = time(); - $session->statusset = 0; - $session->groupid = 0; - $session->absenteereport = 1; - $session->calendarevent = 0; - - // Creating two sessions. - $this->sessions[] = $session; - - $this->attendance->add_sessions($this->sessions); - } - - /** - * Create new attendance activity. - */ - private function create_attendance() { - global $DB; - $att = $this->getDataGenerator()->create_module('attendance', array('course' => $this->course->id)); - $cm = $DB->get_record('course_modules', array('id' => $att->cmid)); - unset($att->cmid); - return new mod_attendance_structure($att, $cm, $this->course); - } - - /** Creating 10 students and 1 teacher. */ - protected function create_and_enrol_users() { - $this->students = array(); - for ($i = 0; $i < 10; $i++) { - $student = $this->getDataGenerator()->create_user(); - $this->getDataGenerator()->enrol_user($student->id, $this->course->id, 5); // Enrol as student. - $this->students[] = $student; - } - - $this->teacher = $this->getDataGenerator()->create_user(); - $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, 3); // Enrol as teacher. - } - - public function test_get_courses_with_today_sessions() { - $this->resetAfterTest(true); - - // Just adding the same session again to check if the method returns the right amount of instances. - $this->attendance->add_sessions($this->sessions); - - $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); - - $this->assertTrue(is_array($courseswithsessions)); - $this->assertEquals(count($courseswithsessions), 1); - $course = array_pop($courseswithsessions); - $this->assertEquals($course->fullname, $this->course->fullname); - $attendanceinstance = array_pop($course->attendance_instances); - $this->assertEquals(count($attendanceinstance['today_sessions']), 2); - } - - public function test_get_courses_with_today_sessions_multiple_instances() { - $this->resetAfterTest(true); - - // Make another attendance. - $second = $this->create_attendance(); - - // Just add the same session. - $secondsession = clone $this->sessions[0]; - $secondsession->sessdate += 3600; - - $second->add_sessions([$secondsession]); - - $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); - $this->assertTrue(is_array($courseswithsessions)); - $this->assertEquals(count($courseswithsessions), 1); - $course = array_pop($courseswithsessions); - $this->assertEquals(count($course->attendance_instances), 2); - } - - public function test_get_session() { - $this->resetAfterTest(true); - - $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); - - $course = array_pop($courseswithsessions); - $attendanceinstance = array_pop($course->attendance_instances); - $session = array_pop($attendanceinstance['today_sessions']); - - $sessioninfo = attendance_handler::get_session($session->id); - - $this->assertEquals($this->attendance->id, $sessioninfo->attendanceid); - $this->assertEquals($session->id, $sessioninfo->id); - $this->assertEquals(count($sessioninfo->users), 10); - } - - public function test_get_session_with_group() { - $this->resetAfterTest(true); - - // Create a group in our course, and add some students to it. - $group = new stdClass(); - $group->courseid = $this->course->id; - $group = $this->getDataGenerator()->create_group($group); - - for ($i = 0; $i < 5; $i++) { - $member = new stdClass; - $member->groupid = $group->id; - $member->userid = $this->students[$i]->id; - $this->getDataGenerator()->create_group_member($member); - } - - // Add a session that's identical to the first, but with a group. - $midnight = usergetmidnight(time()); // Check if this test is running during midnight. - $session = clone $this->sessions[0]; - $session->groupid = $group->id; - $session->sessdate += 3600; // Make sure it appears second in the list. - $this->attendance->add_sessions([$session]); - - $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); - - // This test is fragile when running over midnight - check that it is still the same day, if not, run this again. - // This isn't really ideal code, but will hopefully still give a valid test. - if (empty($courseswithsessions) && $midnight !== usergetmidnight(time())) { - $this->attendance->add_sessions([$session]); - $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); - } - - $course = array_pop($courseswithsessions); - $attendanceinstance = array_pop($course->attendance_instances); - $session = array_pop($attendanceinstance['today_sessions']); - - $sessioninfo = attendance_handler::get_session($session->id); - - $this->assertEquals($session->id, $sessioninfo->id); - $this->assertEquals($group->id, $sessioninfo->groupid); - $this->assertEquals(count($sessioninfo->users), 5); - } - - public function test_update_user_status() { - $this->resetAfterTest(true); - - $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); - - $course = array_pop($courseswithsessions); - $attendanceinstance = array_pop($course->attendance_instances); - $session = array_pop($attendanceinstance['today_sessions']); - - $sessioninfo = attendance_handler::get_session($session->id); - - $student = array_pop($sessioninfo->users); - $status = array_pop($sessioninfo->statuses); - $statusset = $sessioninfo->statusset; - attendance_handler::update_user_status($session->id, $student->id, $this->teacher->id, $status->id, $statusset); - - $sessioninfo = attendance_handler::get_session($session->id); - $log = $sessioninfo->attendance_log; - $studentlog = $log[$student->id]; - - $this->assertEquals($status->id, $studentlog->statusid); - } -} diff --git a/tests/externallib_test.php b/tests/externallib_test.php new file mode 100644 index 0000000..6060da5 --- /dev/null +++ b/tests/externallib_test.php @@ -0,0 +1,422 @@ +. + +/** + * External functions test for attendance plugin. + * + * @package mod_attendance + * @category external + * @copyright 2015 Caio Bressan Doneda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); +require_once($CFG->dirroot . '/mod/attendance/classes/attendance_webservices_handler.php'); +require_once($CFG->dirroot . '/mod/attendance/classes/structure.php'); +require_once($CFG->dirroot . '/mod/attendance/externallib.php'); + +/** + * This class contains the test cases for webservices. + * + * @package mod_attendance + * @category external + * @copyright 2015 Caio Bressan Doneda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_attendance_external_testcase extends externallib_advanced_testcase { + /** @var coursecat */ + protected $category; + /** @var stdClass */ + protected $course; + /** @var stdClass */ + protected $attendance; + /** @var stdClass */ + protected $teacher; + /** @var array */ + protected $students; + /** @var array */ + protected $sessions; + + /** + * Setup class. + */ + public function setUp() { + $this->category = $this->getDataGenerator()->create_category(); + $this->course = $this->getDataGenerator()->create_course(array('category' => $this->category->id)); + + $this->attendance = $this->create_attendance(); + + $this->create_and_enrol_users(); + + $this->setUser($this->teacher); + + $session = new stdClass(); + $session->sessdate = time(); + $session->duration = 6000; + $session->description = ""; + $session->descriptionformat = 1; + $session->descriptionitemid = 0; + $session->timemodified = time(); + $session->statusset = 0; + $session->groupid = 0; + $session->absenteereport = 1; + $session->calendarevent = 0; + + // Creating session. + $this->sessions[] = $session; + + $this->attendance->add_sessions($this->sessions); + } + + /** + * Create new attendance activity. + */ + private function create_attendance() { + global $DB; + $att = $this->getDataGenerator()->create_module('attendance', array('course' => $this->course->id)); + $cm = $DB->get_record('course_modules', array('id' => $att->cmid)); + unset($att->cmid); + return new mod_attendance_structure($att, $cm, $this->course); + } + + /** Creating 10 students and 1 teacher. */ + protected function create_and_enrol_users() { + $this->students = array(); + for ($i = 0; $i < 10; $i++) { + $student = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student->id, $this->course->id, 5); // Enrol as student. + $this->students[] = $student; + } + + $this->teacher = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, 3); // Enrol as teacher. + } + + public function test_get_courses_with_today_sessions() { + $this->resetAfterTest(true); + + // Just adding the same session again to check if the method returns the right amount of instances. + $this->attendance->add_sessions($this->sessions); + + $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); + + $this->assertTrue(is_array($courseswithsessions)); + $this->assertEquals(count($courseswithsessions), 1); + $course = array_pop($courseswithsessions); + $this->assertEquals($course->fullname, $this->course->fullname); + $attendanceinstance = array_pop($course->attendance_instances); + $this->assertEquals(count($attendanceinstance['today_sessions']), 2); + } + + public function test_get_courses_with_today_sessions_multiple_instances() { + $this->resetAfterTest(true); + + // Make another attendance. + $second = $this->create_attendance(); + + // Just add the same session. + $secondsession = clone $this->sessions[0]; + $secondsession->sessdate += 3600; + + $second->add_sessions([$secondsession]); + + $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); + $this->assertTrue(is_array($courseswithsessions)); + $this->assertEquals(count($courseswithsessions), 1); + $course = array_pop($courseswithsessions); + $this->assertEquals(count($course->attendance_instances), 2); + } + + public function test_get_session() { + $this->resetAfterTest(true); + + $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); + + $course = array_pop($courseswithsessions); + $attendanceinstance = array_pop($course->attendance_instances); + $session = array_pop($attendanceinstance['today_sessions']); + + $sessioninfo = attendance_handler::get_session($session->id); + + $this->assertEquals($this->attendance->id, $sessioninfo->attendanceid); + $this->assertEquals($session->id, $sessioninfo->id); + $this->assertEquals(count($sessioninfo->users), 10); + } + + public function test_get_session_with_group() { + $this->resetAfterTest(true); + + // Create a group in our course, and add some students to it. + $group = new stdClass(); + $group->courseid = $this->course->id; + $group = $this->getDataGenerator()->create_group($group); + + for ($i = 0; $i < 5; $i++) { + $member = new stdClass; + $member->groupid = $group->id; + $member->userid = $this->students[$i]->id; + $this->getDataGenerator()->create_group_member($member); + } + + // Add a session that's identical to the first, but with a group. + $midnight = usergetmidnight(time()); // Check if this test is running during midnight. + $session = clone $this->sessions[0]; + $session->groupid = $group->id; + $session->sessdate += 3600; // Make sure it appears second in the list. + $this->attendance->add_sessions([$session]); + + $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); + + // This test is fragile when running over midnight - check that it is still the same day, if not, run this again. + // This isn't really ideal code, but will hopefully still give a valid test. + if (empty($courseswithsessions) && $midnight !== usergetmidnight(time())) { + $this->attendance->add_sessions([$session]); + $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); + } + + $course = array_pop($courseswithsessions); + $attendanceinstance = array_pop($course->attendance_instances); + $session = array_pop($attendanceinstance['today_sessions']); + + $sessioninfo = attendance_handler::get_session($session->id); + + $this->assertEquals($session->id, $sessioninfo->id); + $this->assertEquals($group->id, $sessioninfo->groupid); + $this->assertEquals(count($sessioninfo->users), 5); + } + + public function test_update_user_status() { + $this->resetAfterTest(true); + + $courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id); + + $course = array_pop($courseswithsessions); + $attendanceinstance = array_pop($course->attendance_instances); + $session = array_pop($attendanceinstance['today_sessions']); + + $sessioninfo = attendance_handler::get_session($session->id); + + $student = array_pop($sessioninfo->users); + $status = array_pop($sessioninfo->statuses); + $statusset = $sessioninfo->statusset; + attendance_handler::update_user_status($session->id, $student->id, $this->teacher->id, $status->id, $statusset); + + $sessioninfo = attendance_handler::get_session($session->id); + $log = $sessioninfo->attendance_log; + $studentlog = $log[$student->id]; + + $this->assertEquals($status->id, $studentlog->statusid); + } + + public function test_add_attendance() { + global $DB; + $this->resetAfterTest(true); + + $course = $this->getDataGenerator()->create_course(); + + // Become a teacher. + $teacher = self::getDataGenerator()->create_user(); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + $this->setUser($teacher); + + // Check attendance does not exist. + $this->assertCount(0, $DB->get_records('attendance', ['course' => $course->id])); + + // Create attendance. + $result = mod_wsattendance_external::add_attendance($course->id, 'test', 'test', NOGROUPS); + + // Check attendance exist. + $this->assertCount(1, $DB->get_records('attendance', ['course' => $course->id])); + $record = $DB->get_record('attendance', ['id' => $result['attendanceid']]); + $this->assertEquals($record->name, 'test'); + + // Check group. + $cm = get_coursemodule_from_instance('attendance', $result['attendanceid'], 0, false, MUST_EXIST); + $groupmode = (int)groups_get_activity_groupmode($cm); + $this->assertEquals($groupmode, NOGROUPS); + + // Create attendance with "separate groups" group mode. + $result = mod_wsattendance_external::add_attendance($course->id, 'testsepgrp', 'testsepgrp', SEPARATEGROUPS); + + // Check attendance exist. + $this->assertCount(2, $DB->get_records('attendance', ['course' => $course->id])); + $record = $DB->get_record('attendance', ['id' => $result['attendanceid']]); + $this->assertEquals($record->name, 'testsepgrp'); + + // Check group. + $cm = get_coursemodule_from_instance('attendance', $result['attendanceid'], 0, false, MUST_EXIST); + $groupmode = (int)groups_get_activity_groupmode($cm); + $this->assertEquals($groupmode, SEPARATEGROUPS); + + // Create attendance with wrong group mode. + $this->expectException('invalid_parameter_exception'); + $result = mod_wsattendance_external::add_attendance($course->id, 'test1', 'test1', 100); + } + + public function test_remove_attendance() { + global $DB; + $this->resetAfterTest(true); + + // Become a teacher. + $teacher = self::getDataGenerator()->create_user(); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($teacher->id, $this->course->id, $teacherrole->id); + $this->setUser($teacher); + + // Check attendance exists. + $this->assertCount(1, $DB->get_records('attendance', ['course' => $this->course->id])); + $this->assertCount(1, $DB->get_records('attendance_sessions', ['attendanceid' => $this->attendance->id])); + + // Remove attendance. + mod_wsattendance_external::remove_attendance($this->attendance->id); + + // Check attendance removed. + $this->assertCount(0, $DB->get_records('attendance', ['course' => $this->course->id])); + $this->assertCount(0, $DB->get_records('attendance_sessions', ['attendanceid' => $this->attendance->id])); + } + + public function test_add_session() { + global $DB; + $this->resetAfterTest(true); + + $course = $this->getDataGenerator()->create_course(); + $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + + // Become a teacher. + $teacher = self::getDataGenerator()->create_user(); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + $this->setUser($teacher); + + // Create attendances. + $attendancenogroups = mod_wsattendance_external::add_attendance($course->id, 'nogroups', 'test', NOGROUPS); + $attendancesepgroups = mod_wsattendance_external::add_attendance($course->id, 'sepgroups', 'test', SEPARATEGROUPS); + $attendancevisgroups = mod_wsattendance_external::add_attendance($course->id, 'visgroups', 'test', VISIBLEGROUPS); + + // Check attendances exist. + $this->assertCount(3, $DB->get_records('attendance', ['course' => $course->id])); + + // Create session with group in "no groups" attendance. + $this->expectException('invalid_parameter_exception'); + mod_wsattendance_external::add_session($attendancenogroups['attendanceid'], 'test', time(), 3600, $group->id, false); + + // Create session with no group in "separate groups" attendance. + $this->expectException('invalid_parameter_exception'); + mod_wsattendance_external::add_session($attendancesepgroups['attendanceid'], 'test', time(), 3600, 0, false); + + // Create session with invalid group in "visible groups" attendance. + $this->expectException('invalid_parameter_exception'); + mod_wsattendance_external::add_session($attendancevisgroups['attendanceid'], 'test', time(), 3600, $group->id + 100, false); + + // Create session and validate record. + $time = time(); + $duration = 3600; + $result = mod_wsattendance_external::add_session($attendancesepgroups['attendanceid'], + 'testsession', $time, $duration, $group->id, true); + + $this->assertCount(1, $DB->get_records('attendance_sessions', ['id' => $result['sessionid']])); + $record = $DB->get_records('attendance_sessions', ['id' => $result['sessionid']]); + $this->assertEquals($record->description, 'testsession'); + $this->assertEquals($record->attendanceid, $attendancesepgroups['attendanceid']); + $this->assertEquals($record->groupid, $group->id); + $this->assertEquals($record->sessdate, $time); + $this->assertEquals($record->duration, $duration); + $this->assertEquals($record->calendarevent, 1); + } + + public function test_remove_session() { + global $DB; + $this->resetAfterTest(true); + + $course = $this->getDataGenerator()->create_course(); + + // Become a teacher. + $teacher = self::getDataGenerator()->create_user(); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + $this->setUser($teacher); + + // Create attendance. + $attendance = mod_wsattendance_external::add_attendance($course->id, 'test', 'test', NOGROUPS); + + // Check attendance exist. + $this->assertCount(1, $DB->get_records('attendance', ['course' => $course->id])); + + // Create session. + $result0 = mod_wsattendance_external::add_session($attendance['attendanceid'], 'test0', time(), 3600, 0, false); + $result1 = mod_wsattendance_external::add_session($attendance['attendanceid'], 'test1', time(), 3600, 0, false); + + $this->assertCount(2, $DB->get_records('attendance_sessions', ['attendanceid' => $attendance['attendanceid']])); + + // Delete session 0. + mod_wsattendance_external::remove_session($result0['sessionid']); + $this->assertCount(1, $DB->get_records('attendance_sessions', ['attendanceid' => $attendance['attendanceid']])); + + // Delete session 1. + mod_wsattendance_external::remove_session($result1['sessionid']); + $this->assertCount(0, $DB->get_records('attendance_sessions', ['attendanceid' => $attendance['attendanceid']])); + } + + public function test_add_session_creates_calendar_event() { + global $DB; + $this->resetAfterTest(true); + + $course = $this->getDataGenerator()->create_course(); + + // Become a teacher. + $teacher = self::getDataGenerator()->create_user(); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + $this->setUser($teacher); + + // Create attendance. + $attendance = mod_wsattendance_external::add_attendance($course->id, 'test', 'test', NOGROUPS); + + // Check attendance exist. + $this->assertCount(1, $DB->get_records('attendance', ['course' => $course->id])); + + // Prepare events tracing. + $sink = $this->redirectEvents(); + + // Create session with no calendar event. + mod_wsattendance_external::add_session($attendance['attendanceid'], 'test0', time(), 3600, 0, false); + + // Capture the event. + $events = $sink->get_events(); + $sink->clear(); + + // Validate. + $this->assertCount(1, $events); + $this->assertInstanceOf('\mod_attendance\event\session_added', $events[0]); + + // Create session with calendar event. + mod_wsattendance_external::add_session($attendance['attendanceid'], 'test0', time(), 3600, 0, true); + + // Capture the event. + $events = $sink->get_events(); + $sink->clear(); + + // Validate the event. + $this->assertCount(2, $events); + $this->assertInstanceOf('\core\event\calendar_event_created', $events[0]); + $this->assertInstanceOf('\mod_attendance\event\session_added', $events[1]); + } +} diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 7a3dfb8..e9f8c4a 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -37,6 +37,7 @@ class mod_attendance_generator extends testing_module_generator { /** * Create new attendance module instance + * * @param array|stdClass $record * @param array $options * @return stdClass activity record with extra cmid field @@ -61,8 +62,6 @@ class mod_attendance_generator extends testing_module_generator { $record->grade = 100; } - $record->coursemodule = $this->precreate_course_module($record->course, $options); - $id = attendance_add_instance($record, null); - return $this->post_add_instance($id, $record->coursemodule); + return parent::create_instance($record, (array)$options); } } diff --git a/version.php b/version.php index af98ceb..0818188 100755 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019062200; +$plugin->version = 2019072100; $plugin->requires = 2018102700; // Requires 3.6. $plugin->release = '3.6.7'; $plugin->maturity = MATURITY_STABLE;