From dcac27864263639705196f88197867fd3fe49d06 Mon Sep 17 00:00:00 2001 From: Dan Marsden Date: Wed, 5 Sep 2018 14:27:09 +1200 Subject: [PATCH] Mobile Support - add ability for students to self-mark open sessions. --- attendance.php | 6 +- classes/output/mobile.php | 181 +++++++++++++++++++++- db/mobile.php | 11 ++ lang/en/attendance.php | 3 +- locallib.php | 110 ++++++++++--- renderables.php | 11 +- renderer.php | 5 +- renderhelpers.php | 66 -------- templates/mobile_take_attendance.mustache | 70 +++++++++ templates/mobile_view_page.mustache | 11 ++ 10 files changed, 376 insertions(+), 98 deletions(-) create mode 100644 templates/mobile_take_attendance.mustache diff --git a/attendance.php b/attendance.php index 4aeb0c4..c7b15df 100644 --- a/attendance.php +++ b/attendance.php @@ -40,9 +40,9 @@ $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST) // Require the user is logged in. require_login($course, true, $cm); -if (!attendance_can_student_mark($attforsession)) { - // TODO: should we add a log message here? - student has got to submit page but cannot save attendance (time ran out?) - redirect(new moodle_url('/mod/attendance/view.php', array('id' => $cm->id))); +list($canmark, $reason) = attendance_can_student_mark($attforsession); +if (!$canmark) { + redirect(new moodle_url('/mod/attendance/view.php', array('id' => $cm->id)), get_string($reason, 'attendance')); exit; } diff --git a/classes/output/mobile.php b/classes/output/mobile.php index 82a08d0..b3b0ed9 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -39,7 +39,7 @@ class mobile { * @return array HTML, javascript and other data */ public static function mobile_view_activity($args) { - global $OUTPUT, $DB, $USER, $PAGE, $CFG; + global $OUTPUT, $DB, $USER, $USER, $CFG; require_once($CFG->dirroot.'/mod/attendance/locallib.php'); @@ -59,6 +59,19 @@ class mobile { $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['attendancefunction'] = 'mobile_take_attendance'; + $isteacher = false; + if (has_capability('mod/attendance:takeattendances', $context)) { + $isteacher = true; + $data['attendancefunction'] = 'mobile_take_attendance_all'; + } + + // Add stats for this use to output. $pageparams = new \mod_attendance_view_page_params(); $pageparams->studentid = $USER->id; @@ -69,8 +82,45 @@ class mobile { $summary = new \mod_attendance_summary($att->id, array($USER->id), $att->pageparams->startdate, $att->pageparams->enddate); - $data = array('attendance' => $attendance, - 'summary' => $summary->get_all_sessions_summary_for($USER->id)); + $data['summary'] = $summary->get_all_sessions_summary_for($USER->id); + + // 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(); + + $userdata = new \attendance_user_data($att, $USER->id, true); + + $sessions = $DB->get_records_select('attendance_sessions', + 'attendanceid = ? AND sessdate > ? ORDER BY sessdate', array($attendance->id, $time)); + + if (!empty($sessions)) { + 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; + } + list($canmark, $unused) = attendance_can_student_mark($sess, false); + if ($isteacher || $canmark) { + + $html = array('time' => attendance_construct_session_time($sess->sessdate, $sess->duration)); + if (!empty($sess->groupid)) { + // TODO In-efficient way to get group name - we should get all groups in one query. + $groupname = $DB->get_field('groups', 'name', array('id' => $sess->groupid)); + $html['time'] .= ' ('.$groupname.')'; + } + + if (!$isteacher && !empty($userdata->sessionslog['c'.$sess->id]->statusid)) { + $html['currentstatus'] = $userdata->statuses[$userdata->sessionslog['c'.$sess->id]->statusid]->description; + } else { + $html['sessid'] = $sess->id; + } + + $data['sessions'][] = $html; + } + } + } return [ 'templates' => [ @@ -83,4 +133,129 @@ class mobile { '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_take_attendance($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; + $takenstatus = empty($args->status) ? '' : $args->status; + + // 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['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'][]['string'] = 'subnetwrong'; // Lang string to show as a message. + $data['showstatuses'] = false; // Hide all statuses. + } else if ($attforsession->autoassignstatus && empty($attforsession->studentpassword)) { + $statusid = attendance_session_get_highest_status($att, $attforsession); + if (empty($statusid)) { + $data['messages'][]['string'] = 'attendance_no_status'; + } + $take = new \stdClass(); + $take->status = $statusid; + $take->sessid = $attforsession->id; + $success = $att->take_from_student($take); + + if ($success) { + $data['messages'][]['string'] = 'attendancesuccess'; // Lang string to show as a message. + } else { + $data['messages'][]['string'] = 'attendance_already_submitted'; // Lang string to show as a message. + $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($takenstatus)) { + // Check if user has submitted a status. + if (empty($statuses[$takenstatus])) { + // This status has probably expired and is not available - they need to choose a new one. + $data['messages'][]['string'] = 'invalidstatus'; + } else { + + $take = new \stdClass(); + $take->status = $takenstatus; + $take->sessid = $attforsession->id; + $success = $att->take_from_student($take); + + if ($success) { + $data['messages'][]['string'] = 'attendancesuccess'; // Lang string to show as a message. + $data['showstatuses'] = false; // Hide all statuses. + } else { + $data['messages'][]['string'] = 'attendance_already_submitted'; // Lang string to show as a message. + $data['showstatuses'] = false; // Hide all statuses. + } + } + } + } + if (!empty($data['messages'])) { + $data['showmessage'] = true; + } + + return [ + 'templates' => [ + [ + 'id' => 'main', + 'html' => $OUTPUT->render_from_template('mod_attendance/mobile_take_attendance', $data), + ], + ], + 'javascript' => '', + 'otherdata' => '' + ]; + } } \ No newline at end of file diff --git a/db/mobile.php b/db/mobile.php index 290fb34..22c6815 100644 --- a/db/mobile.php +++ b/db/mobile.php @@ -46,6 +46,17 @@ $addons = [ ['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'] ] ] ]; \ No newline at end of file diff --git a/lang/en/attendance.php b/lang/en/attendance.php index f369493..cdd2bad 100644 --- a/lang/en/attendance.php +++ b/lang/en/attendance.php @@ -53,7 +53,7 @@ $string['attendance:view'] = 'Viewing Attendances'; $string['attendance:viewreports'] = 'Viewing Reports'; $string['attendance:viewsummaryreports'] = 'View course summary reports'; $string['attendance:warningemails'] = 'Can be subscribed to emails with absentee users'; -$string['attendance_already_submitted'] = 'You may not self register attendance that has already been set.'; +$string['attendance_already_submitted'] = 'Your attendance has already been set.'; $string['attendancedata'] = 'Attendance data'; $string['attendanceforthecourse'] = 'Attendance for the course'; $string['attendancegrade'] = 'Attendance grade'; @@ -94,6 +94,7 @@ $string['changeattendance'] = 'Change attendance'; $string['changeduration'] = 'Change duration'; $string['changesession'] = 'Change session'; $string['checkweekdays'] = 'Select weekdays that fall within your selected session date range.'; +$string['closed'] = 'This session is not currently available for self-marking'; $string['column'] = 'column'; $string['columns'] = 'columns'; $string['commonsession'] = 'All students'; diff --git a/locallib.php b/locallib.php index cc2a2a2..7ac157d 100644 --- a/locallib.php +++ b/locallib.php @@ -426,15 +426,18 @@ function attendance_random_string($length=6) { * Check to see if this session is open for student marking. * * @param stdclass $sess the session record from attendance_sessions. - * @return boolean + * @param boolean $log - if student cannot mark, generate log event. + * @return array (boolean, string reason for failure) */ -function attendance_can_student_mark($sess) { +function attendance_can_student_mark($sess, $log = true) { global $DB, $USER, $OUTPUT; $canmark = false; + $reason = 'closed'; $attconfig = get_config('attendance'); if (!empty($attconfig->studentscanmark) && !empty($sess->studentscanmark)) { if (empty($attconfig->studentscanmarksessiontime)) { $canmark = true; + $reason = ''; } else { $duration = $sess->duration; if (empty($duration)) { @@ -442,6 +445,7 @@ function attendance_can_student_mark($sess) { } if ($sess->sessdate < time() && time() < ($sess->sessdate + $duration)) { $canmark = true; + $reason = ''; } } } @@ -460,25 +464,26 @@ function attendance_can_student_mark($sess) { } if (!empty($record)) { - // Trigger an ip_shared event. - $attendanceid = $DB->get_field('attendance_sessions', 'attendanceid', array('id' => $record->sessionid)); - $cm = get_coursemodule_from_instance('attendance', $attendanceid); - $event = \mod_attendance\event\session_ip_shared::create(array( - 'objectid' => 0, - 'context' => \context_module::instance($cm->id), - 'other' => array( - 'sessionid' => $record->sessionid, - 'otheruser' => $record->studentid - ) - )); - - $event->trigger(); - - echo $OUTPUT->notification(get_string('preventsharederror', 'attendance')); - return false; + $canmark = false; + $reason = 'preventsharederror'; + if ($log) { + // Trigger an ip_shared event. + $attendanceid = $DB->get_field('attendance_sessions', 'attendanceid', array('id' => $record->sessionid)); + $cm = get_coursemodule_from_instance('attendance', $attendanceid); + $event = \mod_attendance\event\session_ip_shared::create(array( + 'objectid' => 0, + 'context' => \context_module::instance($cm->id), + 'other' => array( + 'sessionid' => $record->sessionid, + 'otheruser' => $record->studentid + ) + )); + + $event->trigger(); + } } } - return $canmark; + return array($canmark, $reason); } /** @@ -973,3 +978,70 @@ function attendance_get_sharedipoptions() { return $options; } + +/** + * Used to print simple time - 1am instead of 1:00am. + * + * @param int $time - unix timestamp. + */ +function attendance_strftimehm($time) { + $mins = userdate($time, '%M'); + + if ($mins == '00') { + $format = get_string('strftimeh', 'attendance'); + } else { + $format = get_string('strftimehm', 'attendance'); + } + + $userdate = userdate($time, $format); + + // Some Lang packs use %p to suffix with AM/PM but not all strftime support this. + // Check if %p is in use and make sure it's being respected. + if (stripos($format, '%p')) { + // Check if $userdate did something with %p by checking userdate against the same format without %p. + $formatwithoutp = str_ireplace('%p', '', $format); + if (userdate($time, $formatwithoutp) == $userdate) { + // The date is the same with and without %p - we have a problem. + if (userdate($time, '%H') > 11) { + $userdate .= 'pm'; + } else { + $userdate .= 'am'; + } + } + // Some locales and O/S don't respect correct intended case of %p vs %P + // This can cause problems with behat which expects AM vs am. + if (strpos($format, '%p')) { // Should be upper case according to PHP spec. + $userdate = str_replace('am', 'AM', $userdate); + $userdate = str_replace('pm', 'PM', $userdate); + } + } + + return $userdate; +} + +/** + * Used to print simple time - 1am instead of 1:00am. + * + * @param int $datetime - unix timestamp. + * @param int $duration - number of seconds. + */ +function attendance_construct_session_time($datetime, $duration) { + $starttime = attendance_strftimehm($datetime); + $endtime = attendance_strftimehm($datetime + $duration); + + return $starttime . ($duration > 0 ? ' - ' . $endtime : ''); +} + +/** + * Used to print session time. + * + * @param int $datetime - unix timestamp. + * @param int $duration - number of seconds duration. + * @return string. + */ +function construct_session_full_date_time($datetime, $duration) { + $sessinfo = userdate($datetime, get_string('strftimedmyw', 'attendance')); + $sessinfo .= ' '.attendance_construct_session_time($datetime, $duration); + + return $sessinfo; +} \ No newline at end of file diff --git a/renderables.php b/renderables.php index a463906..c0dbc7f 100644 --- a/renderables.php +++ b/renderables.php @@ -466,8 +466,9 @@ class attendance_user_data implements renderable { * attendance_user_data constructor. * @param mod_attendance_structure $att * @param int $userid + * @param boolean $mobile - this is called by the mobile code, don't generate everything. */ - public function __construct(mod_attendance_structure $att, $userid) { + public function __construct(mod_attendance_structure $att, $userid, $mobile = false) { $this->user = $att->get_user($userid); $this->pageparams = $att->pageparams; @@ -475,10 +476,12 @@ class attendance_user_data implements renderable { if ($this->pageparams->mode == mod_attendance_view_page_params::MODE_THIS_COURSE) { $this->statuses = $att->get_statuses(true, true); - $this->summary = new mod_attendance_summary($att->id, array($userid), $att->pageparams->startdate, - $att->pageparams->enddate); + if (!$mobile) { + $this->summary = new mod_attendance_summary($att->id, array($userid), $att->pageparams->startdate, + $att->pageparams->enddate); - $this->filtercontrols = new attendance_filter_controls($att); + $this->filtercontrols = new attendance_filter_controls($att); + } $this->sessionslog = $att->get_user_filtered_sessions_log_extended($userid); diff --git a/renderer.php b/renderer.php index a2d4db1..166d2cc 100644 --- a/renderer.php +++ b/renderer.php @@ -1111,7 +1111,8 @@ class mod_attendance_renderer extends plugin_renderer_base { $cell->colspan = 3; $row->cells[] = $cell; } else { - if (attendance_can_student_mark($sess)) { + list($canmark, $reason) = attendance_can_student_mark($sess, false); + if ($canmark) { // Student can mark their own attendance. // URL to the page that lets the student modify their attendance. @@ -1150,7 +1151,7 @@ class mod_attendance_renderer extends plugin_renderer_base { * @return string */ private function construct_time($datetime, $duration) { - $time = html_writer::tag('nobr', construct_session_time($datetime, $duration)); + $time = html_writer::tag('nobr', attendance_construct_session_time($datetime, $duration)); return $time; } diff --git a/renderhelpers.php b/renderhelpers.php index a851218..fa22f84 100644 --- a/renderhelpers.php +++ b/renderhelpers.php @@ -309,72 +309,6 @@ class user_sessions_cells_text_generator extends user_sessions_cells_generator { } } -/** - * Used to print simple time - 1am instead of 1:00am. - * - * @param int $time - unix timestamp. - */ -function attendance_strftimehm($time) { - $mins = userdate($time, '%M'); - if ($mins == '00') { - $format = get_string('strftimeh', 'attendance'); - } else { - $format = get_string('strftimehm', 'attendance'); - } - - $userdate = userdate($time, $format); - - // Some Lang packs use %p to suffix with AM/PM but not all strftime support this. - // Check if %p is in use and make sure it's being respected. - if (stripos($format, '%p')) { - // Check if $userdate did something with %p by checking userdate against the same format without %p. - $formatwithoutp = str_ireplace('%p', '', $format); - if (userdate($time, $formatwithoutp) == $userdate) { - // The date is the same with and without %p - we have a problem. - if (userdate($time, '%H') > 11) { - $userdate .= 'pm'; - } else { - $userdate .= 'am'; - } - } - // Some locales and O/S don't respect correct intended case of %p vs %P - // This can cause problems with behat which expects AM vs am. - if (strpos($format, '%p')) { // Should be upper case according to PHP spec. - $userdate = str_replace('am', 'AM', $userdate); - $userdate = str_replace('pm', 'PM', $userdate); - } - } - - return $userdate; -} - -/** - * Used to print simple time - 1am instead of 1:00am. - * - * @param int $datetime - unix timestamp. - * @param int $duration - number of seconds. - */ -function construct_session_time($datetime, $duration) { - $starttime = attendance_strftimehm($datetime); - $endtime = attendance_strftimehm($datetime + $duration); - - return $starttime . ($duration > 0 ? ' - ' . $endtime : ''); -} - -/** - * Used to print session time. - * - * @param int $datetime - unix timestamp. - * @param int $duration - number of seconds duration. - * @return string. - */ -function construct_session_full_date_time($datetime, $duration) { - $sessinfo = userdate($datetime, get_string('strftimedmyw', 'attendance')); - $sessinfo .= ' '.construct_session_time($datetime, $duration); - - return $sessinfo; -} - /** * Used to construct user summary. * diff --git a/templates/mobile_take_attendance.mustache b/templates/mobile_take_attendance.mustache new file mode 100644 index 0000000..336c108 --- /dev/null +++ b/templates/mobile_take_attendance.mustache @@ -0,0 +1,70 @@ +{{! + 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 . +}} +{{! + @template mod_attendance/mobile_take_attendance + + 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" + } +}} +{{=<% %>=}} +
+ + <%#showmessage%> + <%#messages%> + + {{ 'plugin.mod_attendance.<% string %>' | translate }} + + <%/messages%> + <%/showmessage%> + <%#showstatuses%> + + <%#statuses%> + + <% description %> + + + <%/statuses%> + + + <%/showstatuses%> + <%#disabledduetotime%> + {{ 'plugin.mod_attendance.somedisabledstatus' | translate }} + <%/disabledduetotime%> +
\ No newline at end of file diff --git a/templates/mobile_view_page.mustache b/templates/mobile_view_page.mustache index afc458b..c3d5fe2 100644 --- a/templates/mobile_view_page.mustache +++ b/templates/mobile_view_page.mustache @@ -49,6 +49,17 @@ {{=<% %>=}}
+ <%#sessions%> + +

<% time %>

+

<% currentstatus %>

+ <%#sessid%> + + <%/sessid%> +
+ <%/sessions%>