Compare commits

...

14 Commits

  1. 14
      .github/workflows/ci.yml
  2. 3
      README.md
  3. 20
      classes/import/sessions.php
  4. 20
      classes/output/mobile.php
  5. 3
      export.php
  6. 3
      lang/en/attendance.php
  7. 8
      locallib.php
  8. 1
      pix/redo.svg
  9. 2
      renderables.php
  10. 7
      renderhelpers.php
  11. 5
      templates/mobile_teacher_form_ionic3.mustache
  12. 104
      templates/mobile_teacher_form_latest.mustache
  13. 0
      templates/mobile_user_form_ionic3.mustache
  14. 82
      templates/mobile_user_form_latest.mustache
  15. 0
      templates/mobile_view_page_ionic3.mustache
  16. 148
      templates/mobile_view_page_latest.mustache
  17. 5
      version.php

14
.github/workflows/ci.yml

@ -27,8 +27,14 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.3', '7.4']
database: ['pgsql', 'mariadb']
include:
- php: '7.2'
moodle-branch: 'MOODLE_39_STABLE'
database: 'mariadb'
- php: '7.4'
moodle-branch: 'MOODLE_310_STABLE'
database: 'pgsql'
steps:
- name: Check out repository code
uses: actions/checkout@v2
@ -45,7 +51,7 @@ jobs:
- name: Setup PHP environment
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
with:
php-version: ${{ matrix.php-versions }}
php-version: ${{ matrix.php }}
extensions: mbstring, pgsql, mysqli
tools: phpunit
@ -63,7 +69,7 @@ jobs:
env:
DB: ${{ matrix.database }}
# TODO: Omitted MOODLE_BRANCH results in regex failure, investigate.
MOODLE_BRANCH: 'master'
MOODLE_BRANCH: ${{ matrix.moodle-branch }}
- name: Run phplint
run: moodle-plugin-ci phplint

3
README.md

@ -15,7 +15,8 @@ The git branches here support the following versions.
| Mooodle 3.5 | MOODLE_35_STABLE |
| Mooodle 3.6 | MOODLE_36_STABLE |
| Moodle 3.7 | MOODLE_37_STABLE |
| Moodle 3.8 and higher | main |
| Moodle 3.8 - 3.10 | MOODLE_38_STABLE |
| Moodle 3.11 and higher | MOODLE_311_STABLE |
# PURPOSE
The Attendance module allows teachers to maintain a record of attendance, replacing or supplementing a paper-based attendance register.

20
classes/import/sessions.php

