diff --git a/README.md b/README.md index c7c805621d9..e2ca77113f6 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,14 @@ The Attendance module was previously developed by Branches -------- -The git branches here support the following versions. +The following git branches are supported: | Moodle version | Branch | |-----------------------|-------------------| -| Mooodle 3.5 | MOODLE_35_STABLE | -| Mooodle 3.6 | MOODLE_36_STABLE | -| Moodle 3.7 | MOODLE_37_STABLE | -| Moodle 3.8 - 3.10 | MOODLE_38_STABLE | -| Moodle 3.11 | MOODLE_311_STABLE | -| Moodle 4.0 | MOODLE_400_STABLE | | Moodle 4.1 | MOODLE_401_STABLE | | Moodle 4.2 | MOODLE_402_STABLE | | Moodle 4.3 | MOODLE_403_STABLE | +| Moodle 4.4 | MOODLE_404_STABLE | # PURPOSE The Attendance module allows teachers to maintain a record of attendance, replacing or supplementing a paper-based attendance register. It is primarily used in blended-learning environments where students are required to attend classes, lectures and tutorials and allows diff --git a/backup/moodle2/restore_attendance_stepslib.php b/backup/moodle2/restore_attendance_stepslib.php index 91d4e50fa0c..6711a1edda3 100644 --- a/backup/moodle2/restore_attendance_stepslib.php +++ b/backup/moodle2/restore_attendance_stepslib.php @@ -145,6 +145,9 @@ protected function process_attendance_session($data) { $data->lasttaken = 0; $data->lasttakenby = 0; } + if (!isset($data->allowupdatestatus)) { + $data->allowupdatestatus = 0; + } $newitemid = $DB->insert_record('attendance_sessions', $data); $data->id = $newitemid; diff --git a/classes/customfield/session_handler.php b/classes/customfield/session_handler.php index 544add97283..c45eee76b3f 100644 --- a/classes/customfield/session_handler.php +++ b/classes/customfield/session_handler.php @@ -87,12 +87,13 @@ public function can_configure() : bool { * @return bool true if the current can edit custom fields, false otherwise */ public function can_edit(field_controller $field, int $instanceid = 0) : bool { + global $PAGE; if ($instanceid) { $context = $this->get_instance_context($instanceid); return (!$field->get_configdata_property('locked') || has_capability('moodle/course:changelockedcustomfields', $context)); } else { - $context = \context_system::instance(); + $context = $PAGE->context->get_course_context(); return (!$field->get_configdata_property('locked') || guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context)); } diff --git a/classes/form/addsession.php b/classes/form/addsession.php index 586eb7fb885..75a19e776ce 100644 --- a/classes/form/addsession.php +++ b/classes/form/addsession.php @@ -240,14 +240,16 @@ public function definition() { $mform->disabledif('studentpassword', 'rotateqrcode', 'checked'); $mgroup[] = & $mform->createElement('checkbox', 'randompassword', '', get_string('randompassword', 'attendance')); $mform->disabledif('randompassword', 'rotateqrcode', 'checked'); - $mgroup[] = & $mform->createElement('checkbox', 'includeqrcode', '', get_string('includeqrcode', 'attendance')); - $mform->disabledif('includeqrcode', 'rotateqrcode', 'checked'); $mform->addGroup($mgroup, 'passwordgrp', get_string('passwordgrp', 'attendance'), array(' '), false); $mform->setType('studentpassword', PARAM_TEXT); $mform->addHelpButton('passwordgrp', 'passwordgrp', 'attendance'); + $mform->addElement('checkbox', 'includeqrcode', '', get_string('includeqrcode', 'attendance')); + $mform->hideif('includeqrcode', 'studentscanmark', 'notchecked'); + $mform->disabledif('includeqrcode', 'rotateqrcode', 'checked'); + $mform->addElement('checkbox', 'rotateqrcode', '', get_string('rotateqrcode', 'attendance')); $mform->hideif('rotateqrcode', 'studentscanmark', 'notchecked'); diff --git a/classes/local/entities/attendance.php b/classes/local/entities/attendance.php index 94bb10e3183..dadd90886a0 100644 --- a/classes/local/entities/attendance.php +++ b/classes/local/entities/attendance.php @@ -739,7 +739,7 @@ private function statusacronyms(): array { * @return array */ private function acronymfieldnames(string $acronym): array { - $fieldname = 'status_' . strtolower($acronym) . '_total_count'; + $fieldname = 'status_' . rtrim(base64_encode($acronym), '=') . '_total_count'; return [ $fieldname, $fieldname . '_current_week', diff --git a/classes/output/mobile.php b/classes/output/mobile.php index dcf05a6ac5f..4f408422887 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -53,7 +53,6 @@ public static function mobile_view_activity($args) { require_once($CFG->dirroot.'/mod/attendance/locallib.php'); - $versionname = $args['appversioncode'] >= 3950 ? 'latest' : 'ionic3'; $cmid = $args['cmid']; $courseid = $args['courseid']; $takenstatus = empty($args['status']) ? '' : $args['status']; @@ -252,7 +251,7 @@ public static function mobile_view_activity($args) { 'templates' => [ [ 'id' => 'main', - 'html' => $OUTPUT->render_from_template("mod_attendance/mobile_view_page_$versionname", $data), + 'html' => $OUTPUT->render_from_template("mod_attendance/mobile_view_page_latest", $data), ], ], 'javascript' => '', @@ -272,7 +271,7 @@ public static function mobile_user_form($args) { require_once($CFG->dirroot.'/mod/attendance/locallib.php'); $args = (object) $args; - $versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3'; + $versionname = $args->appversioncode >= 44000 ? 'latest' : 'ionic5'; $cmid = $args->cmid; $courseid = $args->courseid; $sessid = $args->sessid; @@ -374,7 +373,7 @@ public static function mobile_teacher_form($args) { require_once($CFG->dirroot.'/mod/attendance/locallib.php'); $args = (object) $args; - $versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3'; + $versionname = $args->appversioncode >= 44000 ? 'latest' : 'ionic5'; $cmid = $args->cmid; $courseid = $args->courseid; $sessid = $args->sessid; diff --git a/classes/output/password_icon.php b/classes/output/password_icon.php index f520c4f748b..0d4b3b4894e 100644 --- a/classes/output/password_icon.php +++ b/classes/output/password_icon.php @@ -49,6 +49,16 @@ class password_icon implements renderable, templatable { */ public $linktext = null; + /** + * @var int Session id. + */ + public $sessionid; + + /** + * @var int - should qr code be displayed. + */ + public $includeqrcode; + /** * Constructor * diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 0ccba357d1e..da1e12e0682 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -1147,10 +1147,13 @@ protected function render_user_report_tabs(user_data $userdata) { * @return string */ private function construct_user_data(user_data $userdata) { - global $USER; + global $USER, $COURSE; $o = ''; if ($USER->id <> $userdata->user->id) { - $o = html_writer::tag('h2', fullname($userdata->user)); + $userlink = html_writer::link(new \moodle_url('/user/view.php', + ['id' => $userdata->user->id, 'course' => $COURSE->id]), fullname($userdata->user)); + + $o = html_writer::tag('h2', $userlink); } if ($userdata->pageparams->mode == mod_attendance_view_page_params::MODE_THIS_COURSE) { diff --git a/classes/report_page_params.php b/classes/report_page_params.php index 030e1e1c277..b6c44b07cd2 100644 --- a/classes/report_page_params.php +++ b/classes/report_page_params.php @@ -39,6 +39,8 @@ class mod_attendance_report_page_params extends mod_attendance_page_with_filter_ public $showsessiondetails; /** @var int */ public $sessiondetailspos; + /** @var int */ + public $page; /** * mod_attendance_report_page_params constructor. diff --git a/classes/sessions_page_params.php b/classes/sessions_page_params.php index 9c1878b490c..b58e1899dea 100644 --- a/classes/sessions_page_params.php +++ b/classes/sessions_page_params.php @@ -62,4 +62,7 @@ class mod_attendance_sessions_page_params { /** @var int view mode of taking attendance page*/ public $action; + + /** @var int - sessionid of session */ + public $sessionid; } diff --git a/classes/structure.php b/classes/structure.php index 5d8878e17e0..c5d63721ab8 100644 --- a/classes/structure.php +++ b/classes/structure.php @@ -476,19 +476,16 @@ public function add_sessions($sessions) { } /** - * Undocumented function + * Save customfields * - * @param array $sessions + * @param int $sessionid * @param object $formdata * @return void */ - public function save_customfields($sessions, $formdata) { - foreach ($sessions as $session) { - $handler = mod_attendance\customfield\session_handler::create(); - $formdata->id = $session->id; - $handler->instance_form_save($formdata, true); - } - + public function save_customfields(int $sessionid, $formdata) { + $handler = mod_attendance\customfield\session_handler::create(); + $formdata->id = $sessionid; + $handler->instance_form_save($formdata, true); } /** diff --git a/classes/take_page_params.php b/classes/take_page_params.php index 594deb635b8..b68c5281770 100644 --- a/classes/take_page_params.php +++ b/classes/take_page_params.php @@ -54,6 +54,12 @@ class mod_attendance_take_page_params { /** @var int */ public $gridcols; + /** @var int */ + public $page; + + /** @var int */ + public $perpage; + /** * Initialize params. */ diff --git a/js/password/attendance_QRCodeRotate.js b/js/password/attendance_QRCodeRotate.js index 4aceb32ef71..2593bcd1cf4 100644 --- a/js/password/attendance_QRCodeRotate.js +++ b/js/password/attendance_QRCodeRotate.js @@ -13,12 +13,15 @@ class attendance_QRCodeRotate { this.password = ""; this.qrCodeInstance = ""; this.qrCodeHTMLElement = ""; + this.timeOffset = new Date(); } - start(sessionId, qrCodeHTMLElement, timerHTMLElement) { + start(sessionId, qrCodeHTMLElement, timerHTMLElement, serverTime) { this.sessionId = sessionId; this.qrCodeHTMLElement = qrCodeHTMLElement; this.timerHTMLElement = timerHTMLElement; + this.timeOffset = new Date() - new Date(serverTime * 1000); + console.log(`Sync OK - Server time is ${new Date(serverTime * 1000)}\nClient's time is ${this.timeOffset < 0 ? 'late' : 'early'} by ${Math.abs(this.timeOffset)} milliseconds.`); this.fetchAndRotate(); } @@ -43,13 +46,17 @@ class attendance_QRCodeRotate { this.timerHTMLElement.innerHTML = timeLeft; } + serverTime() { + return Math.round((new Date().getTime() - this.timeOffset) / 1000); + } + startRotating() { var parent = this; setInterval(function() { var found = Object.values(parent.password).find(function(element) { - if (element.expirytime > Math.round(new Date().getTime() / 1000)) { + if (element.expirytime > parent.serverTime()) { return element; } }); @@ -58,7 +65,7 @@ class attendance_QRCodeRotate { location.reload(true); } else { parent.changeQRCode(found.password); - parent.updateTimer(found.expirytime - Math.round(new Date().getTime() / 1000)); + parent.updateTimer(found.expirytime - parent.serverTime()); } diff --git a/locallib.php b/locallib.php index 80d57d34978..a668b066b45 100644 --- a/locallib.php +++ b/locallib.php @@ -564,7 +564,7 @@ function attendance_update_status($status, $acronym, $description, $grade, $visi * @return string */ function attendance_random_string($length=6) { - $randombytes = random_bytes_emulate($length); + $randombytes = random_bytes($length); $pool = 'abcdefghijklmnopqrstuvwxyz'; $pool .= '0123456789'; $poollen = strlen($pool); @@ -1246,6 +1246,7 @@ function attendance_session_get_highest_status(mod_attendance_structure $att, $a * @return array */ function attendance_get_automarkoptions() { + global $COURSE; $options = array(); @@ -1254,7 +1255,9 @@ function attendance_get_automarkoptions() { $options[ATTENDANCE_AUTOMARK_ALL] = get_string('automarkall', 'attendance'); } $options[ATTENDANCE_AUTOMARK_CLOSE] = get_string('automarkclose', 'attendance'); - $options[ATTENDANCE_AUTOMARK_ACTIVITYCOMPLETION] = get_string('onactivitycompletion', 'attendance'); + if ($COURSE->id == SITEID || $COURSE->enablecompletion == COMPLETION_ENABLED) { + $options[ATTENDANCE_AUTOMARK_ACTIVITYCOMPLETION] = get_string('onactivitycompletion', 'attendance'); + } return $options; } @@ -1438,7 +1441,7 @@ function attendance_renderqrcoderotate($session) { echo ' '; } diff --git a/manage.php b/manage.php index e0f891931e6..4bb857584a0 100644 --- a/manage.php +++ b/manage.php @@ -76,7 +76,6 @@ $PAGE->set_heading($course->fullname); $PAGE->set_cacheable(true); $PAGE->force_settings_menu(true); -$PAGE->navbar->add($att->name); $output = $PAGE->get_renderer('mod_attendance'); $filtercontrols = new mod_attendance\output\filter_controls($att); diff --git a/sessions.php b/sessions.php index 88129c15df6..5ac28a7e9f7 100644 --- a/sessions.php +++ b/sessions.php @@ -73,7 +73,10 @@ if ($formdata = $mform->get_data()) { $sessions = attendance_construct_sessions_data_for_add($formdata, $att); $att->add_sessions($sessions); - $att->save_customfields($sessions, $formdata); + // Save custom fields. + foreach ($sessions as $session) { + $att->save_customfields($session->id, $formdata); + } if (count($sessions) == 1) { $message = get_string('sessiongenerated', 'attendance'); @@ -106,6 +109,8 @@ $formdata->allowupdatestatus = 0; } $att->update_session_from_form_data($formdata, $sessionid); + // Save customfields data. + $att->save_customfields($sessionid, $formdata); mod_attendance_notifyqueue::notify_success(get_string('sessionupdated', 'attendance')); redirect($att->url_manage()); diff --git a/templates/mobile_teacher_form_ionic3.mustache b/templates/mobile_teacher_form_ionic5.mustache similarity index 64% rename from templates/mobile_teacher_form_ionic3.mustache rename to templates/mobile_teacher_form_ionic5.mustache index f25374f2a01..4beb511592e 100644 --- a/templates/mobile_teacher_form_ionic3.mustache +++ b/templates/mobile_teacher_form_ionic5.mustache @@ -52,53 +52,53 @@ <%#showmessage%> <%#messages%> - - {{ 'plugin.mod_attendance.<% string %>' | translate }} + + {{ 'plugin.mod_attendance.<% string %>' | translate }} <%/messages%> <%/showmessage%> - - {{ 'plugin.mod_attendance.setallstatuses' | translate }} - - + + {{ 'plugin.mod_attendance.setallstatuses' | translate }} + + <%#statuses%> - - + + <% acronym %> <%/statuses%> - + <%#users%> - - + + -

