Compare commits
85 Commits
40-behatfi
...
MOODLE_35_
Author | SHA1 | Date |
---|---|---|
Dan Marsden | f485ce7d89 | 5 years ago |
Dan Marsden | a37d399b03 | 5 years ago |
Dan Marsden | 0d3a162057 | 5 years ago |
Dan Marsden | e078bb9d87 | 5 years ago |
Nadav Kavalerchik | 119946f24a | 5 years ago |
Dan Marsden | 9f35bbe24f | 5 years ago |
Dan Marsden | 29e822459d | 5 years ago |
Dan Marsden | 5af8de8c36 | 5 years ago |
Dan Marsden | 9836d3cfb2 | 5 years ago |
Dan Marsden | baf90b0304 | 6 years ago |
maksudr | 53f075ab6c | 6 years ago |
Dan Marsden | 9bc4d9e8dd | 6 years ago |
maksudr | 2a91d6d7f5 | 6 years ago |
Morgan Harris | 71bc7f15aa | 6 years ago |
Dan Marsden | f9a0425f04 | 6 years ago |
SSH230 | 0d932f23a3 | 6 years ago |
Morgan Harris | 6d4cf2d81d | 6 years ago |
Dan Marsden | 5082677a2d | 6 years ago |
Dan Marsden | fdbabea033 | 6 years ago |
Dan Marsden | c338beb31a | 6 years ago |
Dan Marsden | cf09ca702e | 6 years ago |
Dan Marsden | 650611ee4e | 6 years ago |
Dan Marsden | 24b54e1062 | 6 years ago |
Dan Marsden | 2540512f1e | 6 years ago |
Dan Marsden | ac06e6cf76 | 6 years ago |
Dan Marsden | 0478b99c45 | 6 years ago |
Dan Marsden | 596c63ecba | 6 years ago |
Dan Marsden | 126dc33dc8 | 6 years ago |
Dan Marsden | 253fd5f504 | 6 years ago |
Tõnis Tartes | a412914418 | 6 years ago |
Dan Marsden | b69eb13de4 | 6 years ago |
Dan Marsden | d17a0d1ad3 | 6 years ago |
Dan Marsden | 04042370dd | 6 years ago |
Dan Marsden | d6aedc26b0 | 6 years ago |
Dan Marsden | 31a29ff647 | 6 years ago |
Dan Marsden | a93f6d202a | 6 years ago |
Dan Marsden | e98011347d | 6 years ago |
Dan Marsden | 899abd4911 | 6 years ago |
Dan Marsden | 310522bb93 | 6 years ago |
Dan Marsden | c394dccf87 | 6 years ago |
Dan Marsden | 78f5b91185 | 6 years ago |
Dan Marsden | 4576ce28ca | 6 years ago |
Dan Marsden | a84562228c | 6 years ago |
Dan Marsden | 22c45da5e4 | 6 years ago |
Dan Marsden | 1906820255 | 6 years ago |
Dan Marsden | 1be1851416 | 6 years ago |
Dan Marsden | 1d57b0ebfa | 6 years ago |
Dan Marsden | c5d9b46791 | 6 years ago |
Dan Marsden | edcc1a9c97 | 6 years ago |
Dan Marsden | d632ac4eae | 6 years ago |
Dan Marsden | 818ae8e559 | 6 years ago |
Dan Marsden | 38d5e8cee2 | 6 years ago |
Dan Marsden | f0bbb6795d | 6 years ago |
Dan Marsden | dcac278642 | 6 years ago |
Dan Marsden | f4a7d9fbb2 | 6 years ago |
Dan Marsden | 6324708c86 | 6 years ago |
Dan Marsden | 3a0759067b | 6 years ago |
Dan Marsden | 7ac9b2eb7d | 6 years ago |
Dan Marsden | 8e513a7b6b | 6 years ago |
Dan Marsden | 67c63bfb1b | 6 years ago |
Eoin Campbell | 37a5549c6a | 6 years ago |
Dan Marsden | dd705d75df | 6 years ago |
Dan Marsden | f6f408e1b5 | 6 years ago |
Dan Marsden | 89b974f184 | 6 years ago |
Dan Marsden | 6542eee57e | 6 years ago |
Dan Marsden | fabd021710 | 7 years ago |
Dan Marsden | ec74ba2dbc | 7 years ago |
Dan Marsden | 4855bfcc29 | 7 years ago |
Dan Marsden | 37c42b4bc2 | 7 years ago |
Dan Marsden | a6576acd19 | 7 years ago |
Nick Phillips | 965e7f5e02 | 7 years ago |
Dan Marsden | 4e11ea78b2 | 7 years ago |
Dan Marsden | 8d3ca3d143 | 7 years ago |
Dan Marsden | 6a4a0572aa | 7 years ago |
Dan Marsden | 2522b8b619 | 7 years ago |
Dan Marsden | 6826bb831e | 7 years ago |
Dan Marsden | c94fc8679a | 7 years ago |
Dan Marsden | a7f83c2e8d | 7 years ago |
Dan Marsden | 0400a932dd | 7 years ago |
Dan Marsden | df9edb01a7 | 7 years ago |
Dan Marsden | a971a53fcd | 7 years ago |
Neill Magill | 35c88f84ea | 7 years ago |
Cameron Ball | b8a6731dbe | 7 years ago |
Dan Marsden | 3872569a88 | 7 years ago |
Dan Marsden | dec2abf3fc | 7 years ago |
53 changed files with 2332 additions and 358 deletions
@ -0,0 +1,50 @@ |
|||||
|
<?php |
||||
|
// This file is part of Moodle - http://moodle.org/ |
||||
|
// |
||||
|
// Moodle is free software: you can redistribute it and/or modify |
||||
|
// it under the terms of the GNU General Public License as published by |
||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||
|
// (at your option) any later version. |
||||
|
// |
||||
|
// Moodle is distributed in the hope that it will be useful, |
||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
// GNU General Public License for more details. |
||||
|
// |
||||
|
// You should have received a copy of the GNU General Public License |
||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
|
||||
|
/** |
||||
|
* The mod_attendance instance list viewed event. |
||||
|
* |
||||
|
* @package mod_attendance |
||||
|
* @copyright 2018 Dan Marsden |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
|
||||
|
namespace mod_attendance\event; |
||||
|
defined('MOODLE_INTERNAL') || die(); |
||||
|
|
||||
|
/** |
||||
|
* The mod_attendance instance list viewed event class. |
||||
|
* |
||||
|
* @package mod_attendance |
||||
|
* @copyright 2018 Dan Marsden |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { |
||||
|
/** |
||||
|
* Create the event from course record. |
||||
|
* |
||||
|
* @param \stdClass $course |
||||
|
* @return course_module_instance_list_viewed |
||||
|
*/ |
||||
|
public static function create_from_course(\stdClass $course) { |
||||
|
$params = array( |
||||
|
'context' => \context_course::instance($course->id) |
||||
|
); |
||||
|
$event = self::create($params); |
||||
|
$event->add_record_snapshot('course', $course); |
||||
|
return $event; |
||||
|
} |
||||
|
} |
@ -0,0 +1,451 @@ |
|||||
|
<?php |
||||
|
// This file is part of Moodle - http://moodle.org/ |
||||
|
// |
||||
|
// Moodle is free software: you can redistribute it and/or modify |
||||
|
// it under the terms of the GNU General Public License as published by |
||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||
|
// (at your option) any later version. |
||||
|
// |
||||
|
// Moodle is distributed in the hope that it will be useful, |
||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
// GNU General Public License for more details. |
||||
|
// |
||||
|
// You should have received a copy of the GNU General Public License |
||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
|
||||
|
/** |
||||
|
* Contains the mobile output class for the attendance |
||||
|
* |
||||
|
* @package mod_attendance |
||||
|
* @copyright 2018 Dan Marsden |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
|
||||
|
namespace mod_attendance\output; |
||||
|
|
||||
|
defined('MOODLE_INTERNAL') || die(); |
||||
|
/** |
||||
|
* Mobile output class for the attendance. |
||||
|
* |
||||
|
* @copyright 2018 Dan Marsden |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
class mobile { |
||||
|
|
||||
|
/* |
||||
|
* Subnet warning. - constants used to prevent warnings from showing multiple times. |
||||
|
*/ |
||||
|
const MESSAGE_SUBNET = 10; |
||||
|
|
||||
|
/* |
||||
|
* Prevent shared warning. used to prevent warnings from showing multiple times. |
||||
|
*/ |
||||
|
const MESSAGE_PREVENTSHARED = 30; |
||||
|
|
||||
|
/** |
||||
|
* Returns the initial page when viewing the activity for the mobile app. |
||||
|
* |
||||
|
* @param array $args Arguments from tool_mobile_get_content WS |
||||
|
* @return array HTML, javascript and other data |
||||
|
*/ |
||||
|
public static function mobile_view_activity($args) { |
||||
|
global $OUTPUT, $DB, $USER, $USER, $CFG; |
||||
|
|
||||
|
require_once($CFG->dirroot.'/mod/attendance/locallib.php'); |
||||
|
|
||||
|
$cmid = $args['cmid']; |
||||
|
$courseid = $args['courseid']; |
||||
|
$takenstatus = empty($args['status']) ? '' : $args['status']; |
||||
|
$sessid = empty($args['sessid']) ? '' : $args['sessid']; |
||||
|
$password = empty($args['studentpass']) ? '' : $args['studentpass']; |
||||
|
|
||||
|
// Capabilities check. |
||||
|
$cm = get_coursemodule_from_id('attendance', $cmid); |
||||
|
|
||||
|
require_login($courseid, false , $cm, true, true); |
||||
|
|
||||
|
$context = \context_module::instance($cm->id); |
||||
|
require_capability('mod/attendance:view', $context); |
||||
|
|
||||
|
$attendance = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST); |
||||
|
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); |
||||
|
|
||||
|
$data = array(); // Data to pass to renderer. |
||||
|
$data['cmid'] = $cmid; |
||||
|
$data['courseid'] = $courseid; |
||||
|
$data['attendance'] = $attendance; |
||||
|
$data['timestamp'] = time(); // Used to prevent attendance session marking page to be cached. |
||||
|
|
||||
|
$data['attendancefunction'] = 'mobile_user_form'; |
||||
|
$isteacher = false; |
||||
|
if (has_capability('mod/attendance:takeattendances', $context)) { |
||||
|
$isteacher = true; |
||||
|
$data['attendancefunction'] = 'mobile_teacher_form'; |
||||
|
} |
||||
|
|
||||
|
// Add stats for this use to output. |
||||
|
$pageparams = new \mod_attendance_view_page_params(); |
||||
|
$pageparams->studentid = $USER->id; |
||||
|
$pageparams->group = groups_get_activity_group($cm, true); |
||||
|
$canseegroupsession = true; |
||||
|
if (!empty($sessid) && (!empty($takenstatus) || $isteacher)) { |
||||
|
$session = $DB->get_record('attendance_sessions', array('id' => $sessid)); |
||||
|
$pageparams->grouptype = $session->groupid; |
||||
|
$pageparams->sessionid = $sessid; |
||||
|
|
||||
|
if ($isteacher && !empty($session->groupid)) { |
||||
|
$allowedgroups = groups_get_activity_allowed_groups($cm); |
||||
|
if (!array_key_exists($session->groupid, $allowedgroups)) { |
||||
|
$canseegroupsession = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
$pageparams->mode = \mod_attendance_view_page_params::MODE_THIS_COURSE; |
||||
|
$pageparams->view = 5; // Show all sessions for this course? |
||||
|
|
||||
|
$att = new \mod_attendance_structure($attendance, $cm, $course, $context, $pageparams); |
||||
|
|
||||
|
// Check if this teacher is allowed to view/mark this group session. |
||||
|
|
||||
|
if ($isteacher && $canseegroupsession) { |
||||
|
$keys = array_keys($args); |
||||
|
$userkeys = preg_grep("/status\d+/", $keys); |
||||
|
if (!empty($userkeys)) { // If this is a post from the teacher form. |
||||
|
// Build data to pass to take_from_form_data. |
||||
|
$formdata = new \stdClass(); |
||||
|
foreach ($userkeys as $uk) { |
||||
|
$userid = str_replace('status', '', $uk); |
||||
|
$status = $args[$uk]; |
||||
|
$formdata->{'remarks'.$userid} = ''; |
||||
|
$formdata->{'user'.$userid} = $status; |
||||
|
} |
||||
|
$att->take_from_form_data($formdata); |
||||
|
$data['showmessage'] = true; |
||||
|
$data['messages'][]['string'] = 'attendancesuccess'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Get list of sessions within the next 24hrs and in last 6hrs. |
||||
|
// TODO: provide way of adjusting which sessions to show in app. |
||||
|
$time = time() - (6 * 60 * 60); |
||||
|
|
||||
|
$data['sessions'] = array(); |
||||
|
|
||||
|
$sessions = $DB->get_records_select('attendance_sessions', |
||||
|
'attendanceid = ? AND sessdate > ? ORDER BY sessdate', array($attendance->id, $time)); |
||||
|
|
||||
|
if (!empty($sessions)) { |
||||
|
$userdata = new \attendance_user_data($att, $USER->id, true); |
||||
|
foreach ($sessions as $sess) { |
||||
|
if (!$isteacher && empty($userdata->sessionslog['c'.$sess->id])) { |
||||
|
// This session isn't viewable to this student - probably a group session. |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Check if this teacher is allowed to view this group session. |
||||
|
if ($isteacher && !empty($sess->groupid)) { |
||||
|
$allowedgroups = groups_get_activity_allowed_groups($cm); |
||||
|
if (!array_key_exists($sess->groupid, $allowedgroups)) { |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
list($canmark, $reason) = attendance_can_student_mark($sess); |
||||
|
if (!$isteacher && $reason == 'preventsharederror') { |
||||
|
$data['showmessage'] = true; |
||||
|
$data['messages'][self::MESSAGE_PREVENTSHARED]['string'] = 'preventsharederror'; // Lang string to show as a message. |
||||
|
} |
||||
|
|
||||
|
if ($isteacher || $canmark) { |
||||
|
$html = array('time' => strip_tags(construct_session_full_date_time($sess->sessdate, $sess->duration)), |
||||
|
'groupname' => ''); |
||||
|
if (!empty($sess->groupid)) { |
||||
|
// TODO In-efficient way to get group name - we should get all groups in one query. |
||||
|
$html['groupname'] = $DB->get_field('groups', 'name', array('id' => $sess->groupid)); |
||||
|
} |
||||
|
|
||||
|
// Check if Status already recorded. |
||||
|
if (!$isteacher && !empty($userdata->sessionslog['c'.$sess->id]->statusid)) { |
||||
|
$html['currentstatus'] = $userdata->statuses[$userdata->sessionslog['c'.$sess->id]->statusid]->description; |
||||
|
} else { |
||||
|
// Status has not been recorded - If student, check auto-assign and form data. |
||||
|
$html['sessid'] = $sess->id; |
||||
|
|
||||
|
if (!$isteacher) { |
||||
|
if (!empty($sess->subnet) && !address_in_subnet(getremoteaddr(), $sess->subnet)) { |
||||
|
$data['showmessage'] = true; |
||||
|
$data['messages'][self::MESSAGE_SUBNET]['string'] = 'subnetwrong'; // Lang string to show as a message. |
||||
|
$html['sessid'] = null; // Unset sessid as we cannot record session on this ip. |
||||
|
} else if ($sess->autoassignstatus && empty($sess->studentpassword)) { |
||||
|
$statusid = attendance_session_get_highest_status($att, $sess); |
||||
|
if (empty($statusid)) { |
||||
|
$data['showmessage'] = true; |
||||
|
$data['messages'][]['string'] = 'attendance_no_status'; |
||||
|
} |
||||
|
$take = new \stdClass(); |
||||
|
$take->status = $statusid; |
||||
|
$take->sessid = $sess->id; |
||||
|
$success = $att->take_from_student($take); |
||||
|
|
||||
|
if ($success) { |
||||
|
$html['currentstatus'] = $userdata->statuses[$statusid]->description; |
||||
|
$html['sessid'] = null; // Unset sessid as we have recorded session. |
||||
|
} |
||||
|
} else if ($sess->id == $sessid) { |
||||
|
if (!empty($sess->studentpassword) && $password != $sess->studentpassword) { |
||||
|
// Password incorrect. |
||||
|
$data['showmessage'] = true; |
||||
|
$data['messages'][]['string'] = 'incorrectpasswordshort'; |
||||
|
} else { |
||||
|
$statuses = $att->get_statuses(); |
||||
|
// Check if user has access to all statuses. |
||||
|
foreach ($statuses as $status) { |
||||
|
if ($status->studentavailability === '0') { |
||||
|
unset($statuses[$status->id]); |
||||
|
continue; |
||||
|
} |
||||
|
if (!empty($status->studentavailability) && |
||||
|
time() > $sess->sessdate + ($status->studentavailability * 60)) { |
||||
|
unset($statuses[$status->id]); |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
if ($sess->autoassignstatus) { |
||||
|
// If this is an auto-assign, get the highest status available. |
||||
|
$takenstatus = attendance_session_get_highest_status($att, $sess); |
||||
|
} |
||||
|
|
||||
|
if (empty($statuses[$takenstatus])) { |
||||
|
// This status has probably expired and is not available - they need to choose a new one. |
||||
|
$data['showmessage'] = true; |
||||
|
$data['messages'][]['string'] = 'invalidstatus'; |
||||
|
} else { |
||||
|
$take = new \stdClass(); |
||||
|
$take->status = $takenstatus; |
||||
|
$take->sessid = $sess->id; |
||||
|
$success = $att->take_from_student($take); |
||||
|
|
||||
|
if ($success) { |
||||
|
$html['currentstatus'] = $userdata->statuses[$takenstatus]->description; |
||||
|
$html['sessid'] = null; // Unset sessid as we have recorded session. |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$data['sessions'][] = $html; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$summary = new \mod_attendance_summary($att->id, array($USER->id), $att->pageparams->startdate, |
||||
|
$att->pageparams->enddate); |
||||
|
$data['summary'] = $summary->get_all_sessions_summary_for($USER->id); |
||||
|
|
||||
|
return [ |
||||
|
'templates' => [ |
||||
|
[ |
||||
|
'id' => 'main', |
||||
|
'html' => $OUTPUT->render_from_template('mod_attendance/mobile_view_page', $data), |
||||
|
], |
||||
|
], |
||||
|
'javascript' => '', |
||||
|
'otherdata' => '' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the form to take attendance for the mobile app. |
||||
|
* |
||||
|
* @param array $args Arguments from tool_mobile_get_content WS |
||||
|
* @return array HTML, javascript and other data |
||||
|
*/ |
||||
|
public static function mobile_user_form($args) { |
||||
|
global $OUTPUT, $DB, $CFG; |
||||
|
|
||||
|
require_once($CFG->dirroot.'/mod/attendance/locallib.php'); |
||||
|
|
||||
|
$args = (object) $args; |
||||
|
$cmid = $args->cmid; |
||||
|
$courseid = $args->courseid; |
||||
|
$sessid = $args->sessid; |
||||
|
|
||||
|
// Capabilities check. |
||||
|
$cm = get_coursemodule_from_id('attendance', $cmid); |
||||
|
|
||||
|
require_login($courseid, false , $cm, true, true); |
||||
|
|
||||
|
$context = \context_module::instance($cm->id); |
||||
|
require_capability('mod/attendance:view', $context); |
||||
|
|
||||
|
$attendance = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST); |
||||
|
$attforsession = $DB->get_record('attendance_sessions', array('id' => $sessid), '*', MUST_EXIST); |
||||
|
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); |
||||
|
|
||||
|
$pageparams = new \mod_attendance_sessions_page_params(); |
||||
|
$pageparams->sessionid = $sessid; |
||||
|
$att = new \mod_attendance_structure($attendance, $cm, $course, $context, $pageparams); |
||||
|
|
||||
|
$data = array(); // Data to pass to renderer. |
||||
|
$data['attendance'] = $attendance; |
||||
|
$data['cmid'] = $cmid; |
||||
|
$data['courseid'] = $courseid; |
||||
|
$data['sessid'] = $sessid; |
||||
|
$data['messages'] = array(); |
||||
|
$data['showmessage'] = false; |
||||
|
$data['showstatuses'] = true; |
||||
|
$data['showpassword'] = false; |
||||
|
$data['statuses'] = array(); |
||||
|
$data['disabledduetotime'] = false; |
||||
|
|
||||
|
list($canmark, $reason) = attendance_can_student_mark($attforsession, false); |
||||
|
// Check if subnet is set and if the user is in the allowed range. |
||||
|
if (!$canmark) { |
||||
|
$data['messages'][]['string'] = $reason; // Lang string to show as a message. |
||||
|
$data['showstatuses'] = false; // Hide all statuses. |
||||
|
} else if (!empty($attforsession->subnet) && !address_in_subnet(getremoteaddr(), $attforsession->subnet)) { |
||||
|
$data['messages'][self::MESSAGE_SUBNET]['string'] = 'subnetwrong'; // Lang string to show as a message. |
||||
|
$data['showstatuses'] = false; // Hide all statuses. |
||||
|
} else if ($attforsession->autoassignstatus && empty($attforsession->studentpassword)) { |
||||
|
// This shouldn't happen as the main function should handle this scenario. |
||||
|
// Hide all status just in case the user manages to hit this page accidentally. |
||||
|
$data['showstatuses'] = false; // Hide all statuses. |
||||
|
} else { |
||||
|
// Show user form for submitting a status. |
||||
|
$statuses = $att->get_statuses(); |
||||
|
// Check if user has access to all statuses. |
||||
|
foreach ($statuses as $status) { |
||||
|
if ($status->studentavailability === '0') { |
||||
|
unset($statuses[$status->id]); |
||||
|
continue; |
||||
|
} |
||||
|
if (!empty($status->studentavailability) && |
||||
|
time() > $attforsession->sessdate + ($status->studentavailability * 60)) { |
||||
|
unset($statuses[$status->id]); |
||||
|
continue; |
||||
|
$data['disabledduetotime'] = true; |
||||
|
} |
||||
|
$data['statuses'][] = array('stid' => $status->id, 'description' => $status->description); |
||||
|
} |
||||
|
if (empty($data['statuses'])) { |
||||
|
$data['messages'][]['string'] = 'attendance_no_status'; |
||||
|
$data['showstatuses'] = false; // Hide all statuses. |
||||
|
} else if (!empty($attforsession->studentpassword)) { |
||||
|
$data['showpassword'] = true; |
||||
|
if ($attforsession->autoassignstatus) { |
||||
|
// If this is an auto status - don't show the statuses, but show the form. |
||||
|
$data['statuses'] = array(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (!empty($data['messages'])) { |
||||
|
$data['showmessage'] = true; |
||||
|
} |
||||
|
|
||||
|
return [ |
||||
|
'templates' => [ |
||||
|
[ |
||||
|
'id' => 'main', |
||||
|
'html' => $OUTPUT->render_from_template('mod_attendance/mobile_user_form', $data), |
||||
|
'cache-view' => false |
||||
|
], |
||||
|
], |
||||
|
'javascript' => '', |
||||
|
'otherdata' => '' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the form to take attendance for the mobile app. |
||||
|
* |
||||
|
* @param array $args Arguments from tool_mobile_get_content WS |
||||
|
* @return array HTML, javascript and other data |
||||
|
*/ |
||||
|
public static function mobile_teacher_form($args) { |
||||
|
global $OUTPUT, $DB, $CFG, $PAGE; |
||||
|
|
||||
|
require_once($CFG->dirroot.'/mod/attendance/locallib.php'); |
||||
|
|
||||
|
$args = (object) $args; |
||||
|
$cmid = $args->cmid; |
||||
|
$courseid = $args->courseid; |
||||
|
$sessid = $args->sessid; |
||||
|
|
||||
|
// Capabilities check. |
||||
|
$cm = get_coursemodule_from_id('attendance', $cmid); |
||||
|
|
||||
|
require_login($courseid, false , $cm, true, true); |
||||
|
|
||||
|
$context = \context_module::instance($cm->id); |
||||
|
require_capability('mod/attendance:takeattendances', $context); |
||||
|
|
||||
|
$attendance = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST); |
||||
|
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); |
||||
|
|
||||
|
$pageparams = new \mod_attendance_sessions_page_params(); |
||||
|
$pageparams->sessionid = $sessid; |
||||
|
$att = new \mod_attendance_structure($attendance, $cm, $course, $context, $pageparams); |
||||
|
|
||||
|
$data = array(); // Data to pass to renderer. |
||||
|
$data['attendance'] = $attendance; |
||||
|
$data['cmid'] = $cmid; |
||||
|
$data['courseid'] = $courseid; |
||||
|
$data['sessid'] = $sessid; |
||||
|
$data['messages'] = array(); |
||||
|
$data['showmessage'] = false; |
||||
|
$data['statuses'] = array(); |
||||
|
$data['btnargs'] = ''; // Stores list of userid status args that should be added to form post. |
||||
|
|
||||
|
$statuses = $att->get_statuses(); |
||||
|
$otherdata = array(); |
||||
|
$existinglog = $DB->get_records('attendance_log', |
||||
|
array('sessionid' => $sessid), '', 'studentid,statusid'); |
||||
|
foreach ($existinglog as $log) { |
||||
|
if (!empty($log->statusid)) { |
||||
|
$otherdata['status'.$log->studentid] = $log->statusid; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
foreach ($statuses as $status) { |
||||
|
$data['statuses'][] = array('stid' => $status->id, 'acronym' => $status->acronym, |
||||
|
'description' => $status->description, 'selectall' => ''); |
||||
|
} |
||||
|
|
||||
|
$data['users'] = array(); |
||||
|
$users = $att->get_users($att->get_session_info($sessid)->groupid, 0); |
||||
|
foreach ($users as $user) { |
||||
|
$userpicture = new \user_picture($user); |
||||
|
$userpicture->size = 1; // Size f1. |
||||
|
$profileimageurl = $userpicture->get_url($PAGE)->out(false); |
||||
|
$data['users'][] = array('userid' => $user->id, 'fullname' => $user->fullname, 'profileimageurl' => $profileimageurl); |
||||
|
// Generate args to use in submission button here. |
||||
|
$data['btnargs'] .= ', status'. $user->id. ': CONTENT_OTHERDATA.status'. $user->id; |
||||
|
// Really Hacky way to do a select-all. This really needs to be moved into a JS function within the app. |
||||
|
foreach ($statuses as $status) { |
||||
|
foreach ($data['statuses'] as $id => $st) { // Statuses not ordered by statusid. |
||||
|
if ($st['stid'] == $status->id) { // Find the item that we need to add to. |
||||
|
$data['statuses'][$id]['selectall'] .= "CONTENT_OTHERDATA.status".$user->id."=".$status->id.";"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (!empty($data['messages'])) { |
||||
|
$data['showmessage'] = true; |
||||
|
} |
||||
|
|
||||
|
return [ |
||||
|
'templates' => [ |
||||
|
[ |
||||
|
'id' => 'main', |
||||
|
'html' => $OUTPUT->render_from_template('mod_attendance/mobile_teacher_form', $data), |
||||
|
'cache-view' => false |
||||
|
], |
||||
|
], |
||||
|
'javascript' => '', |
||||
|
'otherdata' => $otherdata |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,495 @@ |
|||||
|
<?php |
||||
|
// This file is part of Moodle - http://moodle.org/ |
||||
|
// |
||||
|
// Moodle is free software: you can redistribute it and/or modify |
||||
|
// it under the terms of the GNU General Public License as published by |
||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||
|
// (at your option) any later version. |
||||
|
// |
||||
|
// Moodle is distributed in the hope that it will be useful, |
||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
// GNU General Public License for more details. |
||||
|
// |
||||
|
// You should have received a copy of the GNU General Public License |
||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
|
||||
|
/** |
||||
|
* mod_attendance Data provider. |
||||
|
* |
||||
|
* @package mod_attendance |
||||
|
* @copyright 2018 Cameron Ball <cameron@cameron1729.xyz> |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
|
||||
|
namespace mod_attendance\privacy; |
||||
|
defined('MOODLE_INTERNAL') || die(); |
||||
|
|
||||
|
use context; |
||||
|
use context_module; |
||||
|
use core_privacy\local\metadata\collection; |
||||
|
use core_privacy\local\request\{writer, transform, helper, contextlist, approved_contextlist}; |
||||
|
use stdClass; |
||||
|
|
||||
|
/** |
||||
|
* Data provider for mod_attendance. |
||||
|
* |
||||
|
* @copyright 2018 Cameron Ball <cameron@cameron1729.xyz> |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
final class provider implements |
||||
|
\core_privacy\local\request\plugin\provider, |
||||
|
\core_privacy\local\metadata\provider |
||||
|
{ |
||||
|
|
||||
|
/** |
||||
|
* Returns meta data about this system. |
||||
|
* |
||||
|
* @param collection $collection The initialised collection to add items to. |
||||
|
* @return collection A listing of user data stored through this system. |
||||
|
*/ |
||||
|
public static function get_metadata(collection $collection) : collection { |
||||
|
$collection->add_database_table( |
||||
|
'attendance_log', |
||||
|
[ |
||||
|
'sessionid' => 'privacy:metadata:sessionid', |
||||
|
'studentid' => 'privacy:metadata:studentid', |
||||
|
'statusid' => 'privacy:metadata:statusid', |
||||
|
'statusset' => 'privacy:metadata:statusset', |
||||
|
'timetaken' => 'privacy:metadata:timetaken', |
||||
|
'takenby' => 'privacy:metadata:takenby', |
||||
|
'remarks' => 'privacy:metadata:remarks', |
||||
|
'ipaddress' => 'privacy:metadata:ipaddress' |
||||
|
], |
||||
|
'privacy:metadata:attendancelog' |
||||
|
); |
||||
|
|
||||
|
$collection->add_database_table( |
||||
|
'attendance_sessions', |
||||
|
[ |
||||
|
'groupid' => 'privacy:metadata:groupid', |
||||
|
'sessdate' => 'privacy:metadata:sessdate', |
||||
|
'duration' => 'privacy:metadata:duration', |
||||
|
'lasttaken' => 'privacy:metadata:lasttaken', |
||||
|
'lasttakenby' => 'privacy:metadata:lasttakenby', |
||||
|
'timemodified' => 'privacy:metadata:timemodified' |
||||
|
], |
||||
|
'privacy:metadata:attendancesessions' |
||||
|
); |
||||
|
|
||||
|
$collection->add_database_table( |
||||
|
'attendance_warning_done', |
||||
|
[ |
||||
|
'notifyid' => 'privacy:metadata:notifyid', |
||||
|
'userid' => 'privacy:metadata:userid', |
||||
|
'timesent' => 'privacy:metadata:timesent' |
||||
|
], |
||||
|
'privacy:metadata:attendancewarningdone' |
||||
|
); |
||||
|
|
||||
|
return $collection; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the list of contexts that contain user information for the specified user. |
||||
|
* |
||||
|
* In the case of attendance, that is any attendance where a student has had their |
||||
|
* attendance taken or has taken attendance for someone else. |
||||
|
* |
||||
|
* @param int $userid The user to search. |
||||
|
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. |
||||
|
*/ |
||||
|
public static function get_contexts_for_userid(int $userid) : contextlist { |
||||
|
return (new contextlist)->add_from_sql( |
||||
|
"SELECT ctx.id |
||||
|
FROM {course_modules} cm |
||||
|
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename |
||||
|
JOIN {attendance} a ON cm.instance = a.id |
||||
|
JOIN {attendance_sessions} asess ON asess.attendanceid = a.id |
||||
|
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel |
||||
|
JOIN {attendance_log} al ON asess.id = al.sessionid AND (al.studentid = :userid OR al.takenby = :takenbyid)", |
||||
|
[ |
||||
|
'modulename' => 'attendance', |
||||
|
'contextlevel' => CONTEXT_MODULE, |
||||
|
'userid' => $userid, |
||||
|
'takenbyid' => $userid |
||||
|
] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete all data for all users in the specified context. |
||||
|
* |
||||
|
* @param context $context The specific context to delete data for. |
||||
|
*/ |
||||
|
public static function delete_data_for_all_users_in_context(context $context) { |
||||
|
global $DB; |
||||
|
|
||||
|
if (!$context instanceof context_module) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!$cm = get_coursemodule_from_id('attendance', $context->instanceid)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Delete all information recorded against sessions associated with this module. |
||||
|
$DB->delete_records_select( |
||||
|
'attendance_log', |
||||
|
"sessionid IN (SELECT id FROM {attendance_sessions} WHERE attendanceid = :attendanceid", |
||||
|
[ |
||||
|
'attendanceid' => $cm->instance |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// Delete all completed warnings associated with a warning associated with this module. |
||||
|
$DB->delete_records_select( |
||||
|
'attendance_warning_done', |
||||
|
"notifyid IN (SELECT id from {attendance_warning} WHERE idnumber = :attendanceid)", |
||||
|
['attendanceid' => $cm->instance] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete all user data for the specified user, in the specified contexts. |
||||
|
* |
||||
|
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for. |
||||
|
*/ |
||||
|
public static function delete_data_for_user(approved_contextlist $contextlist) { |
||||
|
global $DB; |
||||
|
$userid = (int)$contextlist->get_user()->id; |
||||
|
|
||||
|
foreach ($contextlist as $context) { |
||||
|
if (!$context instanceof context_module) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (!$cm = get_coursemodule_from_id('attendance', $context->instanceid)) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
$attendanceid = (int)$DB->get_record('attendance', ['id' => $cm->instance])->id; |
||||
|
$sessionids = array_keys( |
||||
|
$DB->get_records('attendance_sessions', ['attendanceid' => $attendanceid]) |
||||
|
); |
||||
|
|
||||
|
self::delete_user_from_session_attendance_log($userid, $sessionids); |
||||
|
self::delete_user_from_sessions($userid, $sessionids); |
||||
|
self::delete_user_from_attendance_warnings_log($userid, $attendanceid); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Export all user data for the specified user, in the specified contexts. |
||||
|
* |
||||
|
* @param approved_contextlist $contextlist The approved contexts to export information for. |
||||
|
*/ |
||||
|
public static function export_user_data(approved_contextlist $contextlist) { |
||||
|
global $DB; |
||||
|
|
||||
|
$params = [ |
||||
|
'modulename' => 'attendance', |
||||
|
'contextlevel' => CONTEXT_MODULE, |
||||
|
'studentid' => $contextlist->get_user()->id, |
||||
|
'takenby' => $contextlist->get_user()->id |
||||
|
]; |
||||
|
|
||||
|
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); |
||||
|
|
||||
|
$sql = "SELECT |
||||
|
al.*, |
||||
|
asess.id as session, |
||||
|
asess.description, |
||||
|
ctx.id as contextid, |
||||
|
a.name as attendancename, |
||||
|
a.id as attendanceid, |
||||
|
statuses.description as statusdesc, statuses.grade as statusgrade |
||||
|
FROM {course_modules} cm |
||||
|
JOIN {attendance} a ON cm.instance = a.id |
||||
|
JOIN {attendance_sessions} asess ON asess.attendanceid = a.id |
||||
|
JOIN {attendance_log} al on (al.sessionid = asess.id AND (studentid = :studentid OR al.takenby = :takenby)) |
||||
|
JOIN {context} ctx ON cm.id = ctx.instanceid |
||||
|
JOIN {attendance_statuses} statuses ON statuses.id = al.statusid |
||||
|
WHERE (ctx.id {$contextsql})"; |
||||
|
|
||||
|
$attendances = $DB->get_records_sql($sql, $params + $contextparams); |
||||
|
|
||||
|
self::export_attendance_logs( |
||||
|
get_string('attendancestaken', 'mod_attendance'), |
||||
|
array_filter( |
||||
|
$attendances, |
||||
|
function(stdClass $attendance) use ($contextlist) : bool { |
||||
|
return $attendance->takenby == $contextlist->get_user()->id; |
||||
|
} |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
self::export_attendance_logs( |
||||
|
get_string('attendanceslogged', 'mod_attendance'), |
||||
|
array_filter( |
||||
|
$attendances, |
||||
|
function(stdClass $attendance) use ($contextlist) : bool { |
||||
|
return $attendance->studentid == $contextlist->get_user()->id; |
||||
|
} |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
self::export_attendances( |
||||
|
$contextlist->get_user(), |
||||
|
$attendances, |
||||
|
self::group_by_property( |
||||
|
$DB->get_records_sql( |
||||
|
"SELECT |
||||
|
*, |
||||
|
a.id as attendanceid |
||||
|
FROM {attendance_warning_done} awd |
||||
|
JOIN {attendance_warning} aw ON awd.notifyid = aw.id |
||||
|
JOIN {attendance} a on aw.idnumber = a.id |
||||
|
WHERE userid = :userid", |
||||
|
['userid' => $contextlist->get_user()->id] |
||||
|
), |
||||
|
'notifyid' |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete a user from session logs. |
||||
|
* |
||||
|
* @param int $userid The id of the user to remove. |
||||
|
* @param array $sessionids Array of session ids from which to remove the student from the relevant logs. |
||||
|
*/ |
||||
|
private static function delete_user_from_session_attendance_log(int $userid, array $sessionids) { |
||||
|
global $DB; |
||||
|
|
||||
|
// Delete records where user was marked as attending. |
||||
|
list($sessionsql, $sessionparams) = $DB->get_in_or_equal($sessionids, SQL_PARAMS_NAMED); |
||||
|
$DB->delete_records_select( |
||||
|
'attendance_log', |
||||
|
"(studentid = :studentid) AND sessionid $sessionsql", |
||||
|
['studentid' => $userid] + $sessionparams |
||||
|
); |
||||
|
|
||||
|
// Get every log record where user took the attendance. |
||||
|
$attendancetakenids = array_keys( |
||||
|
$DB->get_records_sql( |
||||
|
"SELECT * from {attendance_log} |
||||
|
WHERE takenby = :takenbyid AND sessionid $sessionsql", |
||||
|
['takenbyid' => $userid] + $sessionparams |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
if (!$attendancetakenids) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Don't delete the record from the log, but update to site admin taking attendance. |
||||
|
list($attendancetakensql, $attendancetakenparams) = $DB->get_in_or_equal($attendancetakenids, SQL_PARAMS_NAMED); |
||||
|
$DB->set_field_select( |
||||
|
'attendance_log', |
||||
|
'takenby', |
||||
|
2, |
||||
|
"id $attendancetakensql", |
||||
|
$attendancetakenparams |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete a user from sessions. |
||||
|
* |
||||
|
* Not much user data is stored in a session, but it's possible that a user id is saved |
||||
|
* in the "lasttakenby" field. |
||||
|
* |
||||
|
* @param int $userid The id of the user to remove. |
||||
|
* @param array $sessionids Array of session ids from which to remove the student. |
||||
|
*/ |
||||
|
private static function delete_user_from_sessions(int $userid, array $sessionids) { |
||||
|
global $DB; |
||||
|
|
||||
|
// Get all sessions where user was last to mark attendance. |
||||
|
list($sessionsql, $sessionparams) = $DB->get_in_or_equal($sessionids, SQL_PARAMS_NAMED); |
||||
|
$sessionstaken = $DB->get_records_sql( |
||||
|
"SELECT * from {attendance_sessions} |
||||
|
WHERE lasttakenby = :lasttakenbyid AND id $sessionsql", |
||||
|
['lasttakenbyid' => $userid] + $sessionparams |
||||
|
); |
||||
|
|
||||
|
if (!$sessionstaken) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Don't delete the session, but update last taken by to the site admin. |
||||
|
list($sessionstakensql, $sessionstakenparams) = $DB->get_in_or_equal(array_keys($sessionstaken), SQL_PARAMS_NAMED); |
||||
|
$DB->set_field_select( |
||||
|
'attendance_sessions', |
||||
|
'lasttakenby', |
||||
|
2, |
||||
|
"id $sessionstakensql", |
||||
|
$sessionstakenparams |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete a user from the attendance waring log. |
||||
|
* |
||||
|
* @param int $userid The id of the user to remove. |
||||
|
* @param int $attendanceid The id of the attendance instance to remove the relevant warnings from. |
||||
|
*/ |
||||
|
private static function delete_user_from_attendance_warnings_log(int $userid, int $attendanceid) { |
||||
|
global $DB; |
||||
|
|
||||
|
// Get all warnings because the user could have their ID listed in the thirdpartyemails column as a comma delimited string. |
||||
|
$warnings = $DB->get_records( |
||||
|
'attendance_warning', |
||||
|
['idnumber' => $attendanceid] |
||||
|
); |
||||
|
|
||||
|
if (!$warnings) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Update the third party emails list for all the relevant warnings. |
||||
|
$updatedwarnings = array_map( |
||||
|
function(stdClass $warning) use ($userid) : stdClass { |
||||
|
$warning->thirdpartyemails = implode(',', array_diff(explode(',', $warning->thirdpartyemails), [$userid])); |
||||
|
return $warning; |
||||
|
}, |
||||
|
array_filter( |
||||
|
$warnings, |
||||
|
function (stdClass $warning) use ($userid) : bool { |
||||
|
return in_array($userid, explode(',', $warning->thirdpartyemails)); |
||||
|
} |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
// Sadly need to update each individually, no way to bulk update as all the thirdpartyemails field can be different. |
||||
|
foreach ($updatedwarnings as $updatedwarning) { |
||||
|
$DB->update_record('attendance_warning', $updatedwarning); |
||||
|
} |
||||
|
|
||||
|
// Delete any record of the user being notified. |
||||
|
list($warningssql, $warningsparams) = $DB->get_in_or_equal(array_keys($warnings), SQL_PARAMS_NAMED); |
||||
|
$DB->delete_records_select( |
||||
|
'attendance_warning_done', |
||||
|
"userid = :userid AND notifyid $warningssql", |
||||
|
['userid' => $userid] + $warningsparams |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Helper function to group an array of stdClasses by a common property. |
||||
|
* |
||||
|
* @param array $classes An array of classes to group. |
||||
|
* @param string $property A common property to group the classes by. |
||||
|
*/ |
||||
|
private static function group_by_property(array $classes, string $property) : array { |
||||
|
return array_reduce( |
||||
|
$classes, |
||||
|
function (array $classes, stdClass $class) use ($property) : array { |
||||
|
$classes[$class->{$property}][] = $class; |
||||
|
return $classes; |
||||
|
}, |
||||
|
[] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Helper function to transform a row from the database in to session data to export. |
||||
|
* |
||||
|
* The properties of the "dbrow" are very specific to the result of the SQL from |
||||
|
* the export_user_data function. |
||||
|
* |
||||
|
* @param stdClass $dbrow A row from the database containing session information. |
||||
|
* @return stdClass The transformed row. |
||||
|
*/ |
||||
|
private static function transform_db_row_to_session_data(stdClass $dbrow) : stdClass { |
||||
|
return (object) [ |
||||
|
'name' => $dbrow->attendancename, |
||||
|
'session' => $dbrow->session, |
||||
|
'takenbyid' => $dbrow->takenby, |
||||
|
'studentid' => $dbrow->studentid, |
||||
|
'status' => $dbrow->statusdesc, |
||||
|
'grade' => $dbrow->statusgrade, |
||||
|
'sessiondescription' => $dbrow->description, |
||||
|
'timetaken' => transform::datetime($dbrow->timetaken), |
||||
|
'remarks' => $dbrow->remarks, |
||||
|
'ipaddress' => $dbrow->ipaddress |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Helper function to transform a row from the database in to warning data to export. |
||||
|
* |
||||
|
* The properties of the "dbrow" are very specific to the result of the SQL from |
||||
|
* the export_user_data function. |
||||
|
* |
||||
|
* @param stdClass $warning A row from the database containing warning information. |
||||
|
* @return stdClass The transformed row. |
||||
|
*/ |
||||
|
private static function transform_warning_data(stdClass $warning) : stdClass { |
||||
|
return (object) [ |
||||
|
'timesent' => transform::datetime($warning->timesent), |
||||
|
'thirdpartyemails' => $warning->thirdpartyemails, |
||||
|
'subject' => $warning->emailsubject, |
||||
|
'body' => $warning->emailcontent |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Helper function to export attendance logs. |
||||
|
* |
||||
|
* The array of "attendances" is actually the result returned by the SQL in export_user_data. |
||||
|
* It is more of a list of sessions. Which is why it needs to be grouped by context id. |
||||
|
* |
||||
|
* @param string $path The path in the export (relative to the current context). |
||||
|
* @param array $attendances Array of attendances to export the logs for. |
||||
|
*/ |
||||
|
private static function export_attendance_logs(string $path, array $attendances) { |
||||
|
$attendancesbycontextid = self::group_by_property($attendances, 'contextid'); |
||||
|
|
||||
|
foreach ($attendancesbycontextid as $contextid => $sessions) { |
||||
|
$context = context::instance_by_id($contextid); |
||||
|
$sessionsbyid = self::group_by_property($sessions, 'sessionid'); |
||||
|
|
||||
|
foreach ($sessionsbyid as $sessionid => $sessions) { |
||||
|
writer::with_context($context)->export_data( |
||||
|
[get_string('session', 'attendance') . ' ' . $sessionid, $path], |
||||
|
(object)[array_map([self::class, 'transform_db_row_to_session_data'], $sessions)] |
||||
|
); |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Helper function to export attendances (and associated warnings for the user). |
||||
|
* |
||||
|
* The array of "attendances" is actually the result returned by the SQL in export_user_data. |
||||
|
* It is more of a list of sessions. Which is why it needs to be grouped by context id. |
||||
|
* |
||||
|
* @param stdClass $user The user to export attendances for. This is needed to retrieve context data. |
||||
|
* @param array $attendances Array of attendances to export. |
||||
|
* @param array $warningsmap Mapping between an attendance id and warnings. |
||||
|
*/ |
||||
|
private static function export_attendances(stdClass $user, array $attendances, array $warningsmap) { |
||||
|
$attendancesbycontextid = self::group_by_property($attendances, 'contextid'); |
||||
|
|
||||
|
foreach ($attendancesbycontextid as $contextid => $attendance) { |
||||
|
$context = context::instance_by_id($contextid); |
||||
|
|
||||
|
// It's "safe" to get the attendanceid from the first element in the array - since they're grouped by context. |
||||
|
// i.e., module context. |
||||
|
// The reason there can be more than one "attendance" is that the attendances array will contain multiple records |
||||
|
// for the same attendance instance if there are multiple sessions. It is not the same as a raw record from the |
||||
|
// attendances table. See the SQL in export_user_data. |
||||
|
$warnings = array_map([self::class, 'transform_warning_data'], $warningsmap[$attendance[0]->attendanceid] ?? []); |
||||
|
|
||||
|
writer::with_context($context)->export_data( |
||||
|
[], |
||||
|
(object)array_merge( |
||||
|
(array) helper::get_context_data($context, $user), |
||||
|
['warnings' => $warnings] |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,70 @@ |
|||||
|
<?php |
||||
|
// This file is part of Moodle - http://moodle.org/ |
||||
|
// |
||||
|
// Moodle is free software: you can redistribute it and/or modify |
||||
|
// it under the terms of the GNU General Public License as published by |
||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||
|
// (at your option) any later version. |
||||
|
// |
||||
|
// Moodle is distributed in the hope that it will be useful, |
||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
// GNU General Public License for more details. |
||||
|
// |
||||
|
// You should have received a copy of the GNU General Public License |
||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
|
||||
|
/** |
||||
|
* Defines mobile handlers. |
||||
|
* |
||||
|
* @package mod_attendance |
||||
|
* @copyright 2018 Dan Marsdenb |
||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
||||
|
*/ |
||||
|
|
||||
|
defined('MOODLE_INTERNAL') || die(); |
||||
|
|
||||
|
$addons = [ |
||||
|
'mod_attendance' => [ |
||||
|
'handlers' => [ |
||||
|
'view' => [ |
||||
|
'displaydata' => [ |
||||
|
'icon' => $CFG->wwwroot . '/mod/attendance/pix/icon.png', |
||||
|
'class' => '', |
||||
|
], |
||||
|
'delegate' => 'CoreCourseModuleDelegate', |
||||
|
'method' => 'mobile_view_activity', |
||||
|
'styles' => [ |
||||
|
'url' => '/mod/attendance/mobilestyles.css', |
||||
|
'version' => 22 |
||||
|
] |
||||
|
] |
||||
|
], |
||||
|
'lang' => [ // Language strings that are used in all the handlers. |
||||
|
['pluginname', 'attendance'], |
||||
|
['sessionscompleted', 'attendance'], |
||||
|
['pointssessionscompleted', 'attendance'], |
||||
|
['percentagesessionscompleted', 'attendance'], |
||||
|
['sessionstotal', 'attendance'], |
||||
|
['pointsallsessions', 'attendance'], |
||||
|
['percentageallsessions', 'attendance'], |
||||
|
['maxpossiblepoints', 'attendance'], |
||||
|
['maxpossiblepercentage', 'attendance'], |
||||
|
['submitattendance', 'attendance'], |
||||
|
['strftimeh', 'attendance'], |
||||
|
['strftimehm', 'attendance'], |
||||
|
['attendancesuccess', 'attendance'], |
||||
|
['attendance_no_status', 'attendance'], |
||||
|
['attendance_already_submitted', 'attendance'], |
||||
|
['somedisabledstatus', 'attendance'], |
||||
|
['invalidstatus', 'attendance'], |
||||
|
['preventsharederror', 'attendance'], |
||||
|
['closed', 'attendance'], |
||||
|
['subnetwrong', 'attendance'], |
||||
|
['enterpassword', 'attendance'], |
||||
|
['incorrectpasswordshort', 'attendance'], |
||||
|
['attendancesuccess', 'attendance'], |
||||
|
['setallstatuses', 'attendance'] |
||||
|
], |
||||
|
] |
||||
|
]; |
@ -0,0 +1,30 @@ |
|||||
|
.attendance_mobile_teacher_form .item-radio { |
||||
|
display: inline-block; |
||||
|
margin-top: 10px; |
||||
|
margin-left: 5px; |
||||
|
padding: 0; |
||||
|
width: 70px; |
||||
|
} |
||||
|
.attendance_mobile_teacher_form .item-inner { |
||||
|
padding: 0; |
||||
|
} |
||||
|
.attendance_mobile_teacher_form .radiolabel .item-inner { |
||||
|
text-align: center; |
||||
|
} |
||||
|
.attendance_mobile_teacher_form .item-inner .input-wrapper label, |
||||
|
.attendance_mobile_teacher_form .radio { |
||||
|
margin: 0; |
||||
|
} |
||||
|
.attendance_mobile_teacher_form .radio .radio-icon { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.attendance_mobile_teacher_form .messages .label, |
||||
|
.attendance_mobile_user_form .messages .label, |
||||
|
.attendance_mobile_view_page .messages .label { |
||||
|
white-space: normal; |
||||
|
} |
||||
|
|
||||
|
.attendance_mobile_teacher_form .attendance_user_row { |
||||
|
padding-bottom: 5px; |
||||
|
} |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 451 B |
@ -0,0 +1,105 @@ |
|||||
|
{{! |
||||
|
This file is part of Moodle - http://moodle.org/ |
||||
|
|
||||
|
Moodle is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
Moodle is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
}} |
||||
|
{{! |
||||
|
@template mod_attendance/mobile_user_form |
||||
|
|
||||
|
The page to take attendance |
||||
|
|
||||
|
Classes required for JS: |
||||
|
* None |
||||
|
|
||||
|
Data attibutes required for JS: |
||||
|
* All data attributes are required |
||||
|
|
||||
|
Context variables required for this template: |
||||
|
* attendance |
||||
|
* summary |
||||
|
* cmid |
||||
|
|
||||
|
Example context (json): |
||||
|
{ |
||||
|
"attendance": { |
||||
|
"id": "1", |
||||
|
"course": "2", |
||||
|
"name": "Class Attendance", |
||||
|
"intro": "Intro" |
||||
|
}, |
||||
|
"cmid": "25", |
||||
|
"courseid": "4", |
||||
|
"sessid": "43", |
||||
|
"btnargs" : "" |
||||
|
} |
||||
|
}} |
||||
|
{{=<% %>=}} |
||||
|
<div class="attendance_mobile_teacher_form"> |
||||
|
<span class="description"> |
||||
|
<core-course-module-description description="<% attendance.intro %>" component="mod_attendance" componentId="<% cmid %>"></core-course-module-description> |
||||
|
</span> |
||||
|
<%#showmessage%> |
||||
|
<%#messages%> |
||||
|
<span class="messages"> |
||||
|
<ion-item> |
||||
|
{{ 'plugin.mod_attendance.<% string %>' | translate }} |
||||
|
</ion-item> |
||||
|
</span> |
||||
|
<%/messages%> |
||||
|
<%/showmessage%> |
||||
|
<span class="attendance_selectall"> |
||||
|
<ion-item> |
||||
|
{{ 'plugin.mod_attendance.setallstatuses' | translate }} |
||||
|
</ion-item> |
||||
|
<ion-list radio-group> |
||||
|
<%#statuses%> |
||||
|
|
||||
|
<span class="radiolabel"> |
||||
|
<ion-item> |
||||
|
<ion-label><% acronym %></ion-label> |
||||
|
<ion-radio (ionSelect)="<% selectall %>" value="<% stid %>"></ion-radio> |
||||
|
</ion-item> |
||||
|
</span> |
||||
|
<%/statuses%> |
||||
|
</ion-list> |
||||
|
</span> |
||||
|
<%#users%> |
||||
|
<span class="attendance_user_row"> |
||||
|
<!-- User and status of the submission. --> |
||||
|
<span ion-item text-wrap title="<% fullname %>"> |
||||
|
<ion-avatar item-start> |
||||
|
<img src="<% profileimageurl %>" core-external-content role="presentation" onError="this.src='assets/img/user-avatar.png'"> |
||||
|
</ion-avatar> |
||||
|
<h2><% fullname %></h2> |
||||
|
<ng-container *ngTemplateOutlet="submissionStatus"></ng-container> |
||||
|
</span> |
||||
|
<ion-list radio-group [(ngModel)]="CONTENT_OTHERDATA.status<% userid %>"> |
||||
|
<%#statuses%> |
||||
|
<span class="radiolabel"> |
||||
|
<ion-item> |
||||
|
<ion-label><% acronym %></ion-label> |
||||
|
<ion-radio value="<% stid %>"></ion-radio> |
||||
|
</ion-item> |
||||
|
</span> |
||||
|
<%/statuses%> |
||||
|
</ion-list> |
||||
|
</span> |
||||
|
<%/users%> |
||||
|
<ion-item> |
||||
|
<button ion-button core-site-plugins-new-content component="mod_attendance" method="mobile_view_activity" [args]="{cmid: <% cmid %>, courseid: <% courseid %>, sessid: <% sessid %><% btnargs %>}"> |
||||
|
{{ 'plugin.mod_attendance.submitattendance' | translate }} |
||||
|
</button> |
||||
|
</ion-item> |
||||
|
|
||||
|
</div> |
@ -0,0 +1,82 @@ |
|||||
|
{{! |
||||
|
This file is part of Moodle - http://moodle.org/ |
||||
|
|
||||
|
Moodle is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
Moodle is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
}} |
||||
|
{{! |
||||
|
@template mod_attendance/mobile_user_form |
||||
|
|
||||
|
The page to take attendance |
||||
|
|
||||
|
Classes required for JS: |
||||
|
* None |
||||
|
|
||||
|
Data attibutes required for JS: |
||||
|
* All data attributes are required |
||||
|
|
||||
|
Context variables required for this template: |
||||
|
* attendance |
||||
|
* summary |
||||
|
* cmid |
||||
|
|
||||
|
Example context (json): |
||||
|
{ |
||||
|
"attendance": { |
||||
|
"id": "1", |
||||
|
"course": "2", |
||||
|
"name": "Class Attendance", |
||||
|
"intro": "Intro" |
||||
|
}, |
||||
|
"cmid": "25", |
||||
|
"courseid": "4", |
||||
|
"sessid": "43" |
||||
|
} |
||||
|
}} |
||||
|
{{=<% %>=}} |
||||
|
<div class="attendance_mobile_user_form"> |
||||
|
<core-course-module-description description="<% attendance.intro %>" component="mod_attendance" componentId="<% cmid %>"></core-course-module-description> |
||||
|
<%#showmessage%> |
||||
|
<%#messages%> |
||||
|
<span class="messages"> |
||||
|
<ion-item> |
||||
|
{{ 'plugin.mod_attendance.<% string %>' | translate }} |
||||
|
</ion-item> |
||||
|
</span> |
||||
|
<%/messages%> |
||||
|
<%/showmessage%> |
||||
|
<%#showpassword%> |
||||
|
<ion-list [(ngModel)]="studentpass"> |
||||
|
<ion-item> |
||||
|
<ion-label>{{ 'plugin.mod_attendance.enterpassword' | translate }}:</ion-label> |
||||
|
<ion-input type="text" name="studentpass"></ion-input> |
||||
|
</ion-item> |
||||
|
</ion-list> |
||||
|
<%/showpassword%> |
||||
|
<%#showstatuses%> |
||||
|
<ion-list radio-group [(ngModel)]="status"> |
||||
|
<%#statuses%> |
||||
|
<ion-item> |
||||
|
<ion-label><% description %></ion-label> |
||||
|
<ion-radio value="<% stid %>"></ion-radio> |
||||
|
</ion-item> |
||||
|
<%/statuses%> |
||||
|
</ion-list> |
||||
|
<button ion-button core-site-plugins-new-content component="mod_attendance" method="mobile_view_activity" [args]="{cmid: <% cmid %>, courseid: <% courseid %>, sessid: <% sessid %>, status: status, studentpass: studentpass}"> |
||||
|
{{ 'plugin.mod_attendance.submitattendance' | translate }} |
||||
|
</button> |
||||
|
<%/showstatuses%> |
||||
|
<%#disabledduetotime%> |
||||
|
{{ 'plugin.mod_attendance.somedisabledstatus' | translate }} |
||||
|
<%/disabledduetotime%> |
||||
|
</div> |
@ -0,0 +1,144 @@ |
|||||
|
{{! |
||||
|
This file is part of Moodle - http://moodle.org/ |
||||
|
|
||||
|
Moodle is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
Moodle is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
||||
|
}} |
||||
|
{{! |
||||
|
@template mod_attendance/mobile_view_page |
||||
|
|
||||
|
The main page to view the attendance activity |
||||
|
|
||||
|
Classes required for JS: |
||||
|
* None |
||||
|
|
||||
|
Data attibutes required for JS: |
||||
|
* All data attributes are required |
||||
|
|
||||
|
Context variables required for this template: |
||||
|
* attendance |
||||
|
* summary |
||||
|
* cmid |
||||
|
|
||||
|
Example context (json): |
||||
|
{ |
||||
|
"attendance": { |
||||
|
"id": "1", |
||||
|
"course": "2", |
||||
|
"name": "Class Attendance", |
||||
|
"intro": "Intro" |
||||
|
}, |
||||
|
"summary": { |
||||
|
"numtakensessions": "1", |
||||
|
"pointssessionscompleted": "2", |
||||
|
"percentagesessionscompleted": "2" |
||||
|
}, |
||||
|
"cmid": "25", |
||||
|
"timestamp": "1234" |
||||
|
} |
||||
|
}} |
||||
|
{{=<% %>=}} |
||||
|
<div class="attendance_mobile_view_page"> |
||||
|
<core-course-module-description description="<% attendance.intro %>" component="mod_attendance" componentId="<% cmid %>"></core-course-module-description> |
||||
|
<%#showmessage%> |
||||
|
<%#messages%> |
||||
|
<span class="messages"> |
||||
|
<ion-item> |
||||
|
{{ 'plugin.mod_attendance.<% string %>' | translate }} |
||||
|
</ion-item> |
||||
|
</span> |
||||
|
<%/messages%> |
||||
|
<%/showmessage%> |
||||
|
<%#sessions%> |
||||
|
<ion-item> |
||||
|
<h2><% time %></h2> |
||||
|
<h3><% groupname %></h3> |
||||
|
<h3><% currentstatus %></h3> |
||||
|
<%#sessid%> |
||||
|
<button ion-button core-site-plugins-new-content component="mod_attendance" method="<% attendancefunction %>" [args]="{cmid: <% cmid %>, courseid: <% courseid %>, sessid: <% sessid %>, timestamp: <% timestamp %>}"> |
||||
|
{{ 'plugin.mod_attendance.submitattendance' | translate }} |
||||
|
</button> |
||||
|
<%/sessid%> |
||||
|
</ion-item> |
||||
|
<%/sessions%> |
||||
|
<ion-item> |
||||
|
<ion-grid> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.sessionscompleted' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.numtakensessions %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.pointssessionscompleted' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.pointssessionscompleted %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.percentagesessionscompleted' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.percentagesessionscompleted %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
|
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.sessionstotal' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.numallsessions %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.pointsallsessions' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.percentagesessionscompleted %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.percentageallsessions' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.allsessionspercentage %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.maxpossiblepoints' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.maxpossiblepoints %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
<ion-row> |
||||
|
<ion-col col-9 class="text-left"> |
||||
|
{{ 'plugin.mod_attendance.maxpossiblepercentage' | translate }} |
||||
|
</ion-col> |
||||
|
<ion-col col-2 class="text-left"> |
||||
|
<% summary.maxpossiblepercentage %> |
||||
|
</ion-col> |
||||
|
</ion-row> |
||||
|
|
||||
|
</ion-grid> |
||||
|
</ion-item> |
||||
|
</div> |
Loading…
Reference in new issue