@ -457,10 +457,11 @@ class sessions {
foreach ($sessions as $index => $sess) {
// Check for duplicate sessions.
if ($this->session_exists($sess)) {
if ($this->session_exists($sess, $att->id)) {
mod_attendance_notifyqueue::notify_message(get_string('sessionduplicate', 'attendance', (array(
'course' => $session->course,
'activity' => $cm->name
'activity' => $cm->name,
'date' => construct_session_full_date_time($sess->sessdate, $sess->duration)
))));
unset($sessions[$index]);
} else {
@ -504,19 +505,16 @@ class sessions {
* Check if an identical session exists.
*
* @param stdClass $session
* @param int $attid
* @return boolean
*/
private function session_exists(stdClass $session) {
private function session_exists(stdClass $session, $attid) {
global $DB;
$check = clone $session;
// Remove the properties that aren't useful to check.
unset($check->description);
unset($check->descriptionitemid);
unset($check->timemodified);
$check = (array) $check;
$check = ['attendanceid' => $attid,
'sessdate' => $session->sessdate,
'duration' => $session->duration,
'groupid' => $session->groupid];
if ($DB->record_exists('attendance_sessions', $check)) {
return true;
}

20
classes/output/mobile.php

@ -54,6 +54,7 @@ class mobile {
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 +253,7 @@ class mobile {
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_attendance/mobile_view_page', $data),
'html' => $OUTPUT->render_from_template("mod_attendance/mobile_view_page_$versionname", $data),
],
],
'javascript' => '',
@ -272,6 +273,7 @@ class mobile {
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
$args = (object) $args;
$versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3';
$cmid = $args->cmid;
$courseid = $args->courseid;
$sessid = $args->sessid;
@ -352,7 +354,7 @@ class mobile {
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_attendance/mobile_user_form', $data),
'html' => $OUTPUT->render_from_template("mod_attendance/mobile_user_form_$versionname", $data),
'cache-view' => false
],
],
@ -373,6 +375,7 @@ class mobile {
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
$args = (object) $args;
$versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3';
$cmid = $args->cmid;
$courseid = $args->courseid;
$sessid = $args->sessid;
@ -414,10 +417,11 @@ class mobile {
foreach ($statuses as $status) {
$data['statuses'][] = array('stid' => $status->id, 'acronym' => $status->acronym,
'description' => $status->description, 'selectall' => '');
'description' => $status->description);
}
$data['users'] = array();
$data['selectall'] = '';
$users = $att->get_users($att->get_session_info($sessid)->groupid, 0);
foreach ($users as $user) {
$userpicture = new \user_picture($user);
@ -427,13 +431,7 @@ class mobile {
// 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.";";
}
}
}
$data['selectall'] .= "CONTENT_OTHERDATA.status".$user->id."=CONTENT_OTHERDATA.statusall;";
}
if (!empty($data['messages'])) {
$data['showmessage'] = true;
@ -443,7 +441,7 @@ class mobile {
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_attendance/mobile_teacher_form', $data),
'html' => $OUTPUT->render_from_template("mod_attendance/mobile_teacher_form_$versionname", $data),
'cache-view' => false
],
],

3
export.php

@ -54,6 +54,9 @@ $formparams = array('course' => $course, 'cm' => $cm, 'modcontext' => $context);
$mform = new mod_attendance\form\export($att->url_export(), $formparams);
if ($formdata = $mform->get_data()) {
// Exporting large courses may use a bit of memory/take a bit of time.
\core_php_time_limit::raise();
raise_memory_limit(MEMORY_HUGE);
$pageparams = new mod_attendance_page_with_filter_controls();
$pageparams->init($cm);

3
lang/en/attendance.php

@ -487,7 +487,7 @@ $string['sessionalreadyexists'] = 'Session already exists for this date';
$string['sessiondate'] = 'Date';
$string['sessiondays'] = 'Session Days';
$string['sessiondeleted'] = 'Session successfully deleted';
$string['sessionduplicate'] = 'A duplicate session exists for course: {$a->course} in attendance: {$a->activity}';
$string['sessionduplicate'] = 'A duplicate session exists for course: {$a->course} in attendance: {$a->activity}, {$a->date}';
$string['sessionexist'] = 'Session not added (already exists)!';
$string['sessiongenerated'] = 'One session was successfully generated';
$string['sessions'] = 'Sessions';
@ -599,6 +599,7 @@ $string['tuseremail'] = 'Email';
$string['tusername'] = 'Full name';
$string['ungraded'] = 'Ungraded sessions';
$string['unknowngroup'] = 'Unknown group';
$string['unknownstatus'] = 'Unknown status id: {$a}';
$string['update'] = 'Update';
$string['uploadattendance'] = 'Upload attendance by CSV';
$string['usedefaultsubnet'] = 'Use default';

8
locallib.php

@ -839,7 +839,13 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
}
} else {
$sess = new stdClass();
$sess->sessdate = $sessiondate;
$sess->sessdate = make_timestamp(
date("Y", $formdata->sessiondate),
date("m", $formdata->sessiondate),
date("d", $formdata->sessiondate),
$formdata->sestime['starthour'],
$formdata->sestime['startminute']
);
$sess->duration = $duration;
$sess->descriptionitemid = $formdata->sdescription['itemid'];
$sess->description = $formdata->sdescription['text'];

1
pix/redo.svg

@ -0,0 +1 @@
<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><style>.st0{fill:#73ca18;stroke:#628945;stroke-miterlimit:10}</style><path id="path1432" class="st0" d="M38.4 45c-40 1.3-33.7-32.7-12.9-32.5V3.1l16.6 14.6L25.5 33v-9.7C11.4 22.8 7.3 44.8 38.4 45z"/></svg>

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 278 B

2
renderables.php

@ -724,7 +724,7 @@ class attendance_report_data implements renderable {
$this->sessions = $att->get_filtered_sessions();
$this->statuses = $att->get_statuses(true, true);
$this->allstatuses = $att->get_statuses(false, true);
$this->allstatuses = attendance_get_statuses($att->id, false);
if ($att->pageparams->view == ATT_VIEW_SUMMARY) {
$this->summary = new mod_attendance_summary($att->id);

7
renderhelpers.php

@ -68,7 +68,12 @@ class user_sessions_cells_generator {
$this->construct_existing_status_cell($this->reportdata->statuses[$statusid]->acronym .
" ({$points}/{$maxpoints})");
} else {
$this->construct_hidden_status_cell($this->reportdata->allstatuses[$statusid]->acronym);
if (!empty($this->reportdata->allstatuses[$statusid] && isset($this->reportdata->allstatuses[$statusid]->acronym))) {
$statusac = $this->reportdata->allstatuses[$statusid]->acronym;
} else {
$statusac = get_string('unknownstatus', 'mod_attendance', $statusid);
}
$this->construct_hidden_status_cell($statusac);
}
if ($remarks) {
$this->construct_remarks_cell($this->reportdata->sessionslog[$this->user->id][$sess->id]->remarks);

5
templates/mobile_teacher_form.mustache → templates/mobile_teacher_form_ionic3.mustache

@ -62,13 +62,13 @@
<ion-item>
{{ 'plugin.mod_attendance.setallstatuses' | translate }}
</ion-item>
<ion-list radio-group>
<ion-list radio-group [(ngModel)]="CONTENT_OTHERDATA.statusall" (ionChange)="<% selectall %>">
<%#statuses%>
<span class="radiolabel">
<ion-item>
<ion-label><% acronym %></ion-label>
<ion-radio (ionSelect)="<% selectall %>" value="<% stid %>"></ion-radio>
<ion-radio value="<% stid %>"></ion-radio>
</ion-item>
</span>
<%/statuses%>
@ -82,7 +82,6 @@
<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%>

104
templates/mobile_teacher_form_latest.mustache

@ -0,0 +1,104 @@
{{!
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 class="ion-text-wrap">
<ion-label>{{ 'plugin.mod_attendance.<% string %>' | translate }}</ion-label>
</ion-item>
</span>
<%/messages%>
<%/showmessage%>
<span class="attendance_selectall">
<ion-item class="ion-text-wrap">
<ion-label>{{ 'plugin.mod_attendance.setallstatuses' | translate }}</ion-label>
</ion-item>
<ion-radio-group [(ngModel)]="CONTENT_OTHERDATA.statusall" (ionChange)="<% selectall %>">
<%#statuses%>
<span class="radiolabel">
<ion-item class="ion-text-wrap">
<ion-label><% acronym %></ion-label>
<ion-radio value="<% stid %>"></ion-radio>
</ion-item>
</span>
<%/statuses%>
</ion-radio-group>
</span>
<%#users%>
<span class="attendance_user_row">
<!-- User and status of the submission. -->
<ion-item class="ion-text-wrap" title="<% fullname %>">
<ion-avatar slot="start">
<img src="<% profileimageurl %>" core-external-content role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<ion-label>
<h2><% fullname %></h2>
</ion-label>
</ion-item>
<ion-radio-group [(ngModel)]="CONTENT_OTHERDATA.status<% userid %>">
<%#statuses%>
<span class="radiolabel">
<ion-item class="ion-text-wrap">
<ion-label><% acronym %></ion-label>
<ion-radio value="<% stid %>"></ion-radio>
</ion-item>
</span>
<%/statuses%>
</ion-radio-group>
</span>
<%/users%>
<ion-button class="ion-margin" expand="block" 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 }}
</ion-button>
</div>

0
templates/mobile_user_form.mustache → templates/mobile_user_form_ionic3.mustache

82
templates/mobile_user_form_latest.mustache

@ -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 class="ion-text-wrap">
<ion-label>{{ 'plugin.mod_attendance.<% string %>' | translate }}</ion-label>
</ion-item>
</span>
<%/messages%>
<%/showmessage%>
<%#showpassword%>
<ion-item>
<ion-label>{{ 'plugin.mod_attendance.enterpassword' | translate }}:</ion-label>
<ion-input type="text" name="studentpass" [(ngModel)]="studentpass"></ion-input>
</ion-item>
<%/showpassword%>
<%#showstatuses%>
<ion-radio-group [(ngModel)]="status" name="status">
<%#statuses%>
<ion-item class="ion-text-wrap">
<ion-label><% description %></ion-label>
<ion-radio slot="end" value="<% stid %>"></ion-radio>
</ion-item>
<%/statuses%>
</ion-radio-group>
<ion-button class="ion-margin" expand="block" 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 }}
</ion-button>
<%/showstatuses%>
<%#disabledduetotime%>
<ion-item>
<ion-label>{{ 'plugin.mod_attendance.somedisabledstatus' | translate }}</ion-label>
</ion-item>
<%/disabledduetotime%>
</div>

0
templates/mobile_view_page.mustache → templates/mobile_view_page_ionic3.mustache

148
templates/mobile_view_page_latest.mustache

@ -0,0 +1,148 @@
{{!
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 class="ion-text-wrap">
<ion-label>{{ 'plugin.mod_attendance.<% string %>' | translate }}</ion-label>
</ion-item>
</span>
<%/messages%>
<%/showmessage%>
<%#sessions%>
<ion-item>
<ion-label>
<h2><% time %></h2>
<h3><% groupname %></h3>
<h3><% currentstatus %></h3>
<%#sessid%>
<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 }}
</ion-button>
<%/sessid%>
</ion-label>
</ion-item>
<%/sessions%>
<ion-item>
<ion-label>
<ion-grid>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.sessionscompleted' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.numtakensessions %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.pointssessionscompleted' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.pointssessionscompleted %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.percentagesessionscompleted' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.percentagesessionscompleted %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.sessionstotal' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.numallsessions %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.pointsallsessions' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.percentagesessionscompleted %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.percentageallsessions' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.allsessionspercentage %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.maxpossiblepoints' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.maxpossiblepoints %>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="9" class="text-left ion-text-wrap">
{{ 'plugin.mod_attendance.maxpossiblepercentage' | translate }}
</ion-col>
<ion-col size="2" class="text-left">
<% summary.maxpossiblepercentage %>
</ion-col>
</ion-row>
</ion-grid>
</ion-label>
</ion-item>
</div>

5
version.php

@ -23,9 +23,10 @@
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2021050700;
$plugin->version = 2021050702;
$plugin->requires = 2019072500; // Requires 3.8.
$plugin->release = '3.9.1';
$plugin->release = '3.9.3';
$plugin->maturity = MATURITY_STABLE;
$plugin->cron = 0;
$plugin->component = 'mod_attendance';
$plugin->supported = [38, 310];

Loading…
Cancel
Save