<% fullname %>

-
- + +

<% fullname %>

+
+ + <%#statuses%> - - <% acronym %> - - - + + <% acronym %> + + +
<%/statuses%> - + <%/users%> - - - + + {{ 'plugin.mod_attendance.submitattendance' | translate }} + \ No newline at end of file diff --git a/templates/mobile_teacher_form_latest.mustache b/templates/mobile_teacher_form_latest.mustache index 4beb511592e..cb9e2cc4d25 100644 --- a/templates/mobile_teacher_form_latest.mustache +++ b/templates/mobile_teacher_form_latest.mustache @@ -46,9 +46,17 @@ }} {{=<% %>=}}
+ <%#attendance.intro%> - + + + + + + + + <%/attendance.intro%> <%#showmessage%> <%#messages%> @@ -67,8 +75,7 @@ - <% acronym %> - + <% acronym %> <%/statuses%> @@ -89,8 +96,7 @@ <%#statuses%> - <% acronym %> - + <% acronym %> <%/statuses%> @@ -101,4 +107,4 @@ {{ 'plugin.mod_attendance.submitattendance' | translate }} -
\ No newline at end of file + diff --git a/templates/mobile_user_form_ionic3.mustache b/templates/mobile_user_form_ionic5.mustache similarity index 71% rename from templates/mobile_user_form_ionic3.mustache rename to templates/mobile_user_form_ionic5.mustache index b47502da52d..103fdc107e8 100644 --- a/templates/mobile_user_form_ionic3.mustache +++ b/templates/mobile_user_form_ionic5.mustache @@ -49,34 +49,34 @@ <%#showmessage%> <%#messages%> - - {{ 'plugin.mod_attendance.<% string %>' | translate }} + + {{ 'plugin.mod_attendance.<% string %>' | translate }} <%/messages%> <%/showmessage%> <%#showpassword%> - {{ 'plugin.mod_attendance.enterpassword' | translate }}: - + - <%/showpassword%> <%#showstatuses%> - + <%#statuses%> - + <% description %> - + <%/statuses%> - - + <%/showstatuses%> <%#disabledduetotime%> - {{ 'plugin.mod_attendance.somedisabledstatus' | translate }} + + {{ 'plugin.mod_attendance.somedisabledstatus' | translate }} + <%/disabledduetotime%> \ No newline at end of file diff --git a/templates/mobile_user_form_latest.mustache b/templates/mobile_user_form_latest.mustache index 103fdc107e8..1185a3558fc 100644 --- a/templates/mobile_user_form_latest.mustache +++ b/templates/mobile_user_form_latest.mustache @@ -45,7 +45,15 @@ }} {{=<% %>=}}
- + <%#attendance.intro%> + + + + + + + + <%/attendance.intro%> <%#showmessage%> <%#messages%> @@ -65,8 +73,7 @@ <%#statuses%> - <% description %> - + <% description %> <%/statuses%> @@ -79,4 +86,4 @@ {{ 'plugin.mod_attendance.somedisabledstatus' | translate }} <%/disabledduetotime%> -
\ No newline at end of file + diff --git a/templates/mobile_view_page_ionic3.mustache b/templates/mobile_view_page_ionic3.mustache deleted file mode 100644 index 8d134f17ab5..00000000000 --- a/templates/mobile_view_page_ionic3.mustache +++ /dev/null @@ -1,144 +0,0 @@ -{{! - 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_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" - } -}} -{{=<% %>=}} -
- - <%#showmessage%> - <%#messages%> - - - {{ 'plugin.mod_attendance.<% string %>' | translate }} - - - <%/messages%> - <%/showmessage%> - <%#sessions%> - -

<% time %>

-

<% groupname %>

-

<% currentstatus %>

- <%#sessid%> - - <%/sessid%> -
- <%/sessions%> - - - - - {{ 'plugin.mod_attendance.sessionscompleted' | translate }} - - - <% summary.numtakensessions %> - - - - - {{ 'plugin.mod_attendance.pointssessionscompleted' | translate }} - - - <% summary.pointssessionscompleted %> - - - - - {{ 'plugin.mod_attendance.percentagesessionscompleted' | translate }} - - - <% summary.percentagesessionscompleted %> - - - - - - {{ 'plugin.mod_attendance.sessionstotal' | translate }} - - - <% summary.numallsessions %> - - - - - {{ 'plugin.mod_attendance.pointsallsessions' | translate }} - - - <% summary.percentagesessionscompleted %> - - - - - {{ 'plugin.mod_attendance.percentageallsessions' | translate }} - - - <% summary.allsessionspercentage %> - - - - - {{ 'plugin.mod_attendance.maxpossiblepoints' | translate }} - - - <% summary.maxpossiblepoints %> - - - - - {{ 'plugin.mod_attendance.maxpossiblepercentage' | translate }} - - - <% summary.maxpossiblepercentage %> - - - - - -
\ No newline at end of file diff --git a/templates/mobile_view_page_latest.mustache b/templates/mobile_view_page_latest.mustache index af7746e80b9..b6da5fbbbc1 100644 --- a/templates/mobile_view_page_latest.mustache +++ b/templates/mobile_view_page_latest.mustache @@ -49,7 +49,9 @@ }} {{=<% %>=}}
- + + <%#showmessage%> <%#messages%> @@ -145,4 +147,4 @@ -
\ No newline at end of file + diff --git a/version.php b/version.php index 9b5f67ad33a..12b30a81c1d 100755 --- a/version.php +++ b/version.php @@ -23,8 +23,8 @@ */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024020500; -$plugin->release = 2023041800; +$plugin->version = 2024042203; +$plugin->release = 2024042203; $plugin->requires = 2023100900; // Requires 4.3. $plugin->maturity = MATURITY_STABLE; $plugin->cron = 0;