Compare commits

...

144 Commits

Author SHA1 Message Date
Morgan Harris a9e4c798ed Allow group sessions in webservices (#364) 6 years ago
Dan Marsden 61b0455a4c Fix #363 typo in key defintion - causes failure in Oracle upgrades. 6 years ago
Dan Marsden 65f82bd0cd Fix #350 - update description in calendar even on session update. 6 years ago
Dan Marsden 537ed2a0e8 Fix #332 tidy up delete message in attendance plugin. 6 years ago
Dan Marsden 8d486b6b54 Fix #340 Check duration of session when checking if a student enrolled before a session. 6 years ago
Dan Marsden d01649fa27 Fix #358 check index exists before dropping. 6 years ago
Tõnis Tartes dadf8426a4 ISSUE-336 - Viewing single session table total counts in also users w… (#337) 6 years ago
Dan Marsden c3b9ad119d Don't save values if not numeric. 6 years ago
Dan Marsden ef75ac81b5 Fix #328 correct validation check for statusset and not marked status. 6 years ago
Dan Marsden 0d8509d814 Fix for session add/update when studentscanmark is empty but automark set to close 7 years ago
Dan Marsden 8f34fbf32a Fix #317 typo in html entity, and entities not rendering inside select options. 7 years ago
Dan Marsden 67f83cfacf Prevent text_to_html from adding divs to email subject. 7 years ago
Dan Marsden 82b9c51139 remove cpd from 33 branch - not required in older branches. 7 years ago
Dan Marsden 0e1d1d5561 Fixes #302 make sure user lang is used when sending mail. 7 years ago
Dan Marsden a2ac42a34e Feature: Prevent students from sharing device while self-marking. 7 years ago
Dan Marsden 4c81211d85 Add class to set status for all users row. 7 years ago
Dan Marsden b72c2c52f5 Check if userto is empty (extra comma in thirdpartyusers field) 7 years ago
Dan Marsden 55d524f85f Fix issue when checking if a status set has an absent flag when updating a session. 7 years ago
Dan Marsden 38dacd225e bump version. 7 years ago
Dan Marsden d7a908707a add setting to allow sessiont description to show in report. 7 years ago
Dan Marsden aa38db6fdc Show warning if teacher using mark on close and no status available. 7 years ago
Dan Marsden 3b275d0d0a Fix import column matching. 7 years ago
Dan Marsden 5754d1a8b5 Remove deprecated calls to notify constants. 7 years ago
Dan Marsden dae3ac525b remove spacing. 7 years ago
Dan Marsden 60402e1d7d Add missing string and fix upgrade previous value. 7 years ago
Nick Phillips 53c30ab658 Make display of user 'extrafields' optional in report. (#294) 7 years ago
Nick Phillips 9a727878ea Fix userpic max-width in attreport. Some themes (e.g. adaptable) (#295) 7 years ago
Dan Marsden 3e41d3ebe6 include description format when creating event. 7 years ago
Nick Phillips c9fc3c4c7b Add description to event, and group to event name if present. (#296) 7 years ago
Dan Marsden 326509fe99 remove reference to user.de domain. 7 years ago
Dan Marsden ba479427f1 fix phpdocs. 7 years ago
Dan Marsden 41f52a6c95 codechecker fix. 7 years ago
Dan Marsden 3d228b9d0e codechecker stuff. 7 years ago
Dan Marsden 83fa2a9dae Bump version for plugins db release. 7 years ago
Dan Marsden 692a8368ed Fix link on subnet error. 7 years ago
Dan Marsden 20f9e4b9e3 Revert "Fix #290 cross-db compatible method to only include sessions with absenteereport set." 7 years ago
Dan Marsden b90eb366a4 Revert "Fix #290 correct $where statement." 7 years ago
Dan Marsden ae7dd6f8d4 Run upgrade for new absenteereport and autoassignstatus fields again. 7 years ago
Dan Marsden 79c40ad826 Fix #292 studentpassword undefined warning 7 years ago
Dan Marsden 5dd2f785a0 Split out graded and ungraded sessions on students all courses list. 7 years ago
Dan Marsden 63546aab95 Fix #290 correct $where statement. 7 years ago
Dan Marsden a2496ef8c8 Fix #290 cross-db compatible method to only include sessions with absenteereport set. 7 years ago
Dan Marsden bd8538c3bb Fixes #289 sorted grid layout select all fix. thank to Andrew Debevec for report. 7 years ago
Dan Marsden 6d53ae24a6 Hide option to automark if standard log store disabled. 7 years ago
Dan Marsden f78902e7eb Fix issue that occurs when updating an individual warning. 7 years ago
Dan Marsden 541f46963f Put magic number into constant. 7 years ago
Dan Marsden 0b3d6bb3d2 Prevent sessions from being added to attendance activites in recycle bin. 7 years ago
Dan Marsden 3458c729bb Fix #287 prevent restore from setting takenby when no userdata restored. 7 years ago
Dan Marsden 0fdea972d1 Exclude ungraded attendance activities from student all courses average. 7 years ago
Dan Marsden ba0e9281f8 Remove repeat fields - not implemented yet. 7 years ago
Dan Marsden 1b9ea04ea3 Add missing fields to csv and set defaults when not mapped. 7 years ago
Dan Marsden a7845a2b87 Correct list of groups for each session. 7 years ago
Dan Marsden ff0b2ae7d6 remove old comment. 7 years ago
Dan Marsden 670f592217 Improve auto-marking when no password. 7 years ago
James Voong 8982de60e4 Added ability to automatically mark all students with a status 7 years ago
Dan Marsden 92b3d5ca82 include absenteereport field in externalib structure. 7 years ago
Dan Marsden 03cdde1a34 Add absenteereport to class when adding session. 7 years ago
Dan Marsden b008844733 Add missing backup field, and new field in tests. 7 years ago
Dan Marsden e4b67ff880 Add ability to exlude sessions from absentee report calculations. 7 years ago
Dan Marsden 5827cb6731 fix travis config. 7 years ago
Dan Marsden d06e216f9c Add output buffering level for progress bar. 7 years ago
Chris Wharton 5647da0b7f Allow bulk importing of attendance sessions 7 years ago
Dan Marsden 89bdd37463 Improve error handling when adding a new status fails. 7 years ago
Dan Marsden bd2856558b Prevent error when updating status when student marking disabled. 7 years ago
Dan Marsden 06cfb316af fix phpdoc warning. 7 years ago
Dan Marsden 40761ac65f update travis config. 7 years ago
Dan Marsden a0503e820c Fix phpdocs. 7 years ago
Dan Marsden 97232cdc8a bump version for db release. 7 years ago
Dan Marsden a1ed607526 try behat on precise. 7 years ago
Dan Marsden ae89d47148 Don't need to call composer selfupdate. 7 years ago
Dan Marsden f67ebb0856 Fixes #270 - allow activity level warnings to be deleted. 7 years ago
Dan Marsden a0a955c020 Add description to warnings/status set. 7 years ago
Dan Marsden a3ff39a7d3 Use attendance-id instead of cmid in warning table. 7 years ago
Dan Marsden fd78759a0a Add missing activity level subnet field to backup. 7 years ago
Dan Marsden 5f1114072f Bump version. 7 years ago
Dan Marsden c7c319cba8 Fix behat test with at-risk change to absentee 7 years ago
Dan Marsden c84c5d8959 Rename at-risk report to absentee report. 7 years ago
Dan Marsden f1e9440b5b Don't show 0% in user report if no taken sessions. 7 years ago
Dan Marsden 34c5125f1a Swap multiple sessions settings and student recording settings 8 years ago
Dan Marsden b90403cf97 Improve use of triggered string. is really first notified. 8 years ago
Dan Marsden a90aa44e5a Feature: Allow warnings to send multiple emails. 8 years ago
Dan Marsden c87e48a1ee use correct Moodle branch in travis checks. 8 years ago
Dan Marsden 35f83c0558 Fixes #269 Allow Calendar events to be enabled/disabled 8 years ago
Dan Marsden 374d547a9a Drop key doesn't work well, drop incorrect key and add correct one. 8 years ago
Dan Marsden 700c4b1bfb Allow warnings with same percentage but different warnlevel. 8 years ago
Dan Marsden d735461745 Fix url used in sort column. 8 years ago
Dan Marsden 8d30f0d8b0 Make tables full width on small screens. 8 years ago
Dan Marsden 336545792c fix spacing in css file. 8 years ago
Dan Marsden 400776b52c Improve responsive display of user pages. 8 years ago
Dan Marsden 8ccea71898 fix php warning when no status set. 8 years ago
Dan Marsden 9b09681b02 Fix incorrect class namespacing. 8 years ago
Dan Marsden 2c0fef46a8 Use lasttaken instead of session end to obtain sessions to calculate 8 years ago
Dan Marsden bb09dd2ff4 Improve messages in cron, check end of session correctly. 8 years ago
Dan Marsden 441b343d17 Improve css on status set page. 8 years ago
Dan Marsden 3751e7fb44 Fix typo in var translation. 8 years ago
Dan Marsden d277d10bb7 Add new capability to control who gets warning emails. 8 years ago
Dan Marsden ac697980a1 Fix bug with userdisplay check. 8 years ago
Dan Marsden 984ef55b0a Simplify user view page to make it better on small devices 8 years ago
Dan Marsden 36a10b9eb5 Set default sort on atrisk page, 8 years ago
Dan Marsden a69bdf1d26 Add some button classes to attendance buttons to improve display. 8 years ago
Dan Marsden a642cd8450 Fix style for password pop-up and mustache template fix. 8 years ago
Dan Marsden ffff8251ba coding guidline improvements. 8 years ago
Dan Marsden ac586b83bc Improve password-pop-up handling. 8 years ago
Dan Marsden 63aeaeba69 update changelog 8 years ago
Dan Marsden c626c78eae Add category selector to coursesummary admin report. 8 years ago
Dan Marsden 36e2b32a92 fix breadcrumbs in atrisk and coursesummary reports. 8 years ago
Dan Marsden 664dfe0ca4 Merge notification/warning feature into master. (#267) 8 years ago
Dan Marsden b4ed81d27c Allow default subnet setting at activity level to be hidden. 8 years ago
Dan Marsden 83a9b87f6f Fix hardcoded reference to mdl_ 8 years ago
Dan Marsden 6cfeef1b2c Fixes #266 - groupmemembersonly setting removed by MDL-44725 8 years ago
Dan Marsden 82912d8e0a Use correct link to show all sessions in behat test. 8 years ago
Dan Marsden aa93cbb0a1 Fix version and changelog. 8 years ago
Dan Marsden 762153972f Fix coding guideline typo. 8 years ago
Dan Marsden 026b2ee06f Improve display of "all courses" user report - display as table 8 years ago
Dan Marsden 26c5c6299f Task doesn't just look at closed sessions. 8 years ago
Dan Marsden 9ae22ff659 New Feature: allow automarking of attendance using logs table. 8 years ago
Dan Marsden 9404332ebc Fixes #265 create calendar events when restoring an attendance. 8 years ago
Dan Marsden 503a924386 Fix #264 improve radio button spacing on self-marking page 8 years ago
Dan Marsden 24da43c6eb Allow default view on login to be set at admin level. 8 years ago
Dan Marsden 311b553543 resort lang into alphabetical order 8 years ago
Dan Marsden 968dc0e5b7 bump version to match block requirements. 8 years ago
Dan Marsden 896d00c99a Add lang strings for block and config course category report. 8 years ago
Dan Marsden 49650bbbac fix some coding guideline things. 8 years ago
Dan Marsden f63b4a62cb Modify subnet field to hopefully improve usability with teachers. 8 years ago
Dan Marsden 8d2967f4c6 New site-level/course category report for average course attendance. 8 years ago
Dan Marsden 45386e0031 Move subnet to advanced setting on session form. 8 years ago
Dan Marsden 2b95e51c05 Lasttakenby = 0 is a valid id when auto-marked. 8 years ago
Dan Marsden fcb1b97670 when course reset, reset automarkcompleted just in case. 8 years ago
Dan Marsden 9b1a853fff Feature: Allow unmarked students to be automatically marked after session close. 8 years ago
Dan Marsden 02840a9015 Sanity check - make sure session id is for this attendance. 8 years ago
Dan Marsden 6445dbc3a1 Fix some coding guideline issues. 8 years ago
Dan Marsden cca799d24e fix some eslint stuff. 8 years ago
Dan Marsden 349d9f2dcf Fix some tests. 8 years ago
Dan Marsden a9960beab1 fix some eslint stuff. 8 years ago
Dan Marsden 8c31246a5f Move subnet from attendance level to session level 8 years ago
Dan Marsden 4bafe58349 fix case of am/pm in tests. 8 years ago
Dan Marsden bd3e960ded Fixes #262 improves compatibility with Mac/Win when converting times. 8 years ago
Dan Marsden 1a5478bc88 Add new field for student availability, 8 years ago
Dan Marsden 117ed9d90e Fixes #262 date time not displaying on windows servers. 8 years ago
Dan Marsden 29be4af8b9 Improve error when incorrect password used. 8 years ago
Dan Marsden 148501bd89 bump version to clear css cache. 8 years ago
Dan Marsden 5d2676d775 Add abilty to view student password from session list page. 8 years ago
Dan Marsden f050d089df coding guideline spacing. 8 years ago
Dan Marsden 6f057052f2 Fix #259 undefined property warnings when showing temp users. 8 years ago
  1. 12
      .travis.yml
  2. 35
      CHANGELOG.md
  3. 140
      absentee.php
  4. 173
      add_form.php
  5. 41
      attendance.php
  6. 21
      backup/moodle2/backup_attendance_stepslib.php
  7. 34
      backup/moodle2/restore_attendance_stepslib.php
  8. 17
      calendar.js
  9. 113
      classes/add_warning_form.php
  10. 3
      classes/attendance_webservices_handler.php
  11. 26
      classes/calendar_helpers.php
  12. 93
      classes/event/session_ip_shared.php
  13. 92
      classes/event/sessions_imported.php
  14. 2
      classes/event/status_updated.php
  15. 86
      classes/form/import/sessions.php
  16. 73
      classes/form/import/sessions_confirm.php
  17. 493
      classes/import/sessions.php
  18. 6
      classes/notifyqueue.php
  19. 5
      classes/page_with_filter_controls.php
  20. 6
      classes/report_page_params.php
  21. 187
      classes/structure.php
  22. 4
      classes/summary.php
  23. 230
      classes/task/auto_mark.php
  24. 158
      classes/task/notify.php
  25. 124
      coursesummary.php
  26. 21
      db/access.php
  27. 51
      db/install.xml
  28. 44
      db/tasks.php
  29. 255
      db/upgrade.php
  30. 24
      defaultstatus.php
  31. 4
      externallib.php
  32. 94
      import/sessions.php
  33. 371
      lang/en/attendance.php
  34. 51
      lib.php
  35. 373
      locallib.php
  36. 15
      mod_form.php
  37. 6
      module.js
  38. 51
      password.php
  39. 50
      password_ajax.php
  40. 1
      pix/key.svg
  41. 33
      preferences.php
  42. 80
      renderables.php
  43. 331
      renderer.php
  44. 2
      renderhelpers.php
  45. 1
      report.php
  46. 92
      resetcalendar.php
  47. 9
      sessions.php
  48. 88
      settings.php
  49. 55
      student_attendance_form.php
  50. 65
      styles.css
  51. 3
      take.php
  52. 21
      templates/attendance_password_icon.mustache
  53. 28
      templates/attendance_password_icon_boost.mustache
  54. 4
      tempusers.php
  55. 39
      tests/attendance_webservices_test.php
  56. 16
      tests/behat/attendance_mod.feature
  57. 4
      tests/behat/extra_features.feature
  58. 39
      tests/behat/report.feature
  59. 109
      update_form.php
  60. 4
      version.php
  61. 203
      warnings.php
  62. 5
      yui/build/moodle-mod_attendance-groupfilter/moodle-mod_attendance-groupfilter-debug.js
  63. 5
      yui/build/moodle-mod_attendance-groupfilter/moodle-mod_attendance-groupfilter.js
  64. 5
      yui/src/groupfilter/js/groupfilter.js

12
.travis.yml

@ -1,9 +1,10 @@
language: php
sudo: false
sudo: true
addons:
postgresql: "9.3"
firefox: "47.0.1"
apt:
packages:
- oracle-java8-installer
@ -21,17 +22,17 @@ php:
env:
global:
- MOODLE_BRANCH=master
- MOODLE_BRANCH=MOODLE_33_STABLE
matrix:
- DB=pgsql
- DB=mysqli
before_install:
- phpenv config-rm xdebug.ini
- nvm install node
- nvm install 8.9
- nvm use 8.9
- cd ../..
- composer selfupdate
- composer create-project -n --no-dev --prefer-dist moodlerooms/moodle-plugin-ci ci v2.x-dev
- composer create-project -n --no-dev --prefer-dist moodlerooms/moodle-plugin-ci ci ^2
- export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH"
install:
@ -39,7 +40,6 @@ install:
script:
- moodle-plugin-ci phplint
- moodle-plugin-ci phpcpd
- moodle-plugin-ci phpmd
- moodle-plugin-ci codechecker
- moodle-plugin-ci validate

35
CHANGELOG.md

@ -0,0 +1,35 @@
### Date: 2017-June-22
### Release: 3.3.9
- New Feature: Allow automatic marking using site logs.
- New Feature: Warn users when attendance drops below threshold.
- Improvement: Allow default view for teachers to be set at admin level.
- Improvement: All courses user report now displays as table.
- Bug fix: Restored attendances do not create calendar events correctly.
### Date: 2017-May-23
### Release: 3.3.7
- New Feature: New site Level/course category report with average course attendance.
- New Feature: Allow unmarked students to be automatically marked when session closes.
---
### Date: 2017-May-11
- New Feature: Allow subnet mask to be set at the attendance session level.
- New Feature: Allow certain statuses to be hidden from students when self-marking attendance.
- New Feature: Allow student password to be viewed on session list page.
- Improvement: Improve usablity by grouping settings on session add form.
- Bug fix - fix issue with displaying dates when site hosted on Windows server.
- Bug fix - improve compliance with Moodle coding guidelines.
---
### Date: 2017-Apr-21
- Feature: Allow a random self-marking password to be used when creating session.
- Improvement: #63 use core useridentity setting when showing list of users.
- Improvement: #258 Add link to attendance on student overview report.
- Improvement: allow student self-marking to be restricted to the session time.
- Improvement: allow admin to set default values when teachers creating new sessions.
- Bug fix - improve compliance with Moodle coding guidelines - phpdocs etc.
---

140
absentee.php

@ -0,0 +1,140 @@
<?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/>.
/**
* Attendance course summary report.
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->dirroot.'/mod/attendance/lib.php');
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->libdir.'/coursecatlib.php');
$category = optional_param('category', 0, PARAM_INT);
$attendancecm = optional_param('id', 0, PARAM_INT);
$download = optional_param('download', '', PARAM_ALPHA);
$sort = optional_param('tsort', 'timesent', PARAM_ALPHA);
if (!empty($category)) {
$context = context_coursecat::instance($category);
$coursecat = coursecat::get($category);
$courses = $coursecat->get_courses(array('recursive' => true, 'idonly' => true));
$PAGE->set_category_by_id($category);
require_login();
} else if (!empty($attendancecm)) {
$cm = get_coursemodule_from_id('attendance', $attendancecm, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$att = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST);
$courses = array($course->id);
$context = context_module::instance($cm->id);
require_login($course, false, $cm);
} else {
admin_externalpage_setup('managemodules');
$context = context_system::instance();
$courses = array(); // Show all courses.
}
// Check permissions.
require_capability('mod/attendance:viewreports', $context);
$exportfilename = 'attendance-absentee.csv';
$PAGE->set_url('/mod/attendance/absentee.php', array('category' => $category, 'id' => $attendancecm));
$PAGE->set_heading($SITE->fullname);
$table = new flexible_table('attendanceabsentee');
$table->define_baseurl($PAGE->url);
if (!$table->is_downloading($download, $exportfilename)) {
if (!empty($attendancecm)) {
$pageparams = new mod_attendance_sessions_page_params();
$att = new mod_attendance_structure($att, $cm, $course, $context, $pageparams);
$output = $PAGE->get_renderer('mod_attendance');
$tabs = new attendance_tabs($att, attendance_tabs::TAB_ABSENTEE);
echo $output->header();
echo $output->heading(get_string('attendanceforthecourse', 'attendance').' :: ' .format_string($course->fullname));
echo $output->render($tabs);
} else {
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('absenteereport', 'mod_attendance'));
if (empty($category)) {
// Only show tabs if displaying via the admin page.
$tabmenu = attendance_print_settings_tabs('absentee');
echo $tabmenu;
}
}
}
$table->define_columns(array('coursename', 'aname', 'userid', 'numtakensessions', 'percent', 'timesent'));
$table->define_headers(array(get_string('course'),
get_string('pluginname', 'attendance'),
get_string('user'),
get_string('takensessions', 'attendance'),
get_string('averageattendance', 'attendance'),
get_string('triggered', 'attendance')));
$table->sortable(true);
$table->set_attribute('cellspacing', '0');
$table->set_attribute('class', 'generaltable generalbox');
$table->show_download_buttons_at(array(TABLE_P_BOTTOM));
$table->setup();
// Work out direction of sort required.
$sortcolumns = $table->get_sort_columns();
// Now do sorting if specified.
$orderby = ' ORDER BY percent ASC';
if (!empty($sort)) {
$direction = ' DESC';
if (!empty($sortcolumns[$sort]) && $sortcolumns[$sort] == SORT_ASC) {
$direction = ' ASC';
}
$orderby = " ORDER BY $sort $direction";
}
$records = attendance_get_users_to_notify($courses, $orderby);
foreach ($records as $record) {
if (!$table->is_downloading($download, $exportfilename)) {
$url = new moodle_url('/mod/attendance/index.php', array('id' => $record->courseid));
$name = html_writer::link($url, $record->coursename);
} else {
$name = $record->coursename;
}
$url = new moodle_url('/mod/attendance/view.php', array('studentid' => $record->userid,
'id' => $record->cmid, 'view' => ATT_VIEW_ALL));
$attendancename = html_writer::link($url, $record->aname);
$username = html_writer::link($url, fullname($record));
$percent = round($record->percent * 100)."%";
$timesent = "-";
if (!empty($record->timesent)) {
$timesent = userdate($record->timesent);
}
$table->add_data(array($name, $attendancename, $username, $record->numtakensessions, $percent, $timesent));
}
$table->finish_output();
if (!$table->is_downloading()) {
echo $OUTPUT->footer();
}

173
add_form.php

@ -121,42 +121,26 @@ class mod_attendance_add_form extends moodleform {
$mform->setType('statusset', PARAM_INT);
}
// Students can mark own attendance.
if (!empty(get_config('attendance', 'studentscanmark'))) {
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$mgroup = array();
$mgroup[] = & $mform->createElement('text', 'studentpassword', get_string('studentpassword', 'attendance'));
$mgroup[] = & $mform->createElement('checkbox', 'randompassword', '', get_string('randompassword', 'attendance'));
$mform->addGroup($mgroup, 'passwordgrp', get_string('passwordgrp', 'attendance'), array(' '), false);
$mform->setType('studentpassword', PARAM_TEXT);
$mform->disabledif('studentpassword', 'studentscanmark', 'notchecked');
$mform->addHelpButton('passwordgrp', 'passwordgrp', 'attendance');
$mform->disabledif('randompassword', 'studentscanmark', 'notchecked');
$mform->disabledif('studentpassword', 'randompassword', 'checked');
if (isset($pluginconfig->studentscanmark_default)) {
$mform->setDefault('studentscanmark', $pluginconfig->studentscanmark_default);
}
if (isset($pluginconfig->randompassword_default)) {
$mform->setDefault('randompassword', $pluginconfig->randompassword_default);
}
} else {
$mform->addElement('hidden', 'studentscanmark', '0');
$mform->settype('studentscanmark', PARAM_INT);
}
$mform->addElement('editor', 'sdescription', get_string('description', 'attendance'), array('rows' => 1, 'columns' => 80),
array('maxfiles' => EDITOR_UNLIMITED_FILES, 'noclean' => true, 'context' => $modcontext));
$mform->setType('sdescription', PARAM_RAW);
// If warnings allow selector for reporting.
if (!empty(get_config('attendance', 'enablewarnings'))) {
$mform->addElement('checkbox', 'absenteereport', '', get_string('includeabsentee', 'attendance'));
$mform->addHelpButton('absenteereport', 'includeabsentee', 'attendance');
if (isset($pluginconfig->absenteereport_default)) {
$mform->setDefault('absenteereport', $pluginconfig->absenteereport_default);
}
} else {
$mform->addElement('hidden', 'absenteereport', 1);
$mform->setType('absenteereport', PARAM_INT);
}
// For multiple sessions.
$mform->addElement('header', 'headeraddmultiplesessions', get_string('addmultiplesessions', 'attendance'));
if (!empty($pluginconfig->multisessionexpanded)) {
$mform->setExpanded('headeraddmultiplesessions');
}
$mform->addElement('checkbox', 'addmultiply', '', get_string('repeatasfollows', 'attendance'));
$mform->addHelpButton('addmultiply', 'createmultiplesessions', 'attendance');
@ -177,7 +161,7 @@ class mod_attendance_add_form extends moodleform {
$mform->disabledIf('sdays', 'addmultiply', 'notchecked');
$period = array(1 => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36);
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36);
$periodgroup = array();
$periodgroup[] =& $mform->createElement('select', 'period', '', $period, false, true);
$periodgroup[] =& $mform->createElement('static', 'perioddesc', '', get_string('week', 'attendance'));
@ -193,6 +177,114 @@ class mod_attendance_add_form extends moodleform {
$mform->addElement('hidden', 'previoussessiondate', 0);
$mform->setType('previoussessiondate', PARAM_INT);
// Students can mark own attendance.
if (!empty(get_config('attendance', 'studentscanmark'))) {
$mform->addElement('header', 'headerstudentmarking', get_string('studentmarking', 'attendance'), true);
if (!empty($pluginconfig->studentrecordingexpanded)) {
$mform->setExpanded('headerstudentmarking');
}
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$options = attendance_get_automarkoptions();
$mform->addElement('select', 'automark', get_string('automark', 'attendance'), $options);
$mform->setType('automark', PARAM_INT);
$mform->addHelpButton('automark', 'automark', 'attendance');
$mform->disabledif('automark', 'studentscanmark', 'notchecked');
$mform->setDefault('automark', $this->_customdata['att']->automark);
$mgroup = array();
$mgroup[] = & $mform->createElement('text', 'studentpassword', get_string('studentpassword', 'attendance'));
$mgroup[] = & $mform->createElement('checkbox', 'randompassword', '', get_string('randompassword', 'attendance'));
$mform->addGroup($mgroup, 'passwordgrp', get_string('passwordgrp', 'attendance'), array(' '), false);
$mform->setType('studentpassword', PARAM_TEXT);
$mform->disabledif('studentpassword', 'studentscanmark', 'notchecked');
$mform->addHelpButton('passwordgrp', 'passwordgrp', 'attendance');
$mform->disabledif('randompassword', 'studentscanmark', 'notchecked');
$mform->disabledif('studentpassword', 'randompassword', 'checked');
$mform->disabledif('studentpassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->disabledif('randompassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->addElement('checkbox', 'autoassignstatus', '', get_string('autoassignstatus', 'attendance'));
$mform->addHelpButton('autoassignstatus', 'autoassignstatus', 'attendance');
$mform->disabledif('autoassignstatus', 'studentscanmark', 'notchecked');
if (isset($pluginconfig->autoassignstatus)) {
$mform->setDefault('autoassignstatus', $pluginconfig->autoassignstatus);
}
if (isset($pluginconfig->studentscanmark_default)) {
$mform->setDefault('studentscanmark', $pluginconfig->studentscanmark_default);
}
if (isset($pluginconfig->randompassword_default)) {
$mform->setDefault('randompassword', $pluginconfig->randompassword_default);
}
if (isset($pluginconfig->automark_default)) {
$mform->setDefault('automark', $pluginconfig->automark_default);
}
$mgroup2 = array();
$mgroup2[] = & $mform->createElement('text', 'subnet', get_string('requiresubnet', 'attendance'));
if (empty(get_config('attendance', 'subnetactivitylevel'))) {
$mform->setDefault('subnet', get_config('attendance', 'subnet'));
} else {
$mform->setDefault('subnet', $this->_customdata['att']->subnet);
}
$mgroup2[] = & $mform->createElement('checkbox', 'usedefaultsubnet', get_string('usedefaultsubnet', 'attendance'));
$mform->setDefault('usedefaultsubnet', 1);
$mform->setType('subnet', PARAM_TEXT);
$mform->addGroup($mgroup2, 'subnetgrp', get_string('requiresubnet', 'attendance'), array(' '), false);
$mform->setAdvanced('subnetgrp');
$mform->addHelpButton('subnetgrp', 'requiresubnet', 'attendance');
$mform->disabledif('usedefaultsubnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'usedefaultsubnet', 'checked');
$mgroup3 = array();
$mgroup3[] = & $mform->createElement('checkbox', 'preventsharedip', '');
$mgroup3[] = & $mform->createElement('text', 'preventsharediptime',
get_string('preventsharediptime', 'attendance'), '', 'test');
$mgroup3[] = & $mform->createElement('static', 'preventsharediptimedesc', '',
get_string('preventsharedipminutes', 'attendance'));
$mform->addGroup($mgroup3, 'preventsharedgroup', get_string('preventsharedip', 'attendance'), array(' '), false);
$mform->addHelpButton('preventsharedgroup', 'preventsharedip', 'attendance');
$mform->setAdvanced('preventsharedgroup');
$mform->setType('preventsharediptime', PARAM_INT);
$mform->disabledif('preventsharedgroup', 'studentscanmark', 'notchecked');
$mform->disabledif('preventsharedip', 'studentscanmark', 'notchecked');
$mform->disabledif('preventsharediptime', 'studentscanmark', 'notchecked');
$mform->disabledIf('preventsharediptime', 'preventsharedip', 'notchecked');
if (isset($pluginconfig->preventsharedip)) {
$mform->setDefault('preventsharedip', $pluginconfig->preventsharedip);
}
if (isset($pluginconfig->preventsharediptime)) {
$mform->setDefault('preventsharediptime', $pluginconfig->preventsharediptime);
}
} else {
$mform->addElement('hidden', 'studentscanmark', '0');
$mform->settype('studentscanmark', PARAM_INT);
$mform->addElement('hidden', 'automark', '0');
$mform->setType('automark', PARAM_INT);
$mform->addElement('hidden', 'autoassignstatus', '0');
$mform->setType('autoassignstatus', PARAM_INT);
$mform->addElement('hidden', 'subnet', '');
$mform->setType('subnet', PARAM_TEXT);
$mform->addElement('hidden', 'preventsharedip', '0');
$mform->setType('preventsharedip', PARAM_INT);
$sharedtime = isset($pluginconfig->preventsharediptime) ? $pluginconfig->preventsharediptime : null;
$mform->addElement('hidden', 'preventsharediptime', $sharedtime);
$mform->setType('preventsharediptime', PARAM_INT);
}
$this->add_action_buttons(true, get_string('add', 'attendance'));
}
@ -202,6 +294,7 @@ class mod_attendance_add_form extends moodleform {
* @param array $files
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$sesstarttime = $data['sestime']['starthour'] * HOURSECS + $data['sestime']['startminute'] * MINSECS;
@ -239,6 +332,24 @@ class mod_attendance_add_form extends moodleform {
$this->_form->setConstant('previoussessiondate', $data['sessiondate']);
}
if (!empty($data['studentscanmark']) && $data['automark'] == ATTENDANCE_AUTOMARK_CLOSE) {
$cm = $this->_customdata['cm'];
// Check that the selected statusset has a status to use when unmarked.
$sql = 'SELECT id
FROM {attendance_statuses}
WHERE deleted = 0 AND (attendanceid = 0 or attendanceid = ?)
AND setnumber = ? AND setunmarked = 1';
$params = array($cm->instance, $data['statusset']);
if (!$DB->record_exists_sql($sql, $params)) {
$errors['automark'] = get_string('noabsentstatusset', 'attendance');
}
}
if (!empty($data['studentscanmark']) && !empty($data['preventsharedip']) &&
empty($data['preventsharediptime'])) {
$errors['preventsharedgroup'] = get_string('iptimemissing', 'attendance');
}
return $errors;
}

41
attendance.php

@ -46,8 +46,9 @@ if (!attendance_can_student_mark($attforsession)) {
}
// Check if subnet is set and if the user is in the allowed range.
if (!empty($attendance->subnet) && !address_in_subnet(getremoteaddr(), $attendance->subnet)) {
notice(get_string('subnetwrong', 'attendance'));
if (!empty($attforsession->subnet) && !address_in_subnet(getremoteaddr(), $attforsession->subnet)) {
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
notice(get_string('subnetwrong', 'attendance'), $url);
exit; // Notice calls this anyway.
}
@ -57,6 +58,26 @@ $att = new mod_attendance_structure($attendance, $cm, $course, $PAGE->context, $
// Require that a session key is passed to this page.
require_sesskey();
// Check to see if autoassignstatus is in use and no password required.
if ($attforsession->autoassignstatus && empty($attforsession->studentpassword)) {
$statusid = attendance_session_get_highest_status($att, $attforsession);
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
if (empty($statusid)) {
print_error('attendance_no_status', 'mod_attendance', $url);
}
$take = new stdClass();
$take->status = $statusid;
$take->sessid = $attforsession->id;
$success = $att->take_from_student($take);
if ($success) {
// Redirect back to the view page.
redirect($url, get_string('studentmarked', 'attendance'));
} else {
print_error('attendance_already_submitted', 'mod_attendance', $url);
}
}
// Create the form.
$mform = new mod_attendance_student_attendance_form(null,
array('course' => $course, 'cm' => $cm, 'modcontext' => $PAGE->context, 'session' => $attforsession, 'attendance' => $att));
@ -73,7 +94,14 @@ if ($mform->is_cancelled()) {
$attforsession->studentpassword !== $fromform->studentpassword) {
$url = new moodle_url('/mod/attendance/attendance.php', array('sessid' => $id, 'sesskey' => sesskey()));
redirect($url, get_string('incorrectpassword', 'mod_attendance'));
redirect($url, get_string('incorrectpassword', 'mod_attendance'), null, \core\output\notification::NOTIFY_ERROR);
}
if ($attforsession->autoassignstatus) {
$fromform->status = attendance_session_get_highest_status($att, $attforsession);
if (empty($fromform->status)) {
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
print_error('attendance_no_status', 'mod_attendance', $url);
}
}
if (!empty($fromform->status)) {
@ -81,10 +109,10 @@ if ($mform->is_cancelled()) {
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
if ($success) {
// Redirect back to the view page for the block.
redirect($url);
// Redirect back to the view page.
redirect($url, get_string('studentmarked', 'attendance'));
} else {
print_error ('attendance_already_submitted', 'mod_attendance', $url);
print_error('attendance_already_submitted', 'mod_attendance', $url);
}
}
@ -101,3 +129,4 @@ $output = $PAGE->get_renderer('mod_attendance');
echo $output->header();
$mform->display();
echo $output->footer();

21
backup/moodle2/backup_attendance_stepslib.php

@ -44,17 +44,22 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
// XML nodes declaration - non-user data.
$attendance = new backup_nested_element('attendance', array('id'), array(
'name', 'grade', 'showsessiondetails', 'sessiondetailspos'));
'name', 'intro', 'introformat', 'grade', 'showextrauserdetails', 'showsessiondetails', 'sessiondetailspos', 'subnet'));
$statuses = new backup_nested_element('statuses');
$status = new backup_nested_element('status', array('id'), array(
'acronym', 'description', 'grade', 'visible', 'deleted', 'setnumber'));
'acronym', 'description', 'grade', 'studentavailability', 'setunmarked', 'visible', 'deleted', 'setnumber'));
$warnings = new backup_nested_element('warnings');
$warning = new backup_nested_element('warning', array('id'), array('warningpercent', 'warnafter',
'maxwarn', 'emailuser', 'emailsubject', 'emailcontent', 'emailcontentformat', 'thirdpartyemails'));
$sessions = new backup_nested_element('sessions');
$session = new backup_nested_element('session', array('id'), array(
'groupid', 'sessdate', 'duration', 'lasttaken', 'lasttakenby',
'timemodified', 'description', 'descriptionformat', 'studentscanmark', 'studentpassword',
'statusset', 'caleventid'));
'groupid', 'sessdate', 'duration', 'lasttaken', 'lasttakenby', 'timemodified',
'description', 'descriptionformat', 'studentscanmark', 'studentpassword', 'autoassignstatus',
'subnet', 'automark', 'automarkcompleted', 'statusset', 'absenteereport', 'preventsharedip',
'preventsharediptime', 'caleventid'));
// XML nodes declaration - user data.
$logs = new backup_nested_element('logs');
@ -65,6 +70,9 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
$attendance->add_child($statuses);
$statuses->add_child($status);
$attendance->add_child($warnings);
$warnings->add_child($warning);
$attendance->add_child($sessions);
$sessions->add_child($session);
@ -77,6 +85,9 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
$status->set_source_table('attendance_statuses', array('attendanceid' => backup::VAR_PARENTID));
$warning->set_source_table('attendance_warning',
array('idnumber' => backup::VAR_PARENTID));
$session->set_source_table('attendance_sessions', array('attendanceid' => backup::VAR_PARENTID));
// Data sources - user related data.

34
backup/moodle2/restore_attendance_stepslib.php

@ -49,6 +49,9 @@ class restore_attendance_activity_structure_step extends restore_activity_struct
$paths[] = new restore_path_element('attendance_status',
'/activity/attendance/statuses/status');
$paths[] = new restore_path_element('attendance_warning',
'/activity/attendance/warnings/warning');
$paths[] = new restore_path_element('attendance_session',
'/activity/attendance/sessions/session');
@ -100,6 +103,21 @@ class restore_attendance_activity_structure_step extends restore_activity_struct
$this->set_mapping('attendance_status', $oldid, $newitemid);
}
/**
* Process attendance warning restore
* @param object $data The data in object form
* @return void
*/
protected function process_attendance_warning($data) {
global $DB;
$data = (object)$data;
$data->idnumber = $this->get_new_parentid('attendance');
$DB->insert_record('attendance_warning', $data);
}
/**
* Process attendance session restore
* @param object $data The data in object form
@ -108,19 +126,31 @@ class restore_attendance_activity_structure_step extends restore_activity_struct
protected function process_attendance_session($data) {
global $DB;
$userinfo = $this->get_setting_value('userinfo'); // Are we including userinfo?
$data = (object)$data;
$oldid = $data->id;
$data->attendanceid = $this->get_new_parentid('attendance');
$data->groupid = $this->get_mappingid('group', $data->groupid);
$data->sessdate = $this->apply_date_offset($data->sessdate);
$data->lasttaken = $this->apply_date_offset($data->lasttaken);
$data->lasttakenby = $this->get_mappingid('user', $data->lasttakenby);
$data->timemodified = $this->apply_date_offset($data->timemodified);
$data->caleventid = $this->get_mappingid('event', $data->caleventid);
if ($userinfo) {
$data->lasttaken = $this->apply_date_offset($data->lasttaken);
$data->lasttakenby = $this->get_mappingid('user', $data->lasttakenby);
} else {
$data->lasttaken = 0;
$data->lasttakenby = 0;
}
$newitemid = $DB->insert_record('attendance_sessions', $data);
$data->id = $newitemid;
$this->set_mapping('attendance_session', $oldid, $newitemid, true);
// Create Calendar event.
attendance_create_calendar_event($data);
}
/**

17
calendar.js

@ -1,4 +1,5 @@
/* global YUI */
// eslint-disable-next-line new-cap
YUI().use('yui2-container', 'yui2-calendar', function(Y) {
var YAHOO = Y.YUI2;
@ -13,12 +14,17 @@ YUI().use('yui2-container', 'yui2-calendar', function(Y) {
var showBtn = Dom.get("show");
Event.on(showBtn, "click", function() {
/**
* Reset handler and set current day.
*/
function resetHandler() {
calendar.cfg.setProperty("pagedate", calendar.today);
calendar.render();
}
/**
* Close dialog.
*/
function closeHandler() {
dialog.hide();
}
@ -39,8 +45,8 @@ YUI().use('yui2-container', 'yui2-calendar', function(Y) {
dialog = new YAHOO.widget.Dialog("attcalendarcontainer", {
visible: false,
context: ["show", "tl", "bl"],
buttons: [{text: M.str.attendance.caltoday, handler: resetHandler, isDefault: true},
{text: M.str.attendance.calclose, handler: closeHandler}],
buttons: [{text: M.util.get_string('caltoday', 'attendance'), handler: resetHandler, isDefault: true},
{text: M.util.get_string('calclose', 'attendance'), handler: closeHandler}],
draggable: false,
close: false
});
@ -62,8 +68,9 @@ YUI().use('yui2-container', 'yui2-calendar', function(Y) {
if (!calendar) {
calendar = new YAHOO.widget.Calendar("cal", {
iframe: false, // Turn iframe off, since container has iframe support.
hide_blank_weeks: true // Enable, to demonstrate how we handle changing height, using changeContent.
iframe: false, // Turn iframe off, since container has iframe support.
// eslint-disable-next-line camelcase
hide_blank_weeks: true // Enable, to demonstrate how we handle changing height, using changeContent.
});
calendar.cfg.setProperty("start_weekday", M.attendance.cal_start_weekday);

113
classes/add_warning_form.php

@ -0,0 +1,113 @@
<?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 class mod_attendance_add_warning_form
*
* @package mod_attendance
* @copyright 2017 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class mod_attendance_add_warning_form
*
* @package mod_attendance
* @copyright 2017 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_attendance_add_warning_form extends moodleform {
/**
* Form definition
*/
public function definition() {
global $COURSE;
$mform = $this->_form;
// Load global defaults.
$config = get_config('attendance');
$options = array();
for ($i = 1; $i <= 100; $i++) {
$options[$i] = "$i%";
}
$mform->addElement('select', 'warningpercent', get_string('warningpercent', 'mod_attendance'), $options);
$mform->addHelpButton('warningpercent', 'warningpercent', 'mod_attendance');
$mform->setType('warningpercent', PARAM_INT);
$mform->setDefault('warningpercent', $config->warningpercent);
$options = array();
for ($i = 1; $i <= ATTENDANCE_MAXWARNAFTER; $i++) {
$options[$i] = "$i";
}
$mform->addElement('select', 'warnafter', get_string('warnafter', 'mod_attendance'), $options);
$mform->addHelpButton('warnafter', 'warnafter', 'mod_attendance');
$mform->setType('warnafter', PARAM_INT);
$mform->setDefault('warnafter', $config->warnafter);
$mform->addElement('select', 'maxwarn', get_string('maxwarn', 'mod_attendance'), $options);
$mform->addHelpButton('maxwarn', 'maxwarn', 'mod_attendance');
$mform->setType('maxwarn', PARAM_INT);
$mform->setDefault('maxwarn', $config->maxwarn);
$mform->addElement('checkbox', 'emailuser', get_string('emailuser', 'mod_attendance'));
$mform->addHelpButton('emailuser', 'emailuser', 'mod_attendance');
$mform->setDefault('emailuser', $config->emailuser);
$mform->addElement('text', 'emailsubject', get_string('emailsubject', 'mod_attendance'), array('size' => '64'));
$mform->setType('emailsubject', PARAM_TEXT);
$mform->addRule('emailsubject', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->addHelpButton('emailsubject', 'emailsubject', 'mod_attendance');
$mform->setDefault('emailsubject', $config->emailsubject);
$mform->addElement('editor', 'emailcontent', get_string('emailcontent', 'mod_attendance'), null, null);
$mform->setDefault('emailcontent', array('text' => format_text($config->emailcontent)));
$mform->setType('emailcontent', PARAM_RAW);
$mform->addHelpButton('emailcontent', 'emailcontent', 'mod_attendance');
$users = get_users_by_capability(context_course::instance($COURSE->id), 'mod/attendance:warningemails');
$options = array();
foreach ($users as $user) {
$options[$user->id] = fullname($user);
}
$select = $mform->addElement('searchableselector', 'thirdpartyemails',
get_string('thirdpartyemails', 'mod_attendance'), $options);
$mform->setType('thirdpartyemails', PARAM_TEXT);
$mform->addHelpButton('thirdpartyemails', 'thirdpartyemails', 'mod_attendance');
$select->setMultiple(true);
// Need to set hidden elements when adding default options.
$mform->addElement('hidden', 'idnumber', 0); // Default options use 0 as the idnumber.
$mform->setType('idnumber', PARAM_INT);
$mform->addElement('hidden', 'notid', 0); // The id of warning record.
$mform->setType('notid', PARAM_INT);
$mform->addElement('hidden', 'id', $this->_customdata['id']); // The id of course module record if attendance level.
$mform->setType('id', PARAM_INT);
if (!empty($this->_customdata['notid'])) {
$btnstring = get_string('update', 'attendance');
} else {
$btnstring = get_string('add', 'attendance');
}
$this->add_action_buttons(true, $btnstring);
}
}

3
classes/attendance_webservices_handler.php

@ -109,7 +109,8 @@ class attendance_handler {
$session->courseid = $DB->get_field('attendance', 'course', array('id' => $session->attendanceid));
$session->statuses = attendance_get_statuses($session->attendanceid, true, $session->statusset);
$coursecontext = context_course::instance($session->courseid);
$session->users = get_enrolled_users($coursecontext, 'mod/attendance:canbelisted', 0, 'u.id, u.firstname, u.lastname');
$session->users = get_enrolled_users($coursecontext, 'mod/attendance:canbelisted',
$session->groupid, 'u.id, u.firstname, u.lastname');
$session->attendance_log = array();
if ($attendancelog = $DB->get_records('attendance_log', array('sessionid' => $sessionid),

26
classes/calendar_helpers.php

@ -32,12 +32,17 @@ require_once(dirname(__FILE__).'/../../../calendar/lib.php');
* @return bool result of calendar event creation
*/
function attendance_create_calendar_event(&$session) {
global $DB;
// We don't want to create multiple calendar events for 1 session.
if ($session->caleventid) {
return $session->caleventid;
}
if (empty(get_config('attendance', 'enablecalendar'))) {
// Calendar events are not used.
return true;
}
global $DB;
$attendance = $DB->get_record('attendance', array('id' => $session->attendanceid));
$caleventdata = new stdClass();
@ -47,10 +52,16 @@ function attendance_create_calendar_event(&$session) {
$caleventdata->instance = $session->attendanceid;
$caleventdata->timestart = $session->sessdate;
$caleventdata->timeduration = $session->duration;
$caleventdata->description = $session->description;
$caleventdata->format = $session->descriptionformat;
$caleventdata->eventtype = 'attendance';
$caleventdata->timemodified = time();
$caleventdata->modulename = 'attendance';
if (!empty($session->groupid)) {
$caleventdata->name .= " (". get_string('group', 'group') ." ". groups_get_group_name($session->groupid) .")";
}
$calevent = new stdClass();
if ($calevent = calendar_event::create($caleventdata, false)) {
$session->caleventid = $calevent->id;
@ -68,6 +79,12 @@ function attendance_create_calendar_event(&$session) {
*/
function attendance_create_calendar_events($sessionsids) {
global $DB;
if (empty(get_config('attendance', 'enablecalendar'))) {
// Calendar events are not used.
return true;
}
$sessions = $DB->get_recordset_list('attendance_sessions', 'id', $sessionsids);
foreach ($sessions as $session) {
@ -87,10 +104,17 @@ function attendance_create_calendar_events($sessionsids) {
* @return bool result of updating
*/
function attendance_update_calendar_event($caleventid, $timeduration, $timestart) {
if (empty(get_config('attendance', 'enablecalendar'))) {
// Calendar events are not used.
return true;
}
$caleventdata = new stdClass();
$caleventdata->timeduration = $timeduration;
$caleventdata->timestart = $timestart;
$caleventdata->timemodified = time();
$caleventdata->description = $session->description;
$calendarevent = calendar_event::load($caleventid);
if ($calendarevent) {

93
classes/event/session_ip_shared.php

@ -0,0 +1,93 @@
<?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/>.
/**
* This file contains an event for when self-marking is blocked because
* another student used the same IP address to self-mark.
*
* @package mod_attendance
* @author Dan Marsden <dan@danmarsden.com>
* @copyright 2018 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\event;
defined('MOODLE_INTERNAL') || die();
/**
* Event for when self-marking is blocked
*
* @property-read array $other {
* Extra information about event properties.
*
* string mode Mode of the report viewed.
* }
* @package mod_attendance
* @author Dan Marsden <dan@danmarsden.com>
* @copyright 2018 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class session_ip_shared extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'attendance_log';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return 'User with id ' . $this->userid . ' was blocked from taking attendance for sessionid: ' . $this->other['sessionid'] .
' because user with id '.$this->other['otheruser'] . ' previously marked attendance with the same IP address.';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsessionipshared', 'mod_attendance');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/attendance/attendance.php');
}
/**
* Get objectid mapping
*
* @return array of parameters for object mapping.
*/
public static function get_objectid_mapping() {
return array(
'db' => 'attendance',
'restore' => 'attendance'
);
}
}

92
classes/event/sessions_imported.php

@ -0,0 +1,92 @@
<?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/>.
/**
* This file contains an event for when an attendance sessions is imported.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\event;
defined('MOODLE_INTERNAL') || die();
/**
* Event for when an attendance sessions is imported
*
* @property-read array $other {
* Extra information about event properties.
*
* string mode Mode of the report viewed.
* }
* @package mod_attendance
* @since Moodle 2.7
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sessions_imported extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'attendance_sessions';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return 'User with id ' . $this->userid . ' imported ' . $this->other['count'] . ' sessions';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsessionsimported', 'mod_attendance');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/attendance/import/sessions.php');
}
/**
* Get objectid mapping
*
* @return array of parameters for object mapping.
*/
public static function get_objectid_mapping() {
return array(
'db' => 'attendance',
'restore' => 'attendance'
);
}
}

2
classes/event/status_updated.php

@ -84,7 +84,7 @@ class status_updated extends \core\event\base {
*/
protected function get_legacy_logdata() {
return array($this->courseid, 'attendance', 'status updated', $this->get_url(),
$this->other['updated'], $this->contextinstanceid);
'', $this->contextinstanceid);
}
/**

86
classes/form/import/sessions.php

@ -0,0 +1,86 @@
<?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/>.
/**
* This file contains the form for importing sessions from a file.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\form\import;
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
use core_text;
use csv_import_reader;
use moodleform;
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/csvlib.class.php');
/**
* Import attendance sessions.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sessions extends moodleform {
/**
* Define the form - called by parent constructor
*/
public function definition() {
global $CFG;
$mform = $this->_form;
$element = $mform->createElement('filepicker', 'importfile', get_string('importfile', 'mod_attendance'));
$mform->addElement($element);
$mform->addHelpButton('importfile', 'importfile', 'mod_attendance');
$mform->addRule('importfile', null, 'required');
$mform->addElement('hidden', 'confirm', 0);
$mform->setType('confirm', PARAM_BOOL);
$choices = csv_import_reader::get_delimiter_list();
$mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'mod_attendance'), $choices);
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter_name', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
$mform->setDefault('delimiter_name', 'semicolon');
} else {
$mform->setDefault('delimiter_name', 'comma');
}
$choices = core_text::get_encodings();
$mform->addElement('select', 'encoding', get_string('encoding', 'mod_attendance'), $choices);
$mform->setDefault('encoding', 'UTF-8');
$this->add_action_buttons(false, get_string('import', 'mod_attendance'));
}
/**
* Display an error on the import form.
*
* @param string $msg
*/
public function set_import_error($msg) {
$mform = $this->_form;
$mform->setElementError('importfile', $msg);
}
}

73
classes/form/import/sessions_confirm.php

@ -0,0 +1,73 @@
<?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/>.
/**
* Import attendance sessions.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\form\import;
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
use moodleform;
require_once($CFG->libdir . '/formslib.php');
/**
* Import attendance sessions.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sessions_confirm extends moodleform {
/**
* Define the form - called by parent constructor
*/
public function definition() {
$importer = $this->_customdata;
$mform = $this->_form;
$mform->addElement('hidden', 'confirm', 1);
$mform->setType('confirm', PARAM_BOOL);
$mform->addElement('hidden', 'importid', $importer->get_importid());
$mform->setType('importid', PARAM_INT);
$requiredheaders = $importer->list_required_headers();
$foundheaders = $importer->list_found_headers();
if (empty($foundheaders)) {
$foundheaders = range(0, count($requiredheaders));
}
$foundheaders[- 1] = get_string('none');
foreach ($requiredheaders as $index => $requiredheader) {
$mform->addElement('select', 'header' . $index, $requiredheader, $foundheaders);
if (isset($foundheaders[$index])) {
$mform->setDefault('header' . $index, $index);
} else {
$mform->setDefault('header' . $index, - 1);
}
}
$this->add_action_buttons(true, get_string('confirm', 'mod_attendance'));
}
}

493
classes/import/sessions.php

@ -0,0 +1,493 @@
<?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/>.
/**
* Import attendance sessions class.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\import;
defined('MOODLE_INTERNAL') || die();
use csv_import_reader;
use mod_attendance_notifyqueue;
use mod_attendance_structure;
use stdClass;
/**
* Import attendance sessions.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sessions {
/** @var string $error The errors message from reading the xml */
protected $error = '';
/** @var array $sessions The sessions info */
protected $sessions = array();
/** @var array $mappings The mappings info */
protected $mappings = array();
/** @var int The id of the csv import */
protected $importid = 0;
/** @var csv_import_reader|null $importer */
protected $importer = null;
/** @var array $foundheaders */
protected $foundheaders = array();
/** @var bool $useprogressbar Control whether importing should use progress bars or not. */
protected $useprogressbar = false;
/** @var \core\progress\display_if_slow|null $progress The progress bar instance. */
protected $progress = null;
/**
* Store an error message for display later
*
* @param string $msg
*/
public function fail($msg) {
$this->error = $msg;
return false;
}
/**
* Get the CSV import id
*
* @return string The import id.
*/
public function get_importid() {
return $this->importid;
}
/**
* Get the list of headers required for import.
*
* @return array The headers (lang strings)
*/
public static function list_required_headers() {
return array(
get_string('course', 'attendance'),
get_string('groups', 'attendance'),
get_string('sessiondate', 'attendance'),
get_string('from', 'attendance'),
get_string('to', 'attendance'),
get_string('description', 'attendance'),
get_string('repeaton', 'attendance'),
get_string('repeatevery', 'attendance'),
get_string('repeatuntil', 'attendance'),
get_string('studentscanmark', 'attendance'),
get_string('passwordgrp', 'attendance'),
get_string('randompassword', 'attendance'),
get_string('subnet', 'attendance'),
get_string('automark', 'attendance'),
get_string('autoassignstatus', 'attendance'),
get_string('absenteereport', 'attendance'),
get_string('preventsharedip', 'attendance'),
get_string('preventsharediptime', 'attendance')
);
}
/**
* Get the list of headers found in the import.
*
* @return array The found headers (names from import)
*/
public function list_found_headers() {
return $this->foundheaders;
}
/**
* Read the data from the mapping form.
*
* @param array $data The mapping data.
*/
protected function read_mapping_data($data) {
if ($data) {
return array(
'course' => $data->header0,
'groups' => $data->header1,
'sessiondate' => $data->header2,
'from' => $data->header3,
'to' => $data->header4,
'description' => $data->header5,
'repeaton' => $data->header6,
'repeatevery' => $data->header7,
'repeatuntil' => $data->header8,
'studentscanmark' => $data->header9,
'passwordgrp' => $data->header10,
'randompassword' => $data->header11,
'subnet' => $data->header12,
'automark' => $data->header13,
'autoassignstatus' => $data->header14,
'absenteereport' => $data->header15,
'preventsharedip' => $data->header16,
'preventsharediptime' => $data->header17,
);
} else {
return array(
'course' => 0,
'groups' => 1,
'sessiondate' => 2,
'from' => 3,
'to' => 4,
'description' => 5,
'repeaton' => 6,
'repeatevery' => 7,
'repeatuntil' => 8,
'studentscanmark' => 9,
'passwordgrp' => 10,
'randompassword' => 11,
'subnet' => 12,
'automark' => 13,
'autoassignstatus' => 14,
'absenteereport' => 15,
'preventsharedip' => 16,
'preventsharediptime' => 17
);
}
}
/**
* Get the a column from the imported data.
*
* @param array $row The imported raw row
* @param int $index The column index we want
* @return string The column data.
*/
protected function get_column_data($row, $index) {
if ($index < 0) {
return '';
}
return isset($row[$index]) ? $row[$index] : '';
}
/**
* Constructor - parses the raw text for sanity.
*
* @param string $text The raw csv text.
* @param string $encoding The encoding of the csv file.
* @param string $delimiter The specified delimiter for the file.
* @param string $importid The id of the csv import.
* @param array $mappingdata The mapping data from the import form.
* @param bool $useprogressbar Whether progress bar should be displayed, to avoid html output on CLI.
*/
public function __construct($text = null, $encoding = null, $delimiter = null, $importid = 0,
$mappingdata = null, $useprogressbar = false) {
global $CFG;
require_once($CFG->libdir . '/csvlib.class.php');
$pluginconfig = get_config('attendance');
$type = 'sessions';
if (! $importid) {
if ($text === null) {
return;
}
$this->importid = csv_import_reader::get_new_iid($type);
$this->importer = new csv_import_reader($this->importid, $type);
if (! $this->importer->load_csv_content($text, $encoding, $delimiter)) {
$this->fail(get_string('invalidimportfile', 'attendance'));
$this->importer->cleanup();
return;
}
} else {
$this->importid = $importid;
$this->importer = new csv_import_reader($this->importid, $type);
}
if (! $this->importer->init()) {
$this->fail(get_string('invalidimportfile', 'attendance'));
$this->importer->cleanup();
return;
}
$this->foundheaders = $this->importer->get_columns();
$this->useprogressbar = $useprogressbar;
$domainid = 1;
$sessions = array();
while ($row = $this->importer->next()) {
// This structure mimics what the UI form returns.
$mapping = $this->read_mapping_data($mappingdata);
$session = new stdClass();
$session->course = $this->get_column_data($row, $mapping['course']);
if (empty($session->course)) {
\mod_attendance_notifyqueue::notify_problem(get_string('error:sessioncourseinvalid', 'attendance'));
continue;
}
// Handle multiple group assignments per session. Expect semicolon separated group names.
$groups = $this->get_column_data($row, $mapping['groups']);
if (! empty($groups)) {
$session->groups = explode(';', $groups);
$session->sessiontype = \mod_attendance_structure::SESSION_GROUP;
} else {
$session->sessiontype = \mod_attendance_structure::SESSION_COMMON;
}
// Expect standardised date format, eg YYYY-MM-DD.
$sessiondate = strtotime($this->get_column_data($row, $mapping['sessiondate']));
if ($sessiondate === false) {
\mod_attendance_notifyqueue::notify_problem(get_string('error:sessiondateinvalid', 'attendance'));
continue;
}
$session->sessiondate = $sessiondate;
// Expect standardised time format, eg HH:MM.
$from = $this->get_column_data($row, $mapping['from']);
if (empty($from)) {
\mod_attendance_notifyqueue::notify_problem(get_string('error:sessionstartinvalid', 'attendance'));
continue;
}
$from = explode(':', $from);
$session->sestime['starthour'] = $from[0];
$session->sestime['startminute'] = $from[1];
$to = $this->get_column_data($row, $mapping['to']);
if (empty($to)) {
\mod_attendance_notifyqueue::notify_problem(get_string('error:sessionendinvalid', 'attendance'));
continue;
}
$to = explode(':', $to);
$session->sestime['endhour'] = $to[0];
$session->sestime['endminute'] = $to[1];
// Wrap the plain text description in html tags.
$session->sdescription['text'] = '<p>' . $this->get_column_data($row, $mapping['description']) . '</p>';
$session->sdescription['format'] = FORMAT_HTML;
$session->sdescription['itemid'] = 0;
$session->passwordgrp = $this->get_column_data($row, $mapping['passwordgrp']);
$session->subnet = $this->get_column_data($row, $mapping['subnet']);
// Set session subnet restriction. Use the default activity level subnet if there isn't one set for this session.
if (empty($session->subnet)) {
$session->usedefaultsubnet = '1';
} else {
$session->usedefaultsubnet = '';
}
if ($mapping['studentscanmark'] == -1) {
$session->studentscanmark = $pluginconfig->studentscanmark_default;
} else {
$session->studentscanmark = $this->get_column_data($row, $mapping['studentscanmark']);
}
if ($mapping['randompassword'] == -1) {
$session->randompassword = $pluginconfig->randompassword_default;
} else {
$session->randompassword = $this->get_column_data($row, $mapping['randompassword']);
}
if ($mapping['automark'] == -1) {
$session->automark = $pluginconfig->automark_default;
} else {
$session->automark = $this->get_column_data($row, $mapping['automark']);
}
if ($mapping['autoassignstatus'] == -1) {
$session->autoassignstatus = $pluginconfig->autoassignstatus;
} else {
$session->autoassignstatus = $this->get_column_data($row, $mapping['autoassignstatus']);
}
if ($mapping['absenteereport'] == -1) {
$session->absenteereport = $pluginconfig->absenteereport_default;
} else {
$session->absenteereport = $this->get_column_data($row, $mapping['absenteereport']);
}
if ($mapping['preventsharedip'] == -1) {
$session->preventsharedip = $pluginconfig->preventsharedip;
} else {
$session->preventsharedip = $this->get_column_data($row, $mapping['preventsharedip']);
}
if ($mapping['preventsharediptime'] == -1) {
$session->preventsharediptime = $pluginconfig->preventsharediptime;
} else {
$session->preventsharediptime = $this->get_column_data($row, $mapping['preventsharediptime']);
}
$session->statusset = 0;
$sessions[] = $session;
}
$this->sessions = $sessions;
$this->importer->close();
if ($this->sessions == null) {
$this->fail(get_string('invalidimportfile', 'attendance'));
return;
} else {
// We are calling from browser, display progress bar.
if ($this->useprogressbar === true) {
$this->progress = new \core\progress\display_if_slow(get_string('processingfile', 'attendance'));
$this->progress->start_html();
} else {
// Avoid html output on CLI scripts.
$this->progress = new \core\progress\none();
}
$this->progress->start_progress('', count($this->sessions));
raise_memory_limit(MEMORY_EXTRA);
$this->progress->end_progress();
}
}
/**
* Get parse errors.
*
* @return array of errors from parsing the xml.
*/
public function get_error() {
return $this->error;
}
/**
* Create sessions using the CSV data.
*
* @return void
*/
public function import() {
global $DB;
// Count of sessions added.
$okcount = 0;
foreach ($this->sessions as $session) {
$groupids = array();
// Check course shortname matches.
if ($DB->record_exists('course', array(
'shortname' => $session->course
))) {
// Get course.
$course = $DB->get_record('course', array(
'shortname' => $session->course
), '*', MUST_EXIST);
// Check course has activities.
if ($DB->record_exists('attendance', array(
'course' => $course->id
))) {
// Translate group names to group IDs. They are unique per course.
if ($session->sessiontype === \mod_attendance_structure::SESSION_GROUP) {
foreach ($session->groups as $groupname) {
$gid = groups_get_group_by_name($course->id, $groupname);
if ($gid === false) {
\mod_attendance_notifyqueue::notify_problem(get_string('sessionunknowngroup',
'attendance', $groupname));
} else {
$groupids[] = $gid;
}
}
$session->groups = $groupids;
}
// Get activities in course.
$activities = $DB->get_recordset('attendance', array(
'course' => $course->id
), 'id', 'id');
foreach ($activities as $activity) {
// Build the session data.
$cm = get_coursemodule_from_instance('attendance', $activity->id, $course->id);
if (!empty($cm->deletioninprogress)) {
// Don't do anything if this attendance is in recycle bin.
continue;
}
$att = new mod_attendance_structure($activity, $cm, $course);
$sessions = attendance_construct_sessions_data_for_add($session, $att);
foreach ($sessions as $index => $sess) {
// Check for duplicate sessions.
if ($this->session_exists($sess)) {
mod_attendance_notifyqueue::notify_message(get_string('sessionduplicate', 'attendance', (array(
'course' => $session->course,
'activity' => $cm->name
))));
unset($sessions[$index]);
} else {
$okcount ++;
}
}
if (! empty($sessions)) {
$att->add_sessions($sessions);
}
}
$activities->close();
} else {
mod_attendance_notifyqueue::notify_problem(get_string('error:coursehasnoattendance',
'attendance', $session->course));
}
} else {
mod_attendance_notifyqueue::notify_problem(get_string('error:coursenotfound', 'attendance', $session->course));
}
}
$message = get_string('sessionsgenerated', 'attendance', $okcount);
if ($okcount < 1) {
mod_attendance_notifyqueue::notify_message($message);
} else {
mod_attendance_notifyqueue::notify_success($message);
}
// Trigger a sessions imported event.
$event = \mod_attendance\event\sessions_imported::create(array(
'objectid' => 0,
'context' => \context_system::instance(),
'other' => array(
'count' => $okcount
)
));
$event->trigger();
}
/**
* Check if an identical session exists.
*
* @param stdClass $session
* @return boolean
*/
private function session_exists(stdClass $session) {
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;
if ($DB->record_exists('attendance_sessions', $check)) {
return true;
}
return false;
}
}

6
classes/notifyqueue.php

@ -52,7 +52,7 @@ class mod_attendance_notifyqueue {
* @param string $message a text with a message
*/
public static function notify_problem($message) {
self::queue_message($message, \core\output\notification::NOTIFY_PROBLEM);
self::queue_message($message, \core\output\notification::NOTIFY_ERROR);
}
/**
@ -61,7 +61,7 @@ class mod_attendance_notifyqueue {
* @param string $message a text with a message
*/
public static function notify_message($message) {
self::queue_message($message, \core\output\notification::NOTIFY_MESSAGE);
self::queue_message($message, \core\output\notification::NOTIFY_INFO);
}
/**
@ -79,7 +79,7 @@ class mod_attendance_notifyqueue {
* @param string $message a text with a message
* @param string $messagetype one of the \core\output\notification messages ('message', 'suceess' or 'problem')
*/
private static function queue_message($message, $messagetype=\core\output\notification::NOTIFY_MESSAGE) {
private static function queue_message($message, $messagetype=\core\output\notification::NOTIFY_INFO) {
global $SESSION;
if (!isset($SESSION->mod_attendance_notifyqueue)) {

5
classes/page_with_filter_controls.php

@ -64,7 +64,7 @@ class mod_attendance_page_with_filter_controls {
public $selectortype = self::SELECTOR_NONE;
/** @var int default view. */
protected $defaultview = ATT_VIEW_WEEKS;
protected $defaultview;
/** @var stdClass course module record. */
private $cm;
@ -82,6 +82,9 @@ class mod_attendance_page_with_filter_controls {
*/
public function init($cm) {
$this->cm = $cm;
if (empty($this->defaultview)) {
$this->defaultview = get_config('attendance', 'defaultview');
}
$this->init_view();
$this->init_curdate();
$this->init_start_end_date();

6
classes/report_page_params.php

@ -35,6 +35,8 @@ class mod_attendance_report_page_params extends mod_attendance_page_with_filter_
/** @var int */
public $sort;
/** @var int */
public $showextrauserdetails;
/** @var int */
public $showsessiondetails;
/** @var int */
public $sessiondetailspos;
@ -73,6 +75,10 @@ class mod_attendance_report_page_params extends mod_attendance_page_with_filter_
$params['sort'] = $this->sort;
}
if (empty($this->showextrauserdetails)) {
$params['showextrauserdetails'] = 0;
}
if (empty($this->showsessiondetails)) {
$params['showsessiondetails'] = 0;
}

187
classes/structure.php

@ -70,6 +70,15 @@ class mod_attendance_structure {
/** @var string subnets (IP range) for student self selection. */
public $subnet;
/** @var string subnets (IP range) for student self selection. */
public $automark;
/** @var boolean flag set when automarking is complete. */
public $automarkcompleted;
/** @var int Define if extra user details should be shown in reports */
public $showextrauserdetails;
/** @var int Define if session details should be shown in reports */
public $showsessiondetails;
@ -119,6 +128,9 @@ class mod_attendance_structure {
$this->pageparams = $pageparams;
if (isset($pageparams->showextrauserdetails) && $pageparams->showextrauserdetails != $this->showextrauserdetails) {
$DB->set_field('attendance', 'showextrauserdetails', $pageparams->showextrauserdetails, array('id' => $this->id));
}
if (isset($pageparams->showsessiondetails) && $pageparams->showsessiondetails != $this->showsessiondetails) {
$DB->set_field('attendance', 'showsessiondetails', $pageparams->showsessiondetails, array('id' => $this->id));
}
@ -362,6 +374,16 @@ class mod_attendance_structure {
return new moodle_url('/mod/attendance/report.php', $params);
}
/**
* Get url for report.
* @param array $params
* @return moodle_url of report.php for attendance instance
*/
public function url_absentee($params=array()) {
$params = array_merge(array('id' => $this->cm->id), $params);
return new moodle_url('/mod/attendance/absentee.php', $params);
}
/**
* Get url for export.
*
@ -386,6 +408,20 @@ class mod_attendance_structure {
return new moodle_url('/mod/attendance/preferences.php', $params);
}
/**
* Get preferences url
* @param array $params
* @return moodle_url of attsettings.php for attendance instance
*/
public function url_warnings($params=array()) {
// Add the statusset params.
if (isset($this->pageparams->statusset) && !isset($params['statusset'])) {
$params['statusset'] = $this->pageparams->statusset;
}
$params = array_merge(array('id' => $this->cm->id), $params);
return new moodle_url('/mod/attendance/warnings.php', $params);
}
/**
* Get take url.
* @param array $params
@ -416,6 +452,10 @@ class mod_attendance_structure {
foreach ($sessions as $sess) {
$sess->attendanceid = $this->id;
$sess->automarkcompleted = 0;
if (!isset($sess->automark)) {
$sess->automark = 0;
}
$sess->id = $DB->insert_record('attendance_sessions', $sess);
$description = file_save_draft_area_files($sess->descriptionitemid,
@ -440,8 +480,26 @@ class mod_attendance_structure {
$sess->description = $description;
$sess->lasttaken = 0;
$sess->lasttakenby = 0;
$sess->studentscanmark = 0;
$sess->studentpassword = '';
if (!isset($sess->studentscanmark)) {
$sess->studentscanmark = 0;
}
if (!isset($sess->autoassignstatus)) {
$sess->autoassignstatus = 0;
}
if (!isset($sess->studentpassword)) {
$sess->studentpassword = '';
}
if (!isset($sess->subnet)) {
$sess->subnet = '';
}
if (!isset($sess->preventsharedip)) {
$sess->preventsharedip = 0;
}
if (!isset($sess->preventsharediptime)) {
$sess->preventsharediptime = '';
}
$event->add_record_snapshot('attendance_sessions', $sess);
$event->trigger();
@ -474,25 +532,58 @@ class mod_attendance_structure {
$sess->descriptionformat = $formdata->sdescription['format'];
$sess->studentscanmark = 0;
$sess->autoassignstatus = 0;
$sess->studentpassword = '';
$sess->subnet = '';
$sess->automark = 0;
$sess->automarkcompleted = 0;
$sess->preventsharedip = 0;
$sess->preventsharediptime = '';
if (!empty(get_config('attendance', 'enablewarnings'))) {
$sess->absenteereport = empty($formdata->absenteereport) ? 0 : 1;
}
if (!empty($formdata->autoassignstatus)) {
$sess->autoassignstatus = $formdata->autoassignstatus;
}
if (!empty(get_config('attendance', 'studentscanmark')) &&
!empty($formdata->studentscanmark)) {
$sess->studentscanmark = $formdata->studentscanmark;
$sess->studentpassword = $formdata->studentpassword;
$sess->autoassignstatus = $formdata->autoassignstatus;
if (!empty($formdata->usedefaultsubnet)) {
$sess->subnet = $this->subnet;
} else {
$sess->subnet = $formdata->subnet;
}
if (!empty($formdata->automark)) {
$sess->automark = $formdata->automark;
}
if (!empty($formdata->preventsharedip)) {
$sess->preventsharedip = $formdata->preventsharedip;
}
if (!empty($formdata->preventsharediptime)) {
$sess->preventsharediptime = $formdata->preventsharediptime;
}
}
$sess->timemodified = time();
$DB->update_record('attendance_sessions', $sess);
attendance_update_calendar_event($sess->caleventid, $sess->duration, $sess->sessdate);
if (empty($sess->caleventid)) {
// This shouldn't really happen, but just in case to prevent fatal error.
attendance_create_calendar_event($sess);
} else {
attendance_update_calendar_event($sess->caleventid, $sess->duration, $sess->sessdate);
}
$info = construct_session_full_date_time($sess->sessdate, $sess->duration);
$event = \mod_attendance\event\session_updated::create(array(
'objectid' => $this->id,
'context' => $this->context,
'other' => array('info' => $info, 'sessionid' => $sessionid,
'action' => mod_attendance_sessions_page_params::ACTION_UPDATE)));
'action' => mod_attendance_sessions_page_params::ACTION_UPDATE)));
$event->add_record_snapshot('course_modules', $this->cm);
$event->add_record_snapshot('attendance_sessions', $sess);
$event->trigger();
@ -518,6 +609,7 @@ class mod_attendance_structure {
$record->sessionid = $mformdata->sessid;
$record->timetaken = $now;
$record->takenby = $USER->id;
$record->ipaddress = getremoteaddr(null);
$dbsesslog = $this->get_session_log($mformdata->sessid);
if (array_key_exists($record->studentid, $dbsesslog)) {
@ -590,14 +682,21 @@ class mod_attendance_structure {
$sesslog[$sid]->takenby = $USER->id;
}
}
// Get existing session log.
$dbsesslog = $this->get_session_log($this->pageparams->sessionid);
foreach ($sesslog as $log) {
// Don't save a record if no statusid or remark.
if (!empty($log->statusid) || !empty($log->remarks)) {
if (array_key_exists($log->studentid, $dbsesslog)) {
$log->id = $dbsesslog[$log->studentid]->id;
$DB->update_record('attendance_log', $log);
// Check if anything important has changed before updating record.
// Don't update timetaken/takenby records if nothing has changed.
if ($dbsesslog[$log->studentid]->remarks <> $log->remarks ||
$dbsesslog[$log->studentid]->statusid <> $log->statusid ||
$dbsesslog[$log->studentid]->statusset <> $log->statusset) {
$log->id = $dbsesslog[$log->studentid]->id;
$DB->update_record('attendance_log', $log);
}
} else {
$DB->insert_record('attendance_log', $log, false);
}
@ -607,6 +706,7 @@ class mod_attendance_structure {
$session = $this->get_session_info($this->pageparams->sessionid);
$session->lasttaken = $now;
$session->lasttakenby = $USER->id;
$DB->update_record('attendance_sessions', $session);
if ($this->grade != 0) {
@ -678,7 +778,7 @@ class mod_attendance_structure {
if ($page) {
$usersperpage = $this->pageparams->perpage;
if (!empty($CFG->enablegroupmembersonly) and $this->cm->groupmembersonly) {
if (!empty($this->cm->groupingid)) {
$startusers = ($page - 1) * $usersperpage;
if ($groupid == 0) {
$groups = array_keys(groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid, 'g.id'));
@ -695,7 +795,7 @@ class mod_attendance_structure {
$orderby, $startusers, $usersperpage);
}
} else {
if (!empty($CFG->enablegroupmembersonly) and $this->cm->groupmembersonly) {
if (!empty($this->cm->groupingid)) {
if ($groupid == 0) {
$groups = array_keys(groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid, 'g.id'));
} else {
@ -742,7 +842,7 @@ class mod_attendance_structure {
// Add the 'temporary' users to this list.
$tempusers = $DB->get_records('attendance_tempusers', array('courseid' => $this->course->id));
foreach ($tempusers as $tempuser) {
$users[] = self::tempuser_to_user($tempuser);
$users[$tempuser->studentid] = self::tempuser_to_user($tempuser);
}
return $users;
@ -755,6 +855,8 @@ class mod_attendance_structure {
* @return object
*/
protected static function tempuser_to_user($tempuser) {
global $CFG;
$ret = (object)array(
'id' => $tempuser->studentid,
'firstname' => $tempuser->fullname,
@ -766,11 +868,17 @@ class mod_attendance_structure {
'picture' => 0,
'type' => 'temporary',
);
foreach (get_all_user_name_fields() as $namefield) {
$allfields = get_all_user_name_fields();
if (!empty($CFG->showuseridentity)) {
$allfields = array_merge($allfields, explode(',', $CFG->showuseridentity));
}
foreach ($allfields as $namefield) {
if (!isset($ret->$namefield)) {
$ret->$namefield = '';
}
}
return $ret;
}
@ -902,7 +1010,7 @@ class mod_attendance_structure {
public function get_session_log($sessionid) {
global $DB;
return $DB->get_records('attendance_log', array('sessionid' => $sessionid), '', 'studentid,statusid,remarks,id');
return $DB->get_records('attendance_log', array('sessionid' => $sessionid), '', 'studentid,statusid,remarks,id,statusset');
}
/**
@ -928,7 +1036,8 @@ class mod_attendance_structure {
$where = "ats.attendanceid = :aid AND ats.sessdate >= :csdate";
}
if ($this->get_group_mode()) {
$sql = "SELECT ats.id, ats.sessdate, ats.groupid, al.statusid, al.remarks
$sql = "SELECT ats.id, ats.sessdate, ats.groupid, al.statusid, al.remarks,
ats.preventsharediptime, ats.preventsharedip
FROM {attendance_sessions} ats
JOIN {attendance_log} al ON ats.id = al.sessionid AND al.studentid = :uid
LEFT JOIN {groups_members} gm ON gm.userid = al.studentid AND gm.groupid = ats.groupid
@ -943,7 +1052,8 @@ class mod_attendance_structure {
'edate' => $this->pageparams->enddate);
} else {
$sql = "SELECT ats.id, ats.sessdate, ats.groupid, al.statusid, al.remarks
$sql = "SELECT ats.id, ats.sessdate, ats.groupid, al.statusid, al.remarks,
ats.preventsharediptime, ats.preventsharedip
FROM {attendance_sessions} ats
JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid
@ -985,7 +1095,8 @@ class mod_attendance_structure {
$id = $DB->sql_concat(':value', 'ats.id');
if ($this->get_group_mode()) {
$sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description,
al.statusid, al.remarks, ats.studentscanmark
al.statusid, al.remarks, ats.studentscanmark, ats.autoassignstatus,
ats.preventsharedip, ats.preventsharediptime
FROM {attendance_sessions} ats
RIGHT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid
@ -994,7 +1105,8 @@ class mod_attendance_structure {
ORDER BY ats.sessdate ASC";
} else {
$sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, ats.statusset,
al.statusid, al.remarks, ats.studentscanmark
al.statusid, al.remarks, ats.studentscanmark, ats.autoassignstatus,
ats.preventsharedip, ats.preventsharediptime
FROM {attendance_sessions} ats
RIGHT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid
@ -1023,9 +1135,9 @@ class mod_attendance_structure {
} else {
$where = "ats.attendanceid = :aid AND ats.sessdate >= :csdate AND ats.groupid $gsql";
}
$sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, ats.statusset,
al.statusid, al.remarks, ats.studentscanmark
al.statusid, al.remarks, ats.studentscanmark, ats.autoassignstatus,
ats.preventsharedip, ats.preventsharediptime
FROM {attendance_sessions} ats
LEFT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid
@ -1122,4 +1234,41 @@ class mod_attendance_structure {
return null;
}
/**
* Gets the status to use when auto-marking.
*
* @param int $time the time the user first accessed the course.
* @param int $sessionid the related sessionid to check.
* @return int the statusid to assign to this user.
*/
public function get_automark_status($time, $sessionid) {
$statuses = $this->get_statuses();
// Statuses are returned highest grade first, find the first high grade we can assign to this user.
// Get status to use when unmarked.
$session = $this->sessioninfo[$sessionid];
$duration = $session->duration;
if (empty($duration)) {
$duration = get_config('attendance', 'studentscanmarksessiontimeend') * 60;
}
if ($time > $session->sessdate + $duration) {
// This session closed after the users access - use the unmarked state.
foreach ($statuses as $status) {
if (!empty($status->setunmarked)) {
return $status->id;
}
}
} else {
foreach ($statuses as $status) {
if ($status->studentavailability !== '0' &&
$this->sessioninfo[$sessionid]->sessdate + ($status->studentavailability * 60) > $time) {
// Found first status we could set.
return $status->id;
}
}
}
return;
}
}

4
classes/summary.php

@ -235,7 +235,7 @@ class mod_attendance_summary {
{$joingroup}
WHERE ats.attendanceid = :attid
AND ats.sessdate >= :cstartdate
AND ats.lasttakenby != 0
AND ats.lasttaken != 0
{$where}
GROUP BY atl.studentid";
$this->userspoints = $DB->get_records_sql($sql, $params);
@ -293,7 +293,7 @@ class mod_attendance_summary {
{$joingroup}
WHERE ats.attendanceid = :attid
AND ats.sessdate >= :cstartdate
AND ats.lasttakenby != 0
AND ats.lasttaken != 0
{$where}
GROUP BY atl.studentid, sts.setnumber, sts.acronym";
$this->userstakensessionsbyacronym = array();

230
classes/task/auto_mark.php

@ -0,0 +1,230 @@
<?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/>.
/**
* Attendance task - auto mark.
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\task;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/attendance/locallib.php');
/**
* get_scores class, used to get scores for submitted files.
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auto_mark extends \core\task\scheduled_task {
/**
* Returns localised general event name.
*
* @return string
*/
public function get_name() {
// Shown in admin screens.
return get_string('automarktask', 'mod_attendance');
}
/**
* Execte the task.
*/
public function execute() {
global $DB;
// Create some cache vars - might be nice to restructure this and make a smaller number of sql calls.
$cachecm = array();
$cacheatt = array();
$cachecourse = array();
$now = time(); // Store current time to use in queries so they all match nicely.
$sessions = $DB->get_recordset_select('attendance_sessions',
'automark > 0 AND automarkcompleted < 2 AND sessdate < ? ', array($now));
foreach ($sessions as $session) {
if ($session->sessdate + $session->duration < $now || // If session is over.
// OR if session is currently open and automark is set to do all.
($session->sessdate < $now && $session->automark == 1)) {
$userfirstaccess = array();
$donesomething = false; // Only trigger grades/events when an update actually occurs.
$sessionover = false; // Is this session over?
if ($session->sessdate + $session->duration < $now) {
$sessionover = true;
}
// Store cm/att/course in cachefields so we don't make unnecessary db calls.
// Would probably be nice to grab this stuff outside of the loop.
// Make sure this status set has something to setunmarked.
$setunmarked = $DB->get_field('attendance_statuses', 'id',
array('attendanceid' => $session->attendanceid, 'setnumber' => $session->statusset,
'setunmarked' => 1, 'deleted' => 0));
if (empty($setunmarked)) {
mtrace("No unmarked status configured for session id: ".$session->id);
continue;
}
if (empty($cacheatt[$session->attendanceid])) {
$cacheatt[$session->attendanceid] = $DB->get_record('attendance', array('id' => $session->attendanceid));
}
if (empty($cachecm[$session->attendanceid])) {
$cachecm[$session->attendanceid] = get_coursemodule_from_instance('attendance',
$session->attendanceid, $cacheatt[$session->attendanceid]->course);
}
$courseid = $cacheatt[$session->attendanceid]->course;
if (empty($cachecourse[$courseid])) {
$cachecourse[$courseid] = $DB->get_record('course', array('id' => $courseid));
}
$context = \context_module::instance($cachecm[$session->attendanceid]->id);
$pageparams = new \mod_attendance_take_page_params();
$pageparams->group = $session->groupid;
if (empty($session->groupid)) {
$pageparams->grouptype = 0;
} else {
$pageparams->grouptype = 1;
}
$pageparams->sessionid = $session->id;
if ($session->automark == 1) {
$userfirstacess = array();
// If set to do full automarking, get all users that have accessed course during session open.
$id = $DB->sql_concat('userid', 'ip'); // Users may access from multiple ip, make the first field unique.
$sql = "SELECT $id, userid, ip, min(timecreated) as timecreated
FROM {logstore_standard_log}
WHERE courseid = ? AND timecreated > ? AND timecreated < ?
GROUP BY userid, ip";
$timestart = $session->sessdate;
if (empty($session->lasttakenby) && $session->lasttaken > $timestart) {
// If the last time session was taken it was done automatically, use the last time taken
// as the start time for the logs we are interested in to help with performance.
$timestart = $session->lasttaken;
}
$duration = $session->duration;
if (empty($duration)) {
$duration = get_config('attendance', 'studentscanmarksessiontimeend') * 60;
}
$timeend = $timestart + $duration;
$logusers = $DB->get_recordset_sql($sql, array($courseid, $timestart, $timeend));
// Check if user access is in allowed subnet.
foreach ($logusers as $loguser) {
if (!empty($session->subnet) && !address_in_subnet($loguser->ip, $session->subnet)) {
// This record isn't in the right subnet.
continue;
}
if (empty($userfirstaccess[$loguser->userid]) ||
$userfirstaccess[$loguser->userid] > $loguser->timecreated) {
// Users may have accessed from mulitple ip addresses, find the earliest access.
$userfirstaccess[$loguser->userid] = $loguser->timecreated;
}
}
$logusers->close();
}
// Get all unmarked students.
$att = new \mod_attendance_structure($cacheatt[$session->attendanceid],
$cachecm[$session->attendanceid], $cachecourse[$courseid], $context, $pageparams);
$users = $att->get_users($session->groupid, 0);
$existinglog = $DB->get_recordset('attendance_log', array('sessionid' => $session->id));
$updated = 0;
foreach ($existinglog as $log) {
if (empty($log->statusid)) {
if ($sessionover || !empty($userfirstaccess[$log->studentid])) {
// Status needs updating.
if ($sessionover) {
$log->statusid = $setunmarked;
} else if (!empty($userfirstaccess[$log->studentid])) {
$log->statusid = $att->get_automark_status($userfirstaccess[$log->studentid], $session->id);
}
if (!empty($log->statusid)) {
$log->timetaken = $now;
$log->takenby = 0;
$log->remarks = get_string('autorecorded', 'attendance');
$DB->update_record('attendance_log', $log);
$updated++;
$donesomething = true;
}
}
}
unset($users[$log->studentid]);
}
$existinglog->close();
mtrace($updated . " session status updated");
$newlog = new \stdClass();
$newlog->timetaken = $now;
$newlog->takenby = 0;
$newlog->sessionid = $session->id;
$newlog->remarks = get_string('autorecorded', 'attendance');
$newlog->statusset = implode(',', array_keys( (array)$att->get_statuses()));
$added = 0;
foreach ($users as $user) {
if ($sessionover || !empty($userfirstaccess[$user->id])) {
if ($sessionover) {
$newlog->statusid = $setunmarked;
} else if (!empty($userfirstaccess[$user->id])) {
$newlog->statusid = $att->get_automark_status($userfirstaccess[$user->id], $session->id);
}
if (!empty($newlog->statusid)) {
$newlog->studentid = $user->id;
$DB->insert_record('attendance_log', $newlog);
$added++;
$donesomething = true;
}
}
}
mtrace($added . " session status inserted");
// Update lasttaken time and automarkcompleted for this session.
$session->lasttaken = $now;
$session->lasttakenby = 0;
if ($sessionover) {
$session->automarkcompleted = 2;
} else {
$session->automarkcompleted = 1;
}
$DB->update_record('attendance_sessions', $session);
if ($donesomething) {
if ($att->grade != 0) {
$att->update_users_grade(array_keys($users));
}
$params = array(
'sessionid' => $att->pageparams->sessionid,
'grouptype' => $att->pageparams->grouptype);
$event = \mod_attendance\event\attendance_taken::create(array(
'objectid' => $att->id,
'context' => $att->context,
'other' => $params));
$event->add_record_snapshot('course_modules', $att->cm);
$event->add_record_snapshot('attendance_sessions', $session);
$event->trigger();
}
}
}
}
}

158
classes/task/notify.php

@ -0,0 +1,158 @@
<?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/>.
/**
* Attendance task - Send warnings.
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\task;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/attendance/locallib.php');
/**
* Task class
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class notify extends \core\task\scheduled_task {
/**
* Returns localised general event name.
*
* @return string
*/
public function get_name() {
// Shown in admin screens.
return get_string('notifytask', 'mod_attendance');
}
/**
* Execute the task.
*/
public function execute() {
global $DB;
if (empty(get_config('attendance', 'enablewarnings'))) {
return; // Warnings not enabled.
}
$now = time(); // Store current time to use in queries so they all match nicely.
$orderby = 'ORDER BY cm.id, atl.studentid, n.warningpercent ASC';
// Get records for attendance sessions that have been updated since last time this task ran.
// Note: this returns all users for these sessions - even if the users attendance wasn't changed
// since last time we ran, before sending a notification we check to see if the users have
// updated attendance logs since last time they were notified.
$records = attendance_get_users_to_notify(array(), $orderby, true);
$sentnotifications = array();
$thirdpartynotifications = array();
$numsentusers = 0;
$numsentthird = 0;
foreach ($records as $record) {
if (empty($sentnotifications[$record->userid])) {
$sentnotifications[$record->userid] = array();
}
if (!empty($record->emailuser)) {
// Only send one warning to this user from each attendance in this run.
// Flag any higher percent notifications as sent.
if (empty($sentnotifications[$record->userid]) || !in_array($record->aid, $sentnotifications[$record->userid])) {
// If has previously been sent a warning, check to see if this user has
// attendance updated since the last time the notification was sent.
if (!empty($record->timesent)) {
$sql = "SELECT *
FROM {attendance_log} l
JOIN {attendance_sessions} s ON s.id = l.sessionid
WHERE s.attendanceid = ? AND studentid = ? AND timetaken > ?";
if (!$DB->record_exists_sql($sql, array($record->aid, $record->userid, $record->timesent))) {
continue; // Skip this record and move to the next user.
}
}
// Convert variables in emailcontent.
$record = attendance_template_variables($record);
$user = $DB->get_record('user', array('id' => $record->userid));
$from = \core_user::get_noreply_user();
$oldforcelang = force_current_language($user->lang);
$emailcontent = format_text($record->emailcontent, $record->emailcontentformat);
$emailsubject = format_text($record->emailsubject, FORMAT_HTML);
email_to_user($user, $from, $emailsubject, $emailcontent, $emailcontent);
force_current_language($oldforcelang);
$sentnotifications[$record->userid][] = $record->aid;
$numsentusers++;
}
}
// Only send one warning to this user from each attendance in this run. - flag any higher percent notifications as sent.
if (!empty($record->thirdpartyemails)) {
$sendto = explode(',', $record->thirdpartyemails);
$record->percent = round($record->percent * 100)."%";
$context = \context_module::instance($record->cmid);
foreach ($sendto as $senduser) {
if (empty($senduser)) {
// Probably an extra comma in the thirdpartyusers field.
continue;
}
// Check user is allowed to receive warningemails.
if (has_capability('mod/attendance:warningemails', $context, $senduser)) {
if (empty($thirdpartynotifications[$senduser])) {
$thirdpartynotifications[$senduser] = array();
}
if (!isset($thirdpartynotifications[$senduser][$record->aid . '_' . $record->userid])) {
$thirdpartynotifications[$senduser][$record->aid . '_' . $record->userid]
= get_string('thirdpartyemailtext', 'attendance', $record);
}
} else {
mtrace("user".$senduser. "does not have capablity in cm".$record->cmid);
}
}
}
$notify = new \stdClass();
$notify->userid = $record->userid;
$notify->notifyid = $record->notifyid;
$notify->timesent = $now;
$DB->insert_record('attendance_warning_done', $notify);
}
if (!empty($numsentusers)) {
mtrace($numsentusers ." user emails sent");
}
if (!empty($thirdpartynotifications)) {
foreach ($thirdpartynotifications as $sendid => $notifications) {
$user = $DB->get_record('user', array('id' => $sendid));
$from = \core_user::get_noreply_user();
$oldforcelang = force_current_language($user->lang);
$emailcontent = implode("\n", $notifications);
$emailcontent .= "\n\n".get_string('thirdpartyemailtextfooter', 'attendance');
$emailcontent = format_text($emailcontent);
$emailsubject = get_string('thirdpartyemailsubject', 'attendance');
email_to_user($user, $from, $emailsubject, $emailcontent, $emailcontent);
force_current_language($oldforcelang);
$numsentthird++;
}
if (!empty($numsentthird)) {
mtrace($numsentthird ." thirdparty emails sent");
}
}
}
}

124
coursesummary.php

@ -0,0 +1,124 @@
<?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/>.
/**
* Attendance course summary report.
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->dirroot.'/mod/attendance/lib.php');
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->libdir.'/coursecatlib.php');
$category = optional_param('category', 0, PARAM_INT);
$download = optional_param('download', '', PARAM_ALPHA);
$sort = optional_param('tsort', '', PARAM_ALPHA);
$fromcourse = optional_param('fromcourse', 0, PARAM_INT);
$admin = false;
if (empty($fromcourse)) {
$admin = true;
admin_externalpage_setup('managemodules');
} else {
require_login($fromcourse);
}
if (empty($category)) {
$context = context_system::instance();
$courses = array(); // Show all courses.
} else {
$context = context_coursecat::instance($category);
$coursecat = coursecat::get($category);
$courses = $coursecat->get_courses(array('recursive' => true, 'idonly' => true));
}
// Check permissions.
require_capability('mod/attendance:viewsummaryreports', $context);
$exportfilename = 'attendancecoursesummary.csv';
$PAGE->set_url('/mod/attendance/coursesummary.php', array('category' => $category));
$PAGE->set_heading($SITE->fullname);
$table = new flexible_table('attendancecoursesummary');
$table->define_baseurl($PAGE->url);
if (!$table->is_downloading($download, $exportfilename)) {
echo $OUTPUT->header();
$heading = get_string('coursesummary', 'mod_attendance');
if (!empty($category)) {
$heading .= " (".$coursecat->name.")";
}
echo $OUTPUT->heading($heading);
if ($admin) {
// Only show tabs if displaying via the admin page.
$tabmenu = attendance_print_settings_tabs('coursesummary');
echo $tabmenu;
}
$url = new moodle_url('/mod/attendance/coursesummary.php', array('category' => $category, 'fromcourse' => $fromcourse));
if ($admin) {
$options = coursecat::make_categories_list('mod/attendance:viewsummaryreports');
echo $OUTPUT->single_select($url, 'category', $options, $category);
}
}
$table->define_columns(array('course', 'percentage'));
$table->define_headers(array(get_string('course'),
get_string('averageattendance', 'attendance')));
$table->sortable(true);
$table->no_sorting('course');
$table->set_attribute('cellspacing', '0');
$table->set_attribute('class', 'generaltable generalbox');
$table->show_download_buttons_at(array(TABLE_P_BOTTOM));
$table->setup();
// Work out direction of sort required.
$sortcolumns = $table->get_sort_columns();
// Now do sorting if specified.
$orderby = ' ORDER BY percentage ASC';
if (!empty($sort)) {
$direction = ' DESC';
if (!empty($sortcolumns[$sort]) && $sortcolumns[$sort] == SORT_ASC) {
$direction = ' ASC';
}
$orderby = " ORDER BY $sort $direction";
}
$records = attendance_course_users_points($courses, $orderby);
foreach ($records as $record) {
if (!$table->is_downloading($download, $exportfilename)) {
$url = new moodle_url('/mod/attendance/index.php', array('id' => $record->courseid));
$name = html_writer::link($url, $record->coursename);
} else {
$name = $record->coursename;
}
$table->add_data(array($name, round($record->percentage * 100)."%"));
}
$table->finish_output();
if (!$table->is_downloading()) {
echo $OUTPUT->footer();
}

21
db/access.php

@ -132,4 +132,25 @@ $capabilities = array(
'manager' => CAP_ALLOW
)
),
// Allow access to site level reports.
'mod/attendance:viewsummaryreports' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_COURSECAT,
'archetypes' => array(
'manager' => CAP_ALLOW
)
),
// Users that can receive extra warning e-mails.
'mod/attendance:warningemails' => array(
'riskbitmask' => RISK_DATALOSS,
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
)
);

51
db/install.xml

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/attendance/db" VERSION="20170307" COMMENT="XMLDB file for Moodle mod/attendance"
<XMLDB PATH="mod/attendance/db" VERSION="20170620" COMMENT="XMLDB file for Moodle mod/attendance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
>
<TABLES>
<TABLE NAME="attendance" COMMENT="Attendance module table">
<FIELDS>
@ -13,9 +13,10 @@
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time the settings for this attendance instance were last modified."/>
<FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="This field is a requirement for activity modules."/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="This field is a requirement for activity modules."/>
<FIELD NAME="subnet" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Restrict ability for students to mark by subnet."/>
<FIELD NAME="subnet" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Default subnet used when creating sessions."/>
<FIELD NAME="sessiondetailspos" TYPE="char" LENGTH="5" NOTNULL="true" DEFAULT="left" SEQUENCE="false" COMMENT="Position for the session detail columns related to summary columns."/>
<FIELD NAME="showsessiondetails" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Define if session details should be shown in reports."/>
<FIELD NAME="showextrauserdetails" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Define if extra user details should be shown in reports."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for attendance"/>
@ -24,7 +25,7 @@
<INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
</INDEXES>
</TABLE>
<TABLE NAME="attendance_sessions" COMMENT="attendance_sessions table retrofitted from MySQL">
<TABLE NAME="attendance_sessions" COMMENT="attendance_sessions table">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="attendanceid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
@ -37,8 +38,15 @@
<FIELD NAME="description" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="studentscanmark" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="autoassignstatus" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="studentpassword" TYPE="char" LENGTH="50" NOTNULL="false" DEFAULT="" SEQUENCE="false"/>
<FIELD NAME="subnet" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Restrict ability for students to mark by subnet."/>
<FIELD NAME="automark" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="automarkcompleted" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="statusset" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Which set of statuses to use"/>
<FIELD NAME="absenteereport" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="preventsharedip" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="preventsharediptime" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="caleventid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
@ -61,6 +69,7 @@
<FIELD NAME="timetaken" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="When attendance of this student was taken"/>
<FIELD NAME="takenby" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="remarks" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="ipaddress" TYPE="char" LENGTH="45" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for attendance_log"/>
@ -78,6 +87,8 @@
<FIELD NAME="acronym" TYPE="char" LENGTH="2" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="description" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="grade" TYPE="number" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="2"/>
<FIELD NAME="studentavailability" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="How many minutes this status is available when self marking is enabled."/>
<FIELD NAME="setunmarked" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false" COMMENT="Set this status if unmarked at end of session."/>
<FIELD NAME="visible" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="deleted" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="setnumber" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Allows different sets of statuses to be allocated to different sessions"/>
@ -108,5 +119,37 @@
<INDEX NAME="studentid" UNIQUE="true" FIELDS="studentid"/>
</INDEXES>
</TABLE>
<TABLE NAME="attendance_warning" COMMENT="Warning configuration">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="idnumber" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="attendance id or other id relating to this warning."/>
<FIELD NAME="warningpercent" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Percentage that triggers this warning."/>
<FIELD NAME="warnafter" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Start warning after this number of taken sessions."/>
<FIELD NAME="maxwarn" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Maximum number of warnings to send."/>
<FIELD NAME="emailuser" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="Should the user be notified at this level."/>
<FIELD NAME="emailsubject" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Email subject line for emails going to user"/>
<FIELD NAME="emailcontent" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The html-formatted text that should be sent to the user"/>
<FIELD NAME="emailcontentformat" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="Format of the emailcontent field"/>
<FIELD NAME="thirdpartyemails" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="list of extra users to receive warnings"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="level_id" TYPE="unique" FIELDS="idnumber, warningpercent, warnafter"/>
</KEYS>
</TABLE>
<TABLE NAME="attendance_warning_done" COMMENT="Warnings processed">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="notifyid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="id of warning"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="user id of user"/>
<FIELD NAME="timesent" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time warning sent to user."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="notifyid_userid" UNIQUE="false" FIELDS="notifyid, userid"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>

44
db/tasks.php

@ -0,0 +1,44 @@
<?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/>.
/**
* Attendance module tasks.
*
* @package mod_attendance
* @copyright 2017 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => 'mod_attendance\task\auto_mark',
'blocking' => 0,
'minute' => '8',
'hour' => '*',
'day' => '*',
'dayofweek' => '*',
'month' => '*'),
array(
'classname' => 'mod_attendance\task\notify',
'blocking' => 0,
'minute' => '30',
'hour' => '1',
'day' => '*',
'dayofweek' => '*',
'month' => '*')
);

255
db/upgrade.php

@ -245,5 +245,260 @@ function xmldb_attendance_upgrade($oldversion=0) {
upgrade_mod_savepoint(true, 2017042800, 'attendance');
}
if ($oldversion < 2017050202) {
// Define field studentavailability to be added to attendance_statuses.
$table = new xmldb_table('attendance_statuses');
$field = new xmldb_field('studentavailability', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'grade');
// Conditionally launch add field studentavailability.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017050202, 'attendance');
}
if ($oldversion < 2017050204) {
$table = new xmldb_table('attendance_sessions');
$newfield = $table->add_field('subnet', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'studentpassword');
if (!$dbman->field_exists($table, $newfield)) {
$dbman->add_field($table, $newfield);
// The meaning of the subnet in the attendance table has changed - it is now the "default" value - find all existing
// Attendance with subnet set and set the session subnet for these.
$attendances = $DB->get_recordset_select('attendance', 'subnet IS NOT NULL');
foreach ($attendances as $attendance) {
if (!empty($attendance->subnet)) {
// Get all sessions for this attendance.
$sessions = $DB->get_recordset('attendance_sessions', array('attendanceid' => $attendance->id));
foreach ($sessions as $session) {
$session->subnet = $attendance->subnet;
$DB->update_record('attendance_sessions', $session);
}
$sessions->close();
}
}
$attendances->close();
}
upgrade_mod_savepoint(true, 2017050204, 'attendance');
}
if ($oldversion < 2017050206) {
// Define field setunmarked to be added to attendance_statuses.
$table = new xmldb_table('attendance_statuses');
$field = new xmldb_field('setunmarked', XMLDB_TYPE_INTEGER, '2', null, null, null, null, 'studentavailability');
// Conditionally launch add field setunmarked.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define field setunmarked to be added to attendance_statuses.
$table = new xmldb_table('attendance_sessions');
$field = new xmldb_field('automark', XMLDB_TYPE_INTEGER, '1', null, true, null, '0', 'subnet');
// Conditionally launch add field automark.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$field = new xmldb_field('automarkcompleted', XMLDB_TYPE_INTEGER, '1', null, true, null, '0', 'automark');
// Conditionally launch add field automarkcompleted.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017050206, 'attendance');
}
if ($oldversion < 2017050209) {
// Automark values changed.
$default = get_config('attendance', 'automark_default');
if (!empty($default)) { // Change default if set.
set_config('automark_default', 2, 'attendance');
}
// Update any sessions set to use automark = 1.
$sql = "UPDATE {attendance_sessions} SET automark = 2 WHERE automark = 1";
$DB->execute($sql);
// Update automarkcompleted to 2 if already complete.
$sql = "UPDATE {attendance_sessions} SET automarkcompleted = 2 WHERE automarkcompleted = 1";
$DB->execute($sql);
upgrade_mod_savepoint(true, 2017050209, 'attendance');
}
// Add new warning table.
if ($oldversion < 2017050210) {
// Define table attendance_warning_done to be created.
$table = new xmldb_table('attendance_warning_done');
// Adding fields to table attendance_warning_done.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('notifyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('timesent', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table attendance_warning_done.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
// Adding indexes to table attendance_warning_done.
$table->add_index('notifyid_userid', XMLDB_INDEX_UNIQUE, array('notifyid', 'userid'));
// Conditionally launch create table for attendance_warning_done.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017050210, 'attendance');
}
if ($oldversion < 2017050213) {
// Fix key.
$table = new xmldb_table('attendance_warning');
if (!$dbman->table_exists($table)) {
// Adding fields to table attendance_warning.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('idnumber', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('warningpercent', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('warnafter', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('emailuser', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, null);
$table->add_field('emailsubject', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('emailcontent', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
$table->add_field('emailcontentformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, null);
$table->add_field('thirdpartyemails', XMLDB_TYPE_TEXT, null, null, null, null, null);
// Adding keys to table attendance_warning.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('level_id', XMLDB_KEY_UNIQUE, array('idnumber', 'warningpercent', 'warnafter'));
// Conditionally launch create table for attendance_warning.
$dbman->create_table($table);
} else {
// Key definition is probably incorrect so fix it - drop_key dml function doesn't seem to work.
$indexes = $DB->get_indexes('attendance_warning');
foreach ($indexes as $name => $index) {
if ($DB->get_dbfamily() === 'mysql') {
$DB->execute("ALTER TABLE {attendance_warning} DROP INDEX ". $name);
} else {
$DB->execute("DROP INDEX ". $name);
}
}
$index = new xmldb_key('level_id', XMLDB_KEY_UNIQUE, array('idnumber', 'warningpercent', 'warnafter'));
$dbman->add_key($table, $index);
}
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017050213, 'attendance');
}
if ($oldversion < 2017050214) {
// Define field setunmarked to be added to attendance_statuses.
$table = new xmldb_table('attendance_warning');
$field = new xmldb_field('maxwarn', XMLDB_TYPE_INTEGER, '10', null, true, null, '1', 'warnafter');
// Conditionally launch add field automark.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define field setunmarked to be added to attendance_statuses.
$table = new xmldb_table('attendance_warning_done');
$index = new xmldb_index('notifyid_userid', XMLDB_INDEX_UNIQUE, array('notifyid', 'userid'));
if ($dbman->index_exists($table, $index)) {
$dbman->drop_index($table, $index);
}
$index = new xmldb_index('notifyid', XMLDB_INDEX_NOTUNIQUE, array('notifyid', 'userid'));
$dbman->add_index($table, $index);
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017050214, 'attendance');
}
if ($oldversion < 2017050216) {
// Warnings idnumber field should use attendanceid instead of cmid.
$sql = "SELECT cm.id, cm.instance
FROM {course_modules} cm
JOIN {modules} md ON md.id = cm.module AND md.name = 'attendance'";
$idnumbers = $DB->get_records_sql_menu($sql);
$warnings = $DB->get_recordset('attendance_warning');
foreach ($warnings as $warning) {
if (!empty($warning->idnumber) && !empty($idnumbers[$warning->idnumber])) {
$warning->idnumber = $idnumbers[$warning->idnumber];
$DB->update_record("attendance_warning", $warning);
}
}
$warnings->close();
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017050216, 'attendance');
}
if ($oldversion < 2017050222) {
$table = new xmldb_table('attendance_sessions');
$field = new xmldb_field('absenteereport');
$field->set_attributes(XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '1', 'statusset');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2017050222, 'attendance');
}
if ($oldversion < 2017050223) {
$table = new xmldb_table('attendance_sessions');
$field = new xmldb_field('autoassignstatus');
$field->set_attributes(XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'studentscanmark');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2017050223, 'attendance');
}
if ($oldversion < 2017050225) {
$table = new xmldb_table('attendance');
$field = new xmldb_field('showextrauserdetails', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED,
XMLDB_NOTNULL, null, '1', 'showsessiondetails');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2017050225, 'attendance');
}
if ($oldversion < 2017050227) {
$table = new xmldb_table('attendance_sessions');
$field = new xmldb_field('preventsharedip', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED,
XMLDB_NOTNULL, null, '0', 'absenteereport');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$field = new xmldb_field('preventsharediptime', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED,
null, null, null, 'preventsharedip');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$table = new xmldb_table('attendance_log');
$field = new xmldb_field('ipaddress', XMLDB_TYPE_CHAR, '45', null,
null, null, '', 'remarks');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2017050227, 'attendance');
}
return $result;
}

24
defaultstatus.php

@ -51,10 +51,18 @@ switch ($action) {
$newacronym = optional_param('newacronym', null, PARAM_TEXT);
$newdescription = optional_param('newdescription', null, PARAM_TEXT);
$newgrade = optional_param('newgrade', 0, PARAM_RAW);
$newstudentavailability = optional_param('newstudentavailability', null, PARAM_INT);
$newgrade = unformat_float($newgrade);
// Default value uses setnumber/attendanceid = 0.
attendance_add_status($newacronym, $newdescription, $newgrade, 0);
$status = new stdClass();
$status->attendanceid = 0;
$status->acronym = $newacronym;
$status->description = $newdescription;
$status->grade = $newgrade;
$status->studentavailability = $newstudentavailability;
$status->setnumber = 0;
attendance_add_status($status);
break;
case mod_attendance_preferences_page_params::ACTION_DELETE:
@ -68,7 +76,7 @@ switch ($action) {
break;
}
$message = get_string('deletecheckfull', '', get_string('variable', 'attendance'));
$message = get_string('deletecheckfull', 'attendance', get_string('variable', 'attendance'));
$message .= str_repeat(html_writer::empty_tag('br'), 2);
$message .= $status->acronym.': '.
($status->description ? $status->description : get_string('nodescription', 'attendance'));
@ -92,6 +100,8 @@ switch ($action) {
$acronym = required_param_array('acronym', PARAM_TEXT);
$description = required_param_array('description', PARAM_TEXT);
$grade = required_param_array('grade', PARAM_RAW);
$studentavailability = optional_param_array('studentavailability', '0', PARAM_RAW);
$unmarkedstatus = optional_param('setunmarked', null, PARAM_INT);
foreach ($grade as &$val) {
$val = unformat_float($val);
}
@ -99,7 +109,15 @@ switch ($action) {
foreach ($acronym as $id => $v) {
$status = $statuses[$id];
$errors[$id] = attendance_update_status($status, $acronym[$id], $description[$id], $grade[$id], null);
$setunmarked = false;
if ($unmarkedstatus == $id) {
$setunmarked = true;
}
if (!isset($studentavailability[$id]) || !is_numeric($studentavailability[$id])) {
$studentavailability[$id] = 0;
}
$errors[$id] = attendance_update_status($status, $acronym[$id], $description[$id], $grade[$id],
null, null, null, $studentavailability[$id], $setunmarked);
}
echo $OUTPUT->notification(get_string('eventstatusupdated', 'attendance'), 'success');

4
externallib.php

@ -68,6 +68,10 @@ class mod_wsattendance_external extends external_api {
'description' => new external_value(PARAM_TEXT, 'Session description.'),
'descriptionformat' => new external_value(PARAM_INT, 'Session description format.'),
'studentscanmark' => new external_value(PARAM_INT, 'Students can mark their own presence.'),
'absenteereport' => new external_value(PARAM_INT, 'Session included in absetee reports.'),
'autoassignstatus' => new external_value(PARAM_INT, 'Automatically assign a status to students.'),
'preventsharedip' => new external_value(PARAM_INT, 'Prevent students from sharing IP addresses.'),
'preventsharediptime' => new external_value(PARAM_INT, 'Time delay before IP address is allowed again.'),
'statusset' => new external_value(PARAM_INT, 'Session statusset.'));
return $session;

94
import/sessions.php

@ -0,0 +1,94 @@
<?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/>.
/**
* Import attendance sessions.
*
* @package mod_attendance
* @author Chris Wharton <chriswharton@catalyst.net.nz>
* @copyright 2017 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('NO_OUTPUT_BUFFERING', true);
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->dirroot . '/mod/attendance/lib.php');
require_once($CFG->dirroot . '/mod/attendance/locallib.php');
admin_externalpage_setup('managemodules');
$pagetitle = get_string('importsessions', 'attendance');
$context = context_system::instance();
$url = new moodle_url('/mod/attendance/import/sessions.php');
$PAGE->set_context($context);
$PAGE->set_url($url);
$PAGE->set_title($pagetitle);
$PAGE->set_pagelayout('admin');
$PAGE->set_heading($pagetitle);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('importsessions', 'attendance'));
$tabmenu = attendance_print_settings_tabs('importsessions');
echo $tabmenu;
$form = null;
if (optional_param('needsconfirm', 0, PARAM_BOOL)) {
$form = new \mod_attendance\form\import\sessions($url->out(false));
} else if (optional_param('confirm', 0, PARAM_BOOL)) {
$importer = new \mod_attendance\import\sessions();
$form = new \mod_attendance\form\import\sessions_confirm(null, $importer);
} else {
$form = new \mod_attendance\form\import\sessions($url->out(false));
}
if ($form->is_cancelled()) {
$form = new \mod_attendance\form\import\sessions($url->out(false));
} else if ($data = $form->get_data()) {
require_sesskey();
if ($data->confirm) {
$importid = $data->importid;
$importer = new \mod_attendance\import\sessions(null, null, null, $importid, $data, true);
$error = $importer->get_error();
if ($error) {
$form = new \mod_attendance\form\import\sessions($url->out(false));
$form->set_import_error($error);
} else {
$sessions = $importer->import();
mod_attendance_notifyqueue::show();
echo $OUTPUT->continue_button($url);
die();
}
} else {
$text = $form->get_file_content('importfile');
$encoding = $data->encoding;
$delimiter = $data->delimiter_name;
$importer = new \mod_attendance\import\sessions($text, $encoding, $delimiter, 0, null, true);
$confirmform = new \mod_attendance\form\import\sessions_confirm(null, $importer);
$form = $confirmform;
$pagetitle = get_string('confirmcolumnmappings', 'attendance');
}
}
echo $OUTPUT->heading($pagetitle);
$form->display();
echo $OUTPUT->footer();

371
lang/en/attendance.php

@ -22,11 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['attendance:addinstance'] = 'Add a new attendance activity';
$string['Aacronym'] = 'A';
$string['adduser'] = 'Add user';
$string['Afull'] = 'Absent';
$string['allsessions'] = 'All sessions';
$string['Eacronym'] = 'E';
$string['Efull'] = 'Excused';
$string['Lacronym'] = 'L';
@ -34,31 +31,50 @@ $string['Lfull'] = 'Late';
$string['Pacronym'] = 'P';
$string['Pfull'] = 'Present';
$string['acronym'] = 'Acronym';
$string['absenteereport'] = 'Absentee report';
$string['add'] = 'Add';
$string['addmultiplesessions'] = 'Multiple sessions';
$string['addwarning'] = 'Add warning';
$string['addsession'] = 'Add session';
$string['allcourses'] = 'All courses';
$string['adduser'] = 'Add user';
$string['all'] = 'All';
$string['allcourses'] = 'All courses';
$string['allpast'] = 'All past';
$string['attendancedata'] = 'Attendance data';
$string['attendanceforthecourse'] = 'Attendance for the course';
$string['attendancegrade'] = 'Attendance grade';
$string['attendancenotstarted'] = 'Attendance has not started yet for this course';
$string['attendancepercent'] = 'Attendance percent';
$string['attendancereport'] = 'Attendance report';
$string['attendancesuccess'] = 'Attendance has been successfully taken';
$string['attendanceupdated'] = 'Attendance successfully updated';
$string['allsessions'] = 'All sessions';
$string['attendance:addinstance'] = 'Add a new attendance activity';
$string['attendance:canbelisted'] = 'Appears in the roster';
$string['attendance:changepreferences'] = 'Changing Preferences';
$string['attendance:changeattendances'] = 'Changing Attendances';
$string['attendance:changepreferences'] = 'Changing Preferences';
$string['attendance:export'] = 'Export Reports';
$string['attendance:manageattendances'] = 'Manage Attendances';
$string['attendance:managetemporaryusers'] = 'Manage temporary users';
$string['attendance:takeattendances'] = 'Taking Attendances';
$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['attendancedata'] = 'Attendance data';
$string['attendanceforthecourse'] = 'Attendance for the course';
$string['attendancegrade'] = 'Attendance grade';
$string['attendancenotset'] = 'You must set your attendance';
$string['attendancenotstarted'] = 'Attendance has not started yet for this course';
$string['attendancepercent'] = 'Attendance percent';
$string['attendancereport'] = 'Attendance report';
$string['attendancesuccess'] = 'Attendance has been successfully taken';
$string['attendanceupdated'] = 'Attendance successfully updated';
$string['attforblockdirstillexists'] = 'old mod/attforblock directory still exists - you must delete this directory on your server before running this upgrade.';
$string['attrecords'] = 'Attendances records';
$string['automark'] = 'Automatic marking';
$string['automarkall'] = 'Yes';
$string['automarkclose'] = 'Set unmarked at end of session';
$string['automark_help'] = 'Allows marking to be completed automatically.
If "Yes" students will be automatically marked depending on their first access to the course.
If "Set unmarked at end of session" any students who have not marked their attendance will be set to the unmarked status selected.';
$string['automarktask'] = 'Check for attendance sessions that require auto marking';
$string['autorecorded'] = 'system auto recorded';
$string['averageattendance'] = 'Average attendance';
$string['averageattendancegraded'] = 'Average attendance';
$string['calclose'] = 'Close';
$string['caleventcreated'] = 'Calendar event for session successfully created';
$string['caleventdeleted'] = 'Calendar event for session successfully deleted';
@ -67,6 +83,8 @@ $string['calshow'] = 'Choose date';
$string['caltoday'] = 'Today';
$string['calweekdays'] = 'Su,Mo,Tu,We,Th,Fr,Sa';
$string['cannottakeforgroup'] = 'You can\'t take attendance for group "{$a}"';
$string['cantaddstatus'] = 'You must set an acronym and description when adding a new status.';
$string['categoryreport'] = 'Course category report';
$string['changeattendance'] = 'Change attendance';
$string['changeduration'] = 'Change duration';
$string['changesession'] = 'Change session';
@ -75,9 +93,14 @@ $string['column'] = 'column';
$string['columns'] = 'columns';
$string['commonsession'] = 'All students';
$string['commonsessions'] = 'All students';
$string['confirm'] = 'Confirm';
$string['confirmcolumnmappings'] = 'Confirm column mappings';
$string['confirmdeletehiddensessions'] = 'Are you sure you want to delete {$a->count} sessions scheduled before the course start date ({$a->date})?';
$string['confirmdeleteuser'] = 'Are you sure you want to delete user \'{$a->fullname}\' ({$a->email})?<br/>All of their attendance records will be permanently deleted.';
$string['countofselected'] = 'Count of selected';
$string['copyfrom'] = 'Copy attendance data from';
$string['countofselected'] = 'Count of selected';
$string['course'] = 'Course';
$string['coursesummary'] = 'Course summary report';
$string['createmultiplesessions'] = 'Create multiple sessions';
$string['createmultiplesessions_help'] = 'This function allows you to create multiple sessions in one simple step.
The sessions begin on the date of the base session and continue until the \'repeat until\' date.
@ -86,11 +109,31 @@ The sessions begin on the date of the base session and continue until the \'repe
* <strong>Repeat every</strong>: This allows for a frequency setting. If your class will meet every week, select 1; if it will meet every other week, select 2; every 3rd week, select 3, etc.
* <strong>Repeat until</strong>: Select the last day of class (the last day you want to take attendance).
';
$string['autoassignstatus'] = 'Automatically select highest status available';
$string['autoassignstatus_help'] = 'If this is selected, students will automatically be assigned the highest available grade.';
$string['createonesession'] = 'Create one session for the course';
$string['csvdelimiter'] = 'CSV delimiter';
$string['date'] = 'Date';
$string['days'] = 'Days';
$string['defaults'] = 'Defaults';
$string['defaultdisplaymode'] = 'Default display mode';
$string['defaultwarnings'] = 'Default warning set';
$string['defaultwarningsettings'] = 'Default warning settings';
$string['defaultwarningsettings_help'] = 'These settings define the defaults for all new warnings';
$string['defaults'] = 'Defaults';
$string['defaultsessionsettings'] = 'Default session settings';
$string['defaultsessionsettings_help'] = 'These settings define the defaults for all new sessions';
$string['defaultsettings'] = 'Default attendance settings';
$string['defaultsettings_help'] = 'These settings define the defaults for all new attendances';
$string['defaultstatus'] = 'Default status set';
$string['defaultsubnet'] = 'Default network address';
$string['defaultsubnet_help'] = 'Attendance recording may be restricted to particular subnets by specifying a comma-separated list of partial or full IP addresses. This is the default value used when creating new sessions.';
$string['defaultview'] = 'Default view on login';
$string['defaultview_desc'] = 'This is the default view shown to teachers on first login.';
$string['delete'] = 'Delete';
$string['deletewarningconfirm'] = 'Are you sure you want to delete this warning?';
$string['deletedgroup'] = 'The group associated with this session has been deleted';
$string['deletecheckfull'] = 'Are you absolutely sure you want to completely delete the {$a}, including all user data?';
$string['deletehiddensessions'] = 'Delete all hidden sessions';
$string['deletelogs'] = 'Delete attendance data';
$string['deleteselected'] = 'Delete selected';
$string['deletesession'] = 'Delete session';
@ -101,51 +144,119 @@ $string['deletingstatus'] = 'Deleting status for the course';
$string['description'] = 'Description';
$string['display'] = 'Display';
$string['displaymode'] = 'Display mode';
$string['donotusepaging'] = 'Do not use paging';
$string['downloadexcel'] = 'Download in Excel format';
$string['downloadooo'] = 'Download in OpenOffice format';
$string['downloadtext'] = 'Download in text format';
$string['donotusepaging'] = 'Do not use paging';
$string['duration'] = 'Duration';
$string['editsession'] = 'Edit Session';
$string['edituser'] = 'Edit user';
$string['emailcontent_default'] = 'Hi %userfirstname%,
Your attendance in %coursename% %attendancename% has dropped below %warningpercent% and is currently %percent% - we hope you are ok!
To get the most out of this course you should improve your attendance, please get in touch if you require any further support.';
$string['emailcontent'] = 'Email content';
$string['emailcontent_help'] = 'When a warning is sent to a student, it takes the email content from this field. The following wildcards can be used:
<ul>
<li>%coursename%</li>
<li>%userfirstname%</li>
<li>%userlastname%</li>
<li>%userid%</li>
<li>%warningpercent%</li>
<li>%attendancename%</li>
<li>%cmid%</li>
<li>%numtakensessions%</li>
<li>%points%</li>
<li>%maxpoints%</li>
<li>%percent%</li>
</ul>';
$string['emailsubject'] = 'Email subject';
$string['emailsubject_help'] = 'When a warning is sent to a student, it takes the email subject from this field.';
$string['emailsubject_default'] = 'Attendance warning';
$string['emailuser'] = 'Email user';
$string['emailuser_help'] = 'If checked, a warning will be sent to the student.';
$string['emptyacronym'] = 'Empty acronyms are not allowed. Status record not updated.';
$string['emptydescription'] = 'Empty descriptions are not allowed. Status record not updated.';
$string['edituser'] = 'Edit user';
$string['endtime'] = 'Session end time';
$string['enablecalendar'] = 'Create calendar events';
$string['enablecalendar_desc'] = 'If enabled, a calendar event will be created for each attendance session. After changing this setting you should run the reset calendar report.';
$string['enablewarnings'] = 'Enable warnings';
$string['enablewarnings_desc'] = 'This allows a warning set to be defined for an attendance and email notifications to users when attendance drops below the configured threshold. <br/><strong>WARNING: This is a new feature and has not been tested extensively. Please use at your own-risk and provide feeback in the moodle forums if you find it works well.</strong>';
$string['encoding'] = 'Encoding';
$string['endofperiod'] = 'End of period';
$string['endtime'] = 'Session end time';
$string['enrolmentend'] = 'User enrolment ends {$a}';
$string['enrolmentstart'] = 'User enrolment starts {$a}';
$string['enrolmentsuspended'] = 'Enrolment suspended';
$string['error:coursenotfound'] = 'A course with the short name {$a} can not be found.';
$string['error:coursehasnoattendance'] = 'The course with the short name {$a} has no attendance activities.';
$string['error:sessioncourseinvalid'] = 'A session course is invalid! Skipping.';
$string['error:sessiondateinvalid'] = 'A session date is invalid! Skipping.';
$string['error:sessionendinvalid'] = 'A session end time is invalid! Skipping.';
$string['error:sessionstartinvalid'] = 'A session start time is invalid! Skipping.';
$string['errorgroupsnotselected'] = 'Select one or more groups';
$string['errorinaddingsession'] = 'Error in adding session';
$string['erroringeneratingsessions'] = 'Error in generating sessions ';
$string['eventdurationupdated'] = 'Session duration updated';
$string['eventreportviewed'] = 'Attendance report viewed';
$string['eventscreated'] = 'Calendar events created';
$string['eventsdeleted'] = 'Calendar events deleted';
$string['eventsessionadded'] = 'Session added';
$string['eventsessiondeleted'] = 'Session deleted';
$string['eventsessionsimported'] = 'Sessions imported';
$string['eventsessionipshared'] = 'Attendance self-marking IP conflict';
$string['eventsessionupdated'] = 'Session updated';
$string['eventstatusadded'] = 'Status added';
$string['eventstatusupdated'] = 'Status updated';
$string['eventtaken'] = 'Attendance taken';
$string['eventtakenbystudent'] = 'Attendance taken by student';
$string['export'] = 'Export';
$string['extrarestrictions'] = 'Extra restrictions';
$string['from'] = 'from:';
$string['gradebookexplanation'] = 'Grade in gradebook';
$string['gradebookexplanation_help'] = 'The Attendance module displays your current attendance grade based on the number of points you have earned to date and the number of points that could have been earned to date; it does not include class periods in the future. In the gradebook, your attendance grade is based on your current attendance percentage and the number of points that can be earned over the entire duration of the course, including future class periods. As such, your attendance grades displayed in the Attendance module and in the gradebook may not be the same number of points but they are the same percentage.
For example, if you have earned 8 of 10 points to date (80% attendance) and attendance for the entire course is worth 50 points, the Attendance module will display 8/10 and the gradebook will display 40/50. You have not yet earned 40 points but 40 is the equivalent point value to your current attendance percentage of 80%. The point value you have earned in the Attendance module can never decrease, as it is based only on attendance to date; however, the attendance point value shown in the gradebook may increase or decrease depending on your future attendance, as it is based on attendance for the entire course.';
$string['gridcolumns'] = 'Grid columns';
$string['group'] = 'Group';
$string['groups'] = 'Groups';
$string['groupsession'] = 'Group of students';
$string['hiddensessions'] = 'Hidden sessions';
$string['hiddensessions_help'] = 'Sessions are hidden if they are scheduled before the course start date.
You can use this feature to hide older sessions instead of deleting them. Only visible sessions will appear in the Gradebook.';
$string['hiddensessionsdeleted'] = 'All hidden sessions were delete';
$string['hideextrauserdetails'] = 'Hide extra user details';
$string['hidensessiondetails'] = 'Hide session details';
$string['import'] = 'Import';
$string['importfile'] = 'Import file';
$string['importfile_help'] = 'Import file';
$string['importsessions'] = 'Import Sessions';
$string['identifyby'] = 'Identify student by';
$string['includeall'] = 'Select all sessions';
$string['includenottaken'] = 'Include not taken sessions';
$string['includeremarks'] = 'Include remarks';
$string['incorrectpassword'] = 'You have entered an incorrect password';
$string['incorrectpassword'] = 'You have entered an incorrect password and your attendance has not been recorded, please enter the correct password.';
$string['indetail'] = 'In detail...';
$string['invalidsessionenddate'] = 'This date can not be earlier than the session date';
$string['invalidaction'] = 'You must select an action';
$string['invalidemails'] = 'You must specify addresses of existing user accounts, could not find: {$a}';
$string['invalidimportfile'] = 'File format is invalid.';
$string['invalidsessionenddate'] = 'This date can not be earlier than the session date';
$string['invalidsessionendtime'] = 'The end time must be greater than start time';
$string['invalidstatus'] = 'You have selected an invalid status, please try again';
$string['iptimemissing'] = 'Invalid minutes to release';
$string['jumpto'] = 'Jump to';
$string['lowgrade'] = 'Low grade';
$string['maxpossible'] = 'Maximum possible';
$string['maxpossible_help'] = 'Shows the score each user can reach if they receive the maximum points in each session not yet taken (past and future):
<ul>
<li><strong>Points</strong>: maximum points each user can reach over all sessions.</li>
<li><strong>Percentage</strong>: maximum percentage each user can reach over all sessions.</li>
</ul>';
$string['maxpossiblepoints'] = 'Maximum possible points';
$string['maxpossiblepercentage'] = 'Maximum possible percentage';
$string['maxpossiblepoints'] = 'Maximum possible points';
$string['maxwarn'] = 'Maximum number of e-mail warnings';
$string['maxwarn_help'] = 'The maximum number of times a warning should be sent (only one warning per session is sent)';
$string['mergeuser'] = 'Merge user';
$string['modulename'] = 'Attendance';
$string['modulename_help'] = 'The attendance activity module enables a teacher to take attendance during class and students to view their own attendance record.
@ -158,14 +269,20 @@ $string['months'] = 'Months';
$string['moreattendance'] = 'Attendance has been successfully taken for this page';
$string['moveleft'] = 'Move left';
$string['moveright'] = 'Move right';
$string['multisessionexpanded'] = 'Multiple sessions expanded';
$string['multisessionexpanded_desc'] = 'Show the "Multiple sessions" settings as expanded by default when creating new sessions.';
$string['mustselectusers'] = 'Must select users to export';
$string['newdate'] = 'New date';
$string['newduration'] = 'New duration';
$string['newstatusset'] = 'New set of statuses';
$string['noabsentstatusset'] = 'The status set in use does not have a status to use when not marked.';
$string['noattendanceusers'] = 'It is not possible to export any data as there are no students enrolled in the course.';
$string['noautomark'] = 'Disabled';
$string['noattforuser'] = 'No attendance records exist for the user';
$string['nodescription'] = 'Regular class session';
$string['noguest'] = 'Guest can\'t see attendance';
$string['noeventstoreset'] = 'There are no calendar events that require an update.';
$string['nogroups'] = 'You can\'t add group sessions. No groups exists in course.';
$string['noguest'] = 'Guest can\'t see attendance';
$string['noofdaysabsent'] = 'No of days absent';
$string['noofdaysexcused'] = 'No of days excused';
$string['noofdayslate'] = 'No of days late';
@ -173,22 +290,61 @@ $string['noofdayspresent'] = 'No of days present';
$string['nosessiondayselected'] = 'No Session day selected';
$string['nosessionexists'] = 'No Session exists for this course';
$string['nosessionsselected'] = 'No sessions selected';
$string['warningdeleted'] = 'Warning deleted';
$string['warningdesc'] = 'These warnings will be automatically added to any new attendance activities. If more than one warning is triggered at exactly the same time, only the warning with the lower warning threshold will be sent.';
$string['warningdesc_course'] = 'Warnings thresholds set here affect the absentee report and allow students and third parties to be notified. If more than one warning is triggered at exactly the same time, only the warning with the lower warning threshold will be sent.';
$string['warnings'] = 'Warnings set';
$string['warningupdated'] = 'Updated warnings';
$string['notifytask'] = 'Send warnings to users';
$string['notfound'] = 'Attendance activity not found in this course!';
$string['notmember'] = 'not&nbsp;member';
$string['noupgradefromthisversion'] = 'The Attendance module cannot upgrade from the version of attforblock you have installed. - please delete attforblock or upgrade it to the latest version before isntalling the new attendance module';
$string['numsessions'] = 'Number of sessions';
$string['olddate'] = 'Old date';
$string['onlyselectedusers'] = 'Export specific users';
$string['overallsessions'] = 'Over all sessions';
$string['overallsessions_help'] = 'Shows statistics for all sessions including those not yet taken (past and future):
<ul>
<li><strong>Sessions</strong>: total number of sessions.</li>
<li><strong>Points</strong>: points awarded based on the taken sessions.</li>
<li><strong>Percentage</strong>: percentage of points awarded over the maxium possible points for all sessions.</li>
</ul>';
$string['oversessionstaken'] = 'Over taken sessions';
$string['oversessionstaken_help'] = 'Shows statistics for sessions where attendance has been taken:
<ul>
<li><strong>Sessions</strong>: number of already taken sessions.</li>
<li><strong>Points</strong>: points awarded based on the taken sessions.</li>
<li><strong>Percentage</strong>: percentage of points awarded over the maxium possible points of the taken sessions.</li>
</ul>';
$string['participant'] = 'Participant';
$string['password'] = 'Password';
$string['passwordgrp'] = 'Student password';
$string['passwordgrp_help'] = 'If set students will be required to enter this password before they can set their own attendance status for the session. If empty, no password is required.';
$string['passwordrequired'] = 'You must enter the session password before you can submit your attendance';
$string['percentage'] = 'Percentage';
$string['percentagesessionscompleted'] = 'Percentage over taken sessions';
$string['percentageallsessions'] = 'Percentage over all sessions';
$string['pluginname'] = 'Attendance';
$string['percentagesessionscompleted'] = 'Percentage over taken sessions';
$string['pluginadministration'] = 'Attendance administration';
$string['passwordgrp'] = 'Student password';
$string['passwordgrp_help'] = 'If set students will be required to enter this password before they can set their own attendance status for the session. If empty, no password is required.';
$string['pluginname'] = 'Attendance';
$string['points'] = 'Points';
$string['pointsallsessions'] = 'Points over all sessions';
$string['pointssessionscompleted'] = 'Points over taken sessions';
$string['preferences_desc'] = 'Changes to status sets will affect existing attendance sessions and may affect grading.';
$string['preventsharedip'] = 'Prevent students sharing IP address';
$string['preventsharedip_help'] = 'Prevent students from using the same device (identified using IP address) to take attendance for other students.';
$string['preventsharediptime'] = 'Time to allow re-use of IP address (minutes)';
$string['preventsharediptime_help'] = 'Allow an IP address to be re-used for taking attendance in this session after this time has elapsed.';
$string['preventsharedipminutes'] = '(minutes to release IP)';
$string['preventsharederror'] = 'Self-marking has been disabled for a session because this device appears to have been used to record attendance for another student.';
$string['priorto'] = 'The session date is prior to the course start date ({$a}) so that the new sessions scheduled before this date will be hidden (not accessible). You can change the course start date at any time (see course settings) in order to have access to earlier sessions.<br><br>Please change the session date or just click the "Add session" button again to confirm?';
$string['processingfile'] = 'Processing file';
$string['randompassword'] = 'Random password';
$string['remark'] = 'Remark for: {$a}';
$string['remarks'] = 'Remarks';
$string['repeatasfollows'] = 'Repeat the session above as follows';
$string['repeatevery'] = 'Repeat every';
$string['repeaton'] = 'Repeat on';
$string['repeatuntil'] = 'Repeat until';
$string['report'] = 'Report';
$string['required'] = 'Required*';
$string['requiredentries'] = ' Temporary records overwrite participant attendance records';
@ -226,6 +382,11 @@ $string['requiredentry_help'] = '<p align="center"><b>Attendance</b></p>
</p>
<p align="left"><strong>Temporay user will be deleted in all cases after merge action</strong></p>';
$string['requiresubnet'] = 'Require network address';
$string['requiresubnet_help'] = 'Attendance recording may be restricted to particular subnets by specifying a comma-separated list of partial or full IP addresses.';
$string['resetcalendar'] = 'Reset calendar';
$string['resetcaledarcreate'] = 'Calendar events have been enabled but a number of existing sessions do not have events. Do you want to create calendar events for all existing sessions?';
$string['resetcaledardelete'] = 'Calendar events have been disabled but a number of existing sessions have events that should be deleted. Do you want to delete all existing events?';
$string['resetdescription'] = 'Remember that deleting attendance data will erase information from database. You can just hide older sessions having changed start date of course!';
$string['resetstatuses'] = 'Reset statuses to default';
$string['restoredefaults'] = 'Restore defaults';
@ -240,143 +401,123 @@ $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['sessionexist'] = 'Session not added (already exists)!';
$string['sessiongenerated'] = 'One session was successfully generated';
$string['sessionunknowngroup'] = 'A session specifies unknown group(s): {$a}';
$string['sessions'] = 'Sessions';
$string['sessionscompleted'] = 'Taken sessions';
$string['sessionstotal'] = 'Total number of sessions';
$string['sessionsids'] = 'IDs of sessions: ';
$string['sessiongenerated'] = 'One session was successfully generated';
$string['sessionsgenerated'] = '{$a} sessions were successfully generated';
$string['sessionsids'] = 'IDs of sessions: ';
$string['sessionsnotfound'] = 'There is no sessions in the selected timespan';
$string['sessionstartdate'] = 'Session start date';
$string['sessiontype'] = 'Type';
$string['sessionstotal'] = 'Total number of sessions';
$string['sessiontype_help'] = 'You can add sessions for all students or for a group of students. Ability to add different types depends on activity group mode.
* In group mode "No groups" you can add only sessions for all students.
* In group mode "Separate groups" you can add only sessions for a group of students.
* In group mode "Visible groups" you can add both types of sessions.
';
$string['sessiontype'] = 'Type';
$string['sessiontypeshort'] = 'Type';
$string['sessionupdated'] = 'Session successfully updated';
$string['set_by_student'] = 'Self-recorded';
$string['setallstatuses'] = 'Set status for all users';
$string['setallstatusesto'] = 'Set status for all users to «{$a}»';
$string['randompassword'] = 'Random password';
$string['settings'] = 'Settings';
$string['setunmarked'] = 'Automatically set when not marked';
$string['setunmarked_help'] = 'If enabled in the session, set this status if a student has not marked their own attendance.';
$string['showdefaults'] = 'Show defaults';
$string['showduration'] = 'Show duration';
$string['showextrauserdetails'] = 'Show extra user details';
$string['showsessiondetails'] = 'Show session details';
$string['showsessiondescriptiononreport'] = 'Show session description in report';
$string['showsessiondescriptiononreport_desc'] = 'Show the session description in the attendance report listing.';
$string['somedisabledstatus'] = '(Some options have been removed as the session has started.)';
$string['sortedgrid'] = 'Sorted grid';
$string['sortedlist'] = 'Sorted list';
$string['startofperiod'] = 'Start of period';
$string['starttime'] = 'Start time';
$string['status'] = 'Status';
$string['statuses'] = 'Statuses';
$string['statusdeleted'] = 'Status deleted';
$string['statuses'] = 'Statuses';
$string['statusset'] = 'Status set {$a}';
$string['statussetsettings'] = 'Status set';
$string['strftimedm'] = '%h %d';
$string['strftimedmy'] = '%d %h %Y';
$string['strftimedmyhm'] = '%d %h %Y %I.%M%P'; // Line added to allow multiple sessions in the same day.
$string['strftimedmyw'] = '<nobr>%a %d %h %Y</nobr>';
$string['strftimeh'] = '%l%P';
$string['strftimehm'] = '%l:%M%P';
$string['strftimedm'] = '%b %d';
$string['strftimedmy'] = '%d %b %Y';
$string['strftimedmyhm'] = '%d %b %Y %I.%M%p'; // Line added to allow multiple sessions in the same day.
$string['strftimedmyw'] = '<nobr>%a %d %b %Y</nobr>';
$string['strftimeh'] = '%I%p';
$string['strftimehm'] = '%I:%M%p';
$string['strftimeshortdate'] = '%d.%m.%Y';
$string['studentavailability'] = 'Available for students (minutes)';
$string['studentavailability_help'] = 'When students are marking their own attendance, the number of minutes after session starts that this status is available.
<br/>If empty, this status will always be available, If set to 0 it will always be hidden to students.';
$string['studentid'] = 'Student ID';
$string['studentmarking'] = 'Student recording';
$string['studentpassword'] = 'Student password';
$string['studentrecordingexpanded'] = 'Student recording expanded';
$string['studentrecordingexpanded_desc'] = 'Show the "Student recording" settings as expanded by default when creating new sessions.';
$string['studentscanmark'] = 'Allow students to record own attendance';
$string['studentscanmark_desc'] = 'If checked, teachers will be able to allow students to mark their own attendance.';
$string['studentscanmark_help'] = 'If checked students will be able to change their own attendance status for the session.';
$string['studentscanmarksessiontime'] = 'Students record attendance during session time';
$string['studentscanmarksessiontime_desc'] = 'If checked students can only record their attendance during the session.';
$string['studentscanmarksessiontimeend'] = 'Session end (minutes)';
$string['studentscanmarksessiontimeend_desc'] = 'If the session does not have an end time, how many minutes should the session be available for students to record their attendance.';
$string['submitattendance'] = 'Submit attendance';
$string['subnet'] = 'Subnet';
$string['subnetactivitylevel'] = 'Allow subnet config at activity level';
$string['subnetactivitylevel_desc'] = 'If enabled, teachers can override the default subnet at the activity level when creating an attendance. Otherwise the site default will be used when creating a session.';
$string['subnetwrong'] = 'Attendance can only be recorded from certain locations, and this computer is not on the allowed list.';
$string['summary'] = 'Summary';
$string['tablerenamefailed'] = 'Rename of old attforblock table to attendance failed';
$string['tactions'] = 'Action';
$string['takeattendance'] = 'Take attendance';
$string['takensessions'] = 'Taken sessions';
$string['tcreated'] = 'Created';
$string['tempaddform'] = 'Add temporary user';
$string['tempexists'] = 'There is already a temporary user with this email address';
$string['tempusers'] = 'Temporary users';
$string['thiscourse'] = 'This course';
$string['tablerenamefailed'] = 'Rename of old attforblock table to attendance failed';
$string['tactions'] = 'Action';
$string['tcreated'] = 'Created';
$string['temptable'] = 'List of temporary users';
$string['tempuser'] = 'Temporary user';
$string['tempusermerge'] = 'Merge temporary user';
$string['tempusers'] = 'Temporary users';
$string['tempusersedit'] = 'Edit temporary user';
$string['tempuserslist'] = 'Temporary users';
$string['tempusermerge'] = 'Merge temporary user';
$string['thirdpartyemailsubject'] = 'Attendance warning';
$string['thirdpartyemailtext'] = '{$a->firstname} {$a->lastname} attendance within {$a->coursename} {$a->aname} is lower than {$a->warningpercent} ({$a->percent})';
$string['thirdpartyemailtextfooter'] = 'You are receiving this because the teacher of this course has added your email to the recipient’s list';
$string['thirdpartyemails'] = 'Notify other users';
$string['thirdpartyemails_help'] = 'List of other users who will be notified. (requires the capability mod/attendance:viewreports)';
$string['thiscourse'] = 'This course';
$string['time'] = 'Time';
$string['timeahead'] = 'Multiple sessions that exceed one year cannot be created, please adjust the start and end dates.';
$string['to'] = 'to:';
$string['triggered'] = 'First notified';
$string['tuseremail'] = 'Email';
$string['tusername'] = 'Full name';
$string['graded'] = 'Graded sessions';
$string['ungraded'] = 'Ungraded sessions';
$string['unknowngroup'] = 'Unknown group';
$string['update'] = 'Update';
$string['usestatusset'] = 'Status set';
$string['usedefaultsubnet'] = 'Use default';
$string['userexists'] = 'There is already a real user with this email address';
$string['users'] = 'Users to export';
$string['usestatusset'] = 'Status set';
$string['variable'] = 'variable';
$string['variablesupdated'] = 'Variables successfully updated';
$string['versionforprinting'] = 'version for printing';
$string['viewmode'] = 'View mode';
$string['warnafter'] = 'Number of sessions taken before warning';
$string['warnafter_help'] = 'Warnings will only be triggered when the user has had their attendance taken for at least this number of sessions.';
$string['warningfailed'] = 'You cannot create a warning that uses the same percentage and number of sessions.';
$string['warningpercent'] = 'Warn if percentage falls under';
$string['warningpercent_help'] = 'A warning will be triggered when the overall percentage falls below this number.';
$string['warningthreshold'] = 'Warning threshold';
$string['week'] = 'week(s)';
$string['weeks'] = 'Weeks';
$string['youcantdo'] = 'You can\'t do anything';
$string['eventreportviewed'] = 'Attendance report viewed';
$string['eventsessionadded'] = 'Session added';
$string['eventsessionupdated'] = 'Session updated';
$string['eventtaken'] = 'Attendance taken';
$string['eventtakenbystudent'] = 'Attendance taken by student';
$string['eventsessiondeleted'] = 'Session deleted';
$string['eventdurationupdated'] = 'Session duration updated';
$string['eventstatusupdated'] = 'Status updated';
$string['eventstatusadded'] = 'Status added';
$string['studentscanmark'] = 'Allow students to record own attendance';
$string['studentscanmark_desc'] = 'If checked, teachers will be able to allow students to mark their own attendance.';
$string['studentscanmark_help'] = 'If checked students will be able to change their own attendance status for the session.';
$string['studentscanmarksessiontime'] = 'Students record attendance during session time';
$string['studentscanmarksessiontime_desc'] = 'If checked students can only record their attendance during the session.';
$string['studentscanmarksessiontimeend'] = 'Session end (minutes)';
$string['studentscanmarksessiontimeend_desc'] = 'If the session does not have an end time, how many minutes should the session be available for students to record their attendance.';
$string['set_by_student'] = 'Self-recorded';
$string['attendance_already_submitted'] = 'You may not self register attendance that has already been set.';
$string['lowgrade'] = 'Low grade';
$string['submitattendance'] = 'Submit attendance';
$string['attendancenotset'] = 'You must set your attendance';
$string['export'] = 'Export';
$string['points'] = 'Points';
$string['oversessionstaken'] = 'Over taken sessions';
$string['oversessionstaken_help'] = 'Shows statistics for sessions where attendance has been taken:
<ul>
<li><strong>Sessions</strong>: number of already taken sessions.</li>
<li><strong>Points</strong>: points awarded based on the taken sessions.</li>
<li><strong>Percentage</strong>: percentage of points awarded over the maxium possible points of the taken sessions.</li>
</ul>';
$string['overallsessions'] = 'Over all sessions';
$string['overallsessions_help'] = 'Shows statistics for all sessions including those not yet taken (past and future):
<ul>
<li><strong>Sessions</strong>: total number of sessions.</li>
<li><strong>Points</strong>: points awarded based on the taken sessions.</li>
<li><strong>Percentage</strong>: percentage of points awarded over the maxium possible points for all sessions.</li>
</ul>';
$string['pointssessionscompleted'] = 'Points over taken sessions';
$string['pointsallsessions'] = 'Points over all sessions';
$string['unknowngroup'] = 'Unknown group';
$string['notmember'] = 'not&nbsp;member';
$string['deletehiddensessions'] = 'Delete all hidden sessions';
$string['confirmdeletehiddensessions'] = 'Are you sure you want to delete {$a->count} sessions scheduled before the course start date ({$a->date})?';
$string['hiddensessionsdeleted'] = 'All hidden sessions were delete';
$string['timeahead'] = 'Multiple sessions that exceed one year cannot be created, please adjust the start and end dates.';
$string['priorto'] = 'The session date is prior to the course start date ({$a}) so that the new sessions scheduled before this date will be hidden (not accessible). You can change the course start date at any time (see course settings) in order to have access to earlier sessions.<br><br>Please change the session date or just click the "Add session" button again to confirm?';
$string['noattendanceusers'] = 'It is not possible to export any data as there are no students enrolled in the course.';
$string['time'] = 'Time';
$string['from'] = 'from:';
$string['to'] = 'to:';
$string['repeatasfollows'] = 'Repeat the session above as follows';
$string['repeatevery'] = 'Repeat every';
$string['repeatuntil'] = 'Repeat until';
$string['repeaton'] = 'Repeat on';
$string['invalidsessionendtime'] = 'The end time must be greater than start time';
$string['deletedgroup'] = 'The group associated with this session has been deleted';
$string['extrarestrictions'] = 'Extra restrictions';
$string['requiresubnet'] = 'Students can only record own attendance from these computers.';
$string['subnetwrong'] = 'Attendance can only be recorded from certain locations, and this computer is not on the allowed list.';
$string['requiresubnet_help'] = 'Attendance recording may be restricted to particular subnets by specifying a comma-separated list of partial or full IP addresses.';
$string['defaultsettings'] = 'Default attendance settings';
$string['defaultsettings_help'] = 'These settings define the defaults for all new attendances';
$string['defaultstatus'] = 'Default status set';
$string['defaultsessionsettings'] = 'Default session settings';
$string['defaultsessionsettings_help'] = 'These settings define the defaults for all new sessions';
$string['includeabsentee'] = 'Include session when calculating absentee report';
$string['includeabsentee_help'] = 'If checked this session will be included in the absentee report calculations.';
$string['attendance_no_status'] = 'No valid status was available - you may be too late to record attendance.';
$string['studentmarked'] = 'Your attendance in this session has been recorded.';

51
lib.php

@ -40,8 +40,6 @@ function attendance_supports($feature) {
return true;
case FEATURE_GROUPINGS:
return true;
case FEATURE_GROUPMEMBERSONLY:
return true;
case FEATURE_MOD_INTRO:
return true;
case FEATURE_BACKUP_MOODLE2:
@ -71,6 +69,25 @@ function att_add_default_statuses($attid) {
$statuses->close();
}
/**
* Add default set of warnings to the new attendance.
*
* @param int $id - id of attendance instance.
*/
function attendance_add_default_warnings($id) {
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
$warnings = $DB->get_recordset('attendance_warning',
array('idnumber' => 0), 'id');
foreach ($warnings as $n) {
$rec = $n;
$rec->idnumber = $id;
$DB->insert_record('attendance_warning', $rec);
}
$warnings->close();
}
/**
* Add new attendance instance.
*
@ -86,6 +103,8 @@ function attendance_add_instance($attendance) {
att_add_default_statuses($attendance->id);
attendance_add_default_warnings($attendance->id);
attendance_grade_item_update($attendance);
return $attendance->id;
@ -119,7 +138,8 @@ function attendance_update_instance($attendance) {
* @return bool
*/
function attendance_delete_instance($id) {
global $DB;
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
if (! $attendance = $DB->get_record('attendance', array('id' => $id))) {
return false;
@ -134,6 +154,8 @@ function attendance_delete_instance($id) {
}
$DB->delete_records('attendance_statuses', array('attendanceid' => $id));
$DB->delete_records('attendance_warning', array('idnumber' => $id));
$DB->delete_records('attendance', array('id' => $id));
attendance_grade_item_delete($attendance);
@ -190,6 +212,10 @@ function attendance_reset_userdata($data) {
$DB->delete_records_select('attendance_log', "sessionid $sql", $params);
list($sql, $params) = $DB->get_in_or_equal($attids);
$DB->set_field_select('attendance_sessions', 'lasttaken', 0, "attendanceid $sql", $params);
if (empty($data->reset_attendance_sessions)) {
// If sessions are being retained, clear automarkcompleted value.
$DB->set_field_select('attendance_sessions', 'automarkcompleted', 0, "attendanceid $sql", $params);
}
$status[] = array(
'component' => get_string('modulenameplural', 'attendance'),
@ -448,6 +474,25 @@ function attendance_print_settings_tabs($selected = 'settings') {
$tabs[] = new tabobject('defaultstatus', $CFG->wwwroot.'/mod/attendance/defaultstatus.php',
get_string('defaultstatus', 'attendance'), get_string('defaultstatus', 'attendance'), false);
if (get_config('attendance', 'enablewarnings')) {
$tabs[] = new tabobject('defaultwarnings', $CFG->wwwroot . '/mod/attendance/warnings.php',
get_string('defaultwarnings', 'attendance'), get_string('defaultwarnings', 'attendance'), false);
}
$tabs[] = new tabobject('coursesummary', $CFG->wwwroot.'/mod/attendance/coursesummary.php',
get_string('coursesummary', 'attendance'), get_string('coursesummary', 'attendance'), false);
if (get_config('attendance', 'enablewarnings')) {
$tabs[] = new tabobject('absentee', $CFG->wwwroot . '/mod/attendance/absentee.php',
get_string('absenteereport', 'attendance'), get_string('absenteereport', 'attendance'), false);
}
$tabs[] = new tabobject('resetcalendar', $CFG->wwwroot.'/mod/attendance/resetcalendar.php',
get_string('resetcalendar', 'attendance'), get_string('resetcalendar', 'attendance'), false);
$tabs[] = new tabobject('importsessions', $CFG->wwwroot . '/mod/attendance/import/sessions.php',
get_string('importsessions', 'attendance'), get_string('importsessions', 'attendance'), false);
ob_start();
print_tabs(array($tabs), $selected);
$tabmenu = ob_get_contents();

373
locallib.php

@ -39,6 +39,13 @@ define('ATT_SORT_DEFAULT', 0);
define('ATT_SORT_LASTNAME', 1);
define('ATT_SORT_FIRSTNAME', 2);
define('ATTENDANCE_AUTOMARK_DISABLED', 0);
define('ATTENDANCE_AUTOMARK_ALL', 1);
define('ATTENDANCE_AUTOMARK_CLOSE', 2);
// Max number of sessions available in the warnings set form to trigger warnings.
define('ATTENDANCE_MAXWARNAFTER', 100);
/**
* Get statuses,
*
@ -88,7 +95,7 @@ function attendance_get_setname($attid, $statusset, $includevalues = true) {
if ($statusesout) {
if (count($statusesout) > 6) {
$statusesout = array_slice($statusesout, 0, 6);
$statusesout[] = '&helip;';
$statusesout[] = '...';
}
$statusesout = implode(' ', $statusesout);
$statusname .= ' ('.$statusesout.')';
@ -258,40 +265,33 @@ function attendance_update_users_grade($attendance, $userids=array()) {
/**
* Add an attendance status variable
*
* @param string $acronym
* @param string $description
* @param int $grade
* @param int $attendanceid
* @param int $setnumber
* @param stdClass $context
* @param stdClass $cm
* @param stdClass $status
* @return bool
*/
function attendance_add_status($acronym, $description, $grade, $attendanceid, $setnumber = 0, $context = null, $cm = null) {
function attendance_add_status($status) {
global $DB;
if (empty($context)) {
$context = context_system::instance();
if (empty($status->context)) {
$status->context = context_system::instance();
}
if ($acronym && $description) {
$rec = new stdClass();
$rec->attendanceid = $attendanceid;
$rec->acronym = $acronym;
$rec->description = $description;
$rec->grade = $grade;
$rec->setnumber = $setnumber; // Save which set it is part of.
$rec->deleted = 0;
$rec->visible = 1;
$id = $DB->insert_record('attendance_statuses', $rec);
$rec->id = $id;
if (!empty($status->acronym) && !empty($status->description)) {
$status->deleted = 0;
$status->visible = 1;
$status->setunmarked = 0;
$id = $DB->insert_record('attendance_statuses', $status);
$status->id = $id;
$event = \mod_attendance\event\status_added::create(array(
'objectid' => $attendanceid,
'context' => $context,
'other' => array('acronym' => $acronym, 'description' => $description, 'grade' => $grade)));
if (!empty($cm)) {
$event->add_record_snapshot('course_modules', $cm);
'objectid' => $status->attendanceid,
'context' => $status->context,
'other' => array('acronym' => $status->acronym,
'description' => $status->description,
'grade' => $status->grade)));
if (!empty($status->cm)) {
$event->add_record_snapshot('course_modules', $status->cm);
}
$event->add_record_snapshot('attendance_statuses', $rec);
$event->add_record_snapshot('attendance_statuses', $status);
$event->trigger();
return true;
} else {
@ -336,9 +336,12 @@ function attendance_remove_status($status, $context = null, $cm = null) {
* @param bool $visible
* @param stdClass $context
* @param stdClass $cm
* @param int $studentavailability
* @param bool $setunmarked
* @return array
*/
function attendance_update_status($status, $acronym, $description, $grade, $visible, $context = null, $cm = null) {
function attendance_update_status($status, $acronym, $description, $grade, $visible,
$context = null, $cm = null, $studentavailability = null, $setunmarked = false) {
global $DB;
if (empty($context)) {
@ -366,6 +369,21 @@ function attendance_update_status($status, $acronym, $description, $grade, $visi
$status->grade = $grade;
$updated[] = $grade;
}
if (isset($studentavailability)) {
if (empty($studentavailability)) {
if ($studentavailability !== '0') {
$studentavailability = null;
}
}
$status->studentavailability = $studentavailability;
$updated[] = $studentavailability;
}
if ($setunmarked) {
$status->setunmarked = 1;
} else {
$status->setunmarked = 0;
}
$DB->update_record('attendance_statuses', $status);
$event = \mod_attendance\event\status_updated::create(array(
@ -407,6 +425,7 @@ function attendance_random_string($length=6) {
* @return boolean
*/
function attendance_can_student_mark($sess) {
global $DB, $USER, $OUTPUT;
$canmark = false;
$attconfig = get_config('attendance');
if (!empty($attconfig->studentscanmark) && !empty($sess->studentscanmark)) {
@ -422,6 +441,31 @@ function attendance_can_student_mark($sess) {
}
}
}
// Check if another student has marked attendance from this IP address recently.
if ($canmark && !empty($sess->preventsharedip)) {
$time = time() - ($sess->preventsharediptime * 60);
$sql = 'sessionid = ? AND studentid <> ? AND timetaken > ? AND ipaddress = ?';
$params = array($sess->id, $USER->id, $time, getremoteaddr());
$record = $DB->get_record_select('attendance_log', $sql, $params);
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;
}
}
return $canmark;
}
@ -509,15 +553,22 @@ function attendance_exporttocsv($data, $filename) {
/**
* Get session data for form.
* @param stdClass $formdata moodleform - attendance form.
* @param mod_attendance_structure $att - used to get attendance level subnet.
* @return array.
*/
function attendance_construct_sessions_data_for_add($formdata) {
function attendance_construct_sessions_data_for_add($formdata, mod_attendance_structure $att) {
global $CFG;
$sesstarttime = $formdata->sestime['starthour'] * HOURSECS + $formdata->sestime['startminute'] * MINSECS;
$sesendtime = $formdata->sestime['endhour'] * HOURSECS + $formdata->sestime['endminute'] * MINSECS;
$sessiondate = $formdata->sessiondate + $sesstarttime;
$duration = $sesendtime - $sesstarttime;
if (empty(get_config('attendance', 'enablewarnings'))) {
$absenteereport = get_config('attendance', 'absenteereport_default');
} else {
$absenteereport = empty($formdata->absenteereport) ? 0 : 1;
}
$now = time();
if (empty(get_config('attendance', 'studentscanmark'))) {
@ -557,15 +608,37 @@ function attendance_construct_sessions_data_for_add($formdata) {
$sess->description = $formdata->sdescription['text'];
$sess->descriptionformat = $formdata->sdescription['format'];
$sess->timemodified = $now;
$sess->absenteereport = $absenteereport;
$sess->studentpassword = '';
if (isset($formdata->studentscanmark)) { // Students will be able to mark their own attendance.
$sess->studentscanmark = 1;
if (!empty($formdata->usedefaultsubnet)) {
$sess->subnet = $att->subnet;
} else {
$sess->subnet = $formdata->subnet;
}
$sess->automark = $formdata->automark;
if (isset($formdata->autoassignstatus)) {
$sess->autoassignstatus = 1;
}
$sess->automarkcompleted = 0;
if (!empty($formdata->randompassword)) {
$sess->studentpassword = attendance_random_string();
} else {
} else if (!empty($formdata->studentpassword)) {
$sess->studentpassword = $formdata->studentpassword;
}
if (!empty($formdata->preventsharedip)) {
$sess->preventsharedip = $formdata->preventsharedip;
}
if (!empty($formdata->preventsharediptime)) {
$sess->preventsharediptime = $formdata->preventsharediptime;
}
} else {
$sess->studentpassword = '';
$sess->subnet = '';
$sess->automark = 0;
$sess->automarkcompleted = 0;
$sess->preventsharedip = 0;
$sess->preventsharediptime = '';
}
$sess->statusset = $formdata->statusset;
@ -586,16 +659,39 @@ function attendance_construct_sessions_data_for_add($formdata) {
$sess->descriptionformat = $formdata->sdescription['format'];
$sess->timemodified = $now;
$sess->studentscanmark = 0;
$sess->autoassignstatus = 0;
$sess->subnet = '';
$sess->studentpassword = '';
$sess->automark = 0;
$sess->automarkcompleted = 0;
$sess->absenteereport = $absenteereport;
if (isset($formdata->studentscanmark) && !empty($formdata->studentscanmark)) {
// Students will be able to mark their own attendance.
$sess->studentscanmark = 1;
if (isset($formdata->autoassignstatus) && !empty($formdata->autoassignstatus)) {
$sess->autoassignstatus = 1;
}
if (!empty($formdata->randompassword)) {
$sess->studentpassword = attendance_random_string();
} else {
} else if (!empty($formdata->studentpassword)) {
$sess->studentpassword = $formdata->studentpassword;
}
if (!empty($formdata->usedefaultsubnet)) {
$sess->subnet = $att->subnet;
} else {
$sess->subnet = $formdata->subnet;
}
if (!empty($formdata->automark)) {
$sess->automark = $formdata->automark;
}
if (!empty($formdata->preventsharedip)) {
$sess->preventsharedip = $formdata->preventsharedip;
}
if (!empty($formdata->preventsharediptime)) {
$sess->preventsharediptime = $formdata->preventsharediptime;
}
}
$sess->statusset = $formdata->statusset;
@ -625,3 +721,214 @@ function attendance_fill_groupid($formdata, &$sessions, $sess) {
}
}
}
/**
* Generates a summary of points for the courses selected.
*
* @param array $courseids optional list of courses to return
* @param string $orderby - optional order by param
* @return stdClass
*/
function attendance_course_users_points($courseids = array(), $orderby = '') {
global $DB;
$where = '';
$params = array();
$where .= ' AND ats.sessdate < :enddate ';
$params['enddate'] = time();
$joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
$where .= ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
if (!empty($courseids)) {
list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
$where .= ' AND c.id ' . $insql;
$params = array_merge($params, $inparams);
}
$sql = "SELECT courseid, coursename, sum(points) / sum(maxpoints) as percentage FROM (
SELECT a.id, a.course as courseid, c.fullname as coursename, atl.studentid AS userid, COUNT(DISTINCT ats.id) AS numtakensessions,
SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints
FROM {attendance_sessions} ats
JOIN {attendance} a ON a.id = ats.attendanceid
JOIN {course} c ON c.id = a.course
JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
FROM {attendance_statuses}
WHERE deleted = 0
AND visible = 1
GROUP BY attendanceid, setnumber) stm
ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
{$joingroup}
WHERE ats.sessdate >= c.startdate
AND ats.lasttaken != 0
{$where}
GROUP BY a.id, a.course, c.fullname, atl.studentid
) p GROUP by courseid, coursename {$orderby}";
return $DB->get_records_sql($sql, $params);
}
/**
* Generates a list of users flagged absent.
*
* @param array $courseids optional list of courses to return
* @param string $orderby how to order results.
* @param bool $allfornotify get notification list for scheduled task.
* @return stdClass
*/
function attendance_get_users_to_notify($courseids = array(), $orderby = '', $allfornotify = false) {
global $DB;
$joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
$where = ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
$having = '';
$params = array();
if (!empty($courseids)) {
list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
$where .= ' AND c.id ' . $insql;
$params = array_merge($params, $inparams);
}
if ($allfornotify) {
// Exclude warnings that have already sent the max num.
$having .= ' AND n.maxwarn > COUNT(DISTINCT ns.id) ';
}
$unames = get_all_user_name_fields(true);
$unames2 = get_all_user_name_fields(true, 'u');
$idfield = $DB->sql_concat('cm.id', 'atl.studentid', 'n.id');
$sql = "SELECT {$idfield} as uniqueid, a.id as aid, {$unames2}, a.name as aname, cm.id as cmid, c.id as courseid,
c.fullname as coursename, atl.studentid AS userid, n.id as notifyid, n.warningpercent, n.emailsubject,
n.emailcontent, n.emailcontentformat, n.emailuser, n.thirdpartyemails, n.warnafter, n.maxwarn,
COUNT(DISTINCT ats.id) AS numtakensessions, SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints,
COUNT(DISTINCT ns.id) as nscount, MAX(ns.timesent) as timesent,
SUM(stg.grade) / SUM(stm.maxgrade) AS percent
FROM {attendance_sessions} ats
JOIN {attendance} a ON a.id = ats.attendanceid
JOIN {course_modules} cm ON cm.instance = a.id
JOIN {course} c on c.id = cm.course
JOIN {modules} md ON md.id = cm.module AND md.name = 'attendance'
JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
JOIN {user} u ON (u.id = atl.studentid)
JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
JOIN {attendance_warning} n ON n.idnumber = a.id
LEFT JOIN {attendance_warning_done} ns ON ns.notifyid = n.id AND ns.userid = atl.studentid
JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
FROM {attendance_statuses}
WHERE deleted = 0
AND visible = 1
GROUP BY attendanceid, setnumber) stm
ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
{$joingroup}
WHERE ats.absenteereport = 1 {$where}
GROUP BY uniqueid, a.id, a.name, a.course, c.fullname, atl.studentid, n.id, n.warningpercent,
n.emailsubject, n.emailcontent, n.emailcontentformat, n.warnafter, n.maxwarn,
n.emailuser, n.thirdpartyemails, cm.id, c.id, {$unames2}, ns.userid
HAVING n.warnafter <= COUNT(DISTINCT ats.id) AND n.warningpercent > ((SUM(stg.grade) / SUM(stm.maxgrade)) * 100)
{$having}
{$orderby}";
if (!$allfornotify) {
$idfield = $DB->sql_concat('cmid', 'userid');
// Only show one record per attendance for teacher reports.
$sql = "SELECT DISTINCT {$idfield} as id, {$unames}, aid, cmid, courseid, aname, coursename, userid,
numtakensessions, percent, MAX(timesent) as timesent
FROM ({$sql}) as m
GROUP BY id, aid, cmid, courseid, aname, userid, numtakensessions,
percent, coursename, {$unames} {$orderby}";
}
return $DB->get_records_sql($sql, $params);
}
/**
* Template variables into place in supplied email content.
*
* @param object $record db record of details
* @return array - the content of the fields after templating.
*/
function attendance_template_variables($record) {
$templatevars = array(
'/%coursename%/' => $record->coursename,
'/%courseid%/' => $record->courseid,
'/%userfirstname%/' => $record->firstname,
'/%userlastname%/' => $record->lastname,
'/%userid%/' => $record->userid,
'/%warningpercent%/' => $record->warningpercent,
'/%attendancename%/' => $record->aname,
'/%cmid%/' => $record->cmid,
'/%numtakensessions%/' => $record->numtakensessions,
'/%points%/' => $record->points,
'/%maxpoints%/' => $record->maxpoints,
'/%percent%/' => $record->percent,
);
$extrauserfields = get_all_user_name_fields();
foreach ($extrauserfields as $extra) {
$templatevars['/%'.$extra.'%/'] = $record->$extra;
}
$patterns = array_keys($templatevars); // The placeholders which are to be replaced.
$replacements = array_values($templatevars); // The values which are to be templated in for the placeholders.
// Array to describe which fields in reengagement object should have a template replacement.
$replacementfields = array('emailsubject', 'emailcontent');
// Replace %variable% with relevant value everywhere it occurs in reengagement->field.
foreach ($replacementfields as $field) {
$record->$field = preg_replace($patterns, $replacements, $record->$field);
}
return $record;
}
/**
* Find highest available status for a user.
*
* @param mod_attendance_structure $att attendance structure
* @param stdclass $attforsession attendance_session record.
* @return bool/int
*/
function attendance_session_get_highest_status(mod_attendance_structure $att, $attforsession) {
// Find the status to set here.
$statuses = $att->get_statuses();
$highestavailablegrade = 0;
$highestavailablestatus = new stdClass();
foreach ($statuses as $status) {
if ($status->studentavailability === '0') {
// This status is never available to students.
continue;
}
if (!empty($status->studentavailability)) {
$toolateforstatus = (($attforsession->sessdate + ($status->studentavailability * 60)) < time());
if ($toolateforstatus) {
continue;
}
}
// This status is available to the student.
if ($status->grade > $highestavailablegrade) {
// This is the most favourable grade so far; save it.
$highestavailablegrade = $status->grade;
$highestavailablestatus = $status;
}
}
if (empty($highestavailablestatus)) {
return false;
}
return $highestavailablestatus->id;
}
/**
* Get available automark options.
*
* @return array
*/
function attendance_get_automarkoptions() {
$options = array();
$options[ATTENDANCE_AUTOMARK_DISABLED] = get_string('noautomark', 'attendance');
if (strpos(get_config('tool_log', 'enabled_stores'), 'logstore_standard') !== false) {
$options[ATTENDANCE_AUTOMARK_ALL] = get_string('automarkall', 'attendance');
}
$options[ATTENDANCE_AUTOMARK_CLOSE] = get_string('automarkclose', 'attendance');
return $options;
}

15
mod_form.php

@ -59,12 +59,17 @@ class mod_attendance_mod_form extends moodleform_mod {
$this->standard_coursemodule_elements(true);
$mform->addElement('header', 'security', get_string('extrarestrictions', 'attendance'));
// IP address.
$mform->addElement('text', 'subnet', get_string('requiresubnet', 'attendance'), array('size' => '164'));
$mform->setType('subnet', PARAM_TEXT);
$mform->addHelpButton('subnet', 'requiresubnet', 'attendance');
$mform->setDefault('subnet', $attendanceconfig->subnet);
if (get_config('attendance', 'subnetactivitylevel')) {
$mform->addElement('header', 'security', get_string('extrarestrictions', 'attendance'));
$mform->addElement('text', 'subnet', get_string('defaultsubnet', 'attendance'), array('size' => '164'));
$mform->setType('subnet', PARAM_TEXT);
$mform->addHelpButton('subnet', 'defaultsubnet', 'attendance');
$mform->setDefault('subnet', $attendanceconfig->subnet);
} else {
$mform->addElement('hidden', 'subnet', '');
$mform->setType('subnet', PARAM_TEXT);
}
$this->add_action_buttons();
}

6
module.js

@ -1,6 +1,6 @@
M.mod_attendance = {};
M.mod_attendance = {}; // eslint-disable-line camelcase
M.mod_attendance.init_manage = function(Y) {
M.mod_attendance.init_manage = function(Y) { // eslint-disable-line camelcase
Y.on('click', function(e) {
if (e.target.get('checked')) {
@ -15,7 +15,7 @@ M.mod_attendance.init_manage = function(Y) {
}, '#cb_selector');
};
M.mod_attendance.set_preferences_action = function(action) {
M.mod_attendance.set_preferences_action = function(action) { // eslint-disable-line camelcase
var item = document.getElementById('preferencesaction');
if (item) {
item.setAttribute('value', action);

51
password.php

@ -0,0 +1,51 @@
<?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/>.
/**
* Displays help via AJAX call or in a new page
*
* Use {@link core_renderer::help_icon()} or {@link addHelpButton()} to display
* the help icon.
*
* @copyright 2017 Dan Marsden
* @package mod_attendance
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__).'/../../config.php');
$session = required_param('session', PARAM_INT);
$session = $DB->get_record('attendance_sessions', array('id' => $session), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('attendance', $session->attendanceid);
require_login($cm->course, $cm);
$context = context_module::instance($cm->id);
$capabilities = array('mod/attendance:manageattendances', 'mod/attendance:takeattendances', 'mod/attendance:changeattendances');
if (!has_any_capability($capabilities, $context)) {
exit;
}
$PAGE->set_url('/mod/attendance/password.php');
$PAGE->set_pagelayout('popup');
$PAGE->set_context(context_system::instance());
$PAGE->set_title(get_string('password', 'attendance'));
echo $OUTPUT->header();
echo html_writer::span($session->studentpassword, 'student-password');
echo $OUTPUT->footer();

50
password_ajax.php

@ -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/>.
/**
* Displays help via AJAX call or in a new page
*
* Use {@link core_renderer::help_icon()} or {@link addHelpButton()} to display
* the help icon.
*
* @copyright 2017 Dan Marsden
* @package mod_attendance
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('AJAX_SCRIPT', true);
require_once(dirname(__FILE__).'/../../config.php');
$session = required_param('session', PARAM_INT);
$session = $DB->get_record('attendance_sessions', array('id' => $session), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('attendance', $session->attendanceid);
require_login($cm->course, $cm);
$context = context_module::instance($cm->id);
$capabilities = array('mod/attendance:manageattendances', 'mod/attendance:takeattendances', 'mod/attendance:changeattendances');
if (!has_any_capability($capabilities, $context)) {
exit;
}
$PAGE->set_url('/mod/attendance/password.php');
$PAGE->set_pagelayout('popup');
$PAGE->set_context(context_system::instance());
$data->heading = '';
$data->text = html_writer::span($session->studentpassword, 'student-password');
echo json_encode($data);

1
pix/key.svg

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M832 512q0-80-56-136t-136-56-136 56-56 136q0 42 19 83-41-19-83-19-80 0-136 56t-56 136 56 136 136 56 136-56 56-136q0-42-19-83 41 19 83 19 80 0 136-56t56-136zm851 704q0 17-49 66t-66 49q-9 0-28.5-16t-36.5-33-38.5-40-24.5-26l-96 96 220 220q28 28 28 68 0 42-39 81t-81 39q-40 0-68-28l-671-671q-176 131-365 131-163 0-265.5-102.5t-102.5-265.5q0-160 95-313t248-248 313-95q163 0 265.5 102.5t102.5 265.5q0 189-131 365l355 355 96-96q-3-3-26-24.5t-40-38.5-33-36.5-16-28.5q0-17 49-66t66-49q13 0 23 10 6 6 46 44.5t82 79.5 86.5 86 73 78 28.5 41z"/></svg>

Before

Width:  |  Height:  |  Size: 638 B

After

Width:  |  Height:  |  Size: 638 B

33
preferences.php

@ -61,19 +61,29 @@ $errors = array();
if (!empty($att->pageparams->action)) {
require_sesskey();
}
$notification = '';
// TODO: combine this with the stuff in defaultstatus.php to avoid code duplication.
switch ($att->pageparams->action) {
case mod_attendance_preferences_page_params::ACTION_ADD:
$newacronym = optional_param('newacronym', null, PARAM_TEXT);
$newdescription = optional_param('newdescription', null, PARAM_TEXT);
$newgrade = optional_param('newgrade', 0, PARAM_RAW);
$newstudentavailability = optional_param('newstudentavailability', null, PARAM_INT);
$newgrade = unformat_float($newgrade);
$status = attendance_add_status($newacronym, $newdescription, $newgrade, $att->id,
$att->pageparams->statusset, $att->context, $att->cm);
$newstatus = new stdClass();
$newstatus->attendanceid = $att->id;
$newstatus->acronym = $newacronym;
$newstatus->description = $newdescription;
$newstatus->grade = $newgrade;
$newstatus->studentavailability = $newstudentavailability;
$newstatus->setnumber = $att->pageparams->statusset;
$newstatus->cm = $att->cm;
$newstatus->context = $att->context;
$status = attendance_add_status($newstatus);
if (!$status) {
print_error('cantaddstatus', 'attendance', $this->url_preferences());
$notification = $OUTPUT->notification(get_string('cantaddstatus', 'attendance'), 'error');
}
if ($pageparams->statusset > $maxstatusset) {
@ -94,7 +104,7 @@ switch ($att->pageparams->action) {
redirect($att->url_preferences(), get_string('statusdeleted', 'attendance'));
}
$message = get_string('deletecheckfull', '', get_string('variable', 'attendance'));
$message = get_string('deletecheckfull', 'attendance', get_string('variable', 'attendance'));
$message .= str_repeat(html_writer::empty_tag('br'), 2);
$message .= $status->acronym.': '.
($status->description ? $status->description : get_string('nodescription', 'attendance'));
@ -118,6 +128,9 @@ switch ($att->pageparams->action) {
$acronym = required_param_array('acronym', PARAM_TEXT);
$description = required_param_array('description', PARAM_TEXT);
$grade = required_param_array('grade', PARAM_RAW);
$studentavailability = optional_param_array('studentavailability', null, PARAM_RAW);
$unmarkedstatus = optional_param('setunmarked', null, PARAM_INT);
foreach ($grade as &$val) {
$val = unformat_float($val);
}
@ -125,8 +138,12 @@ switch ($att->pageparams->action) {
foreach ($acronym as $id => $v) {
$status = $statuses[$id];
$setunmarked = false;
if ($unmarkedstatus == $id) {
$setunmarked = true;
}
$errors[$id] = attendance_update_status($status, $acronym[$id], $description[$id], $grade[$id],
null, $att->context, $att->cm);
null, $att->context, $att->cm, $studentavailability[$id], $setunmarked);
}
attendance_update_users_grade($att);
break;
@ -140,8 +157,12 @@ $setselector = new attendance_set_selector($att, $maxstatusset);
// Output starts here.
echo $output->header();
if (!empty($notification)) {
echo $notification;
}
echo $output->heading(get_string('attendanceforthecourse', 'attendance').' :: '. format_string($course->fullname));
echo $output->render($tabs);
echo $OUTPUT->box(get_string('preferences_desc', 'attendance'), 'generalbox attendancedesc', 'notice');
echo $output->render($setselector);
echo $output->render($prefdata);

80
renderables.php

@ -51,7 +51,10 @@ class attendance_tabs implements renderable {
const TAB_TEMPORARYUSERS = 6; // Tab for managing temporary users.
/** Update tab */
const TAB_UPDATE = 7;
/** Warnings tab */
const TAB_WARNINGS = 8;
/** Absentee tab */
const TAB_ABSENTEE = 9;
/** @var int current tab */
public $currenttab;
@ -97,6 +100,12 @@ class attendance_tabs implements renderable {
get_string('report', 'attendance'));
}
if (has_capability('mod/attendance:viewreports', $context) &&
get_config('attendance', 'enablewarnings')) {
$toprow[] = new tabobject(self::TAB_ABSENTEE, $this->att->url_absentee()->out(),
get_string('absenteereport', 'attendance'));
}
if (has_capability('mod/attendance:export', $context)) {
$toprow[] = new tabobject(self::TAB_EXPORT, $this->att->url_export()->out(),
get_string('export', 'attendance'));
@ -105,6 +114,11 @@ class attendance_tabs implements renderable {
if (has_capability('mod/attendance:changepreferences', $context)) {
$toprow[] = new tabobject(self::TAB_PREFERENCES, $this->att->url_preferences()->out(),
get_string('statussetsettings', 'attendance'));
if (get_config('attendance', 'enablewarnings')) {
$toprow[] = new tabobject(self::TAB_WARNINGS, $this->att->url_warnings()->out(),
get_string('warnings', 'attendance'));
}
}
if (has_capability('mod/attendance:managetemporaryusers', $context)) {
$toprow[] = new tabobject(self::TAB_TEMPORARYUSERS, $this->att->url_managetemp()->out(),
@ -475,9 +489,12 @@ class attendance_user_data implements renderable {
$this->summary = array();
foreach ($this->coursesatts as $atid => $ca) {
// Check to make sure the user can view this cm.
if (!get_fast_modinfo($ca->courseid)->instances['attendance'][$ca->attid]->uservisible) {
unset($this->courseatts[$atid]);
$modinfo = get_fast_modinfo($ca->courseid);
if (!$modinfo->instances['attendance'][$ca->attid]->uservisible) {
unset($this->coursesatts[$atid]);
continue;
} else {
$this->coursesatts[$atid]->cmid = $modinfo->instances['attendance'][$ca->attid]->get_course_module_record()->id;
}
$this->statuses[$ca->attid] = attendance_get_statuses($ca->attid);
$this->summary[$ca->attid] = new mod_attendance_summary($ca->attid, array($userid));
@ -792,3 +809,60 @@ class url_helpers {
return $att->url_view($params);
}
}
/**
* Data structure representing an attendance password icon.
*
* @copyright 2017 Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attendance_password_icon implements renderable, templatable {
/**
* @var string text to show
*/
public $text;
/**
* @var string Extra descriptive text next to the icon
*/
public $linktext = null;
/**
* Constructor
*
* @param string $text string for help page title,
* string with _help suffix is used for the actual help text.
* string with _link suffix is used to create a link to further info (if it exists)
* @param string $sessionid
*/
public function __construct($text, $sessionid) {
$this->text = $text;
$this->sessionid = $sessionid;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return array
*/
public function export_for_template(renderer_base $output) {
$title = get_string('password', 'attendance');
$data = new stdClass();
$data->heading = '';
$data->text = $this->text;
$data->alt = $title;
$data->icon = (new pix_icon('key', '', 'attendance'))->export_for_template($output);
$data->linktext = '';
$data->title = $title;
$data->url = (new moodle_url('/mod/attendance/password.php', [
'session' => $this->sessionid]))->out(false);
$data->ltr = !right_to_left();
return $data;
}
}

331
renderer.php

@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
require_once(dirname(__FILE__).'/locallib.php');
require_once(dirname(__FILE__).'/renderables.php');
require_once(dirname(__FILE__).'/renderhelpers.php');
require_once($CFG->libdir.'/tablelib.php');
/**
* Attendance module renderer class
@ -165,6 +166,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$params = array(
'title' => get_string('calshow', 'attendance'),
'id' => 'show',
'class' => 'btn btn-secondary',
'type' => 'button');
$buttonform = html_writer::tag('button', $fcontrols->curdatetxt, $params);
foreach ($fcontrols->url_params(array('curdate' => '')) as $name => $value) {
@ -257,8 +259,8 @@ class mod_attendance_renderer extends plugin_renderer_base {
get_string('actions'),
html_writer::checkbox('cb_selector', 0, false, '', array('id' => 'cb_selector'))
);
$table->align = array('', 'right', '', '', 'left', 'center', 'center');
$table->size = array('1px', '1px', '1px', '', '*', '110px', '1px');
$table->align = array('', 'right', '', '', 'left', 'right', 'center');
$table->size = array('1px', '1px', '1px', '', '*', '120px', '1px');
$i = 0;
foreach ($sessdata->sessions as $key => $sess) {
@ -291,6 +293,15 @@ class mod_attendance_renderer extends plugin_renderer_base {
return html_writer::table($table);
}
/**
* Implementation of user image rendering.
*
* @param attendance_password_icon $helpicon A help icon instance
* @return string HTML fragment
*/
protected function render_attendance_password_icon(attendance_password_icon $helpicon) {
return $this->render_from_template('attendance/attendance_password_icon', $helpicon->export_for_template($this));
}
/**
* Construct date time actions.
*
@ -300,6 +311,14 @@ class mod_attendance_renderer extends plugin_renderer_base {
*/
private function construct_date_time_actions(attendance_manage_data $sessdata, $sess) {
$actions = '';
if (!empty($sess->studentpassword) &&
(has_capability('mod/attendance:manageattendances', $sessdata->att->context) ||
has_capability('mod/attendance:takeattendances', $sessdata->att->context) ||
has_capability('mod/attendance:changeattendances', $sessdata->att->context))) {
$icon = new attendance_password_icon($sess->studentpassword, $sess->id);
$actions .= $this->render($icon);
}
$date = userdate($sess->sessdate, get_string('strftimedmyw', 'attendance'));
$time = $this->construct_time($sess->sessdate, $sess->duration);
@ -311,7 +330,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$date = html_writer::link($url, $date, array('title' => $title));
$time = html_writer::link($url, $time, array('title' => $title));
$actions = $this->output->action_icon($url, new pix_icon('redo', $title, 'attendance'));
$actions .= $this->output->action_icon($url, new pix_icon('redo', $title, 'attendance'));
} else {
$date = '<i>' . $date . '</i>';
$time = '<i>' . $time . '</i>';
@ -320,7 +339,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
if (has_capability('mod/attendance:takeattendances', $sessdata->att->context)) {
$url = $sessdata->url_take($sess->id, $sess->groupid);
$title = get_string('takeattendance', 'attendance');
$actions = $this->output->action_icon($url, new pix_icon('t/go', $title));
$actions .= $this->output->action_icon($url, new pix_icon('t/go', $title));
}
}
@ -357,6 +376,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$attributes = array(
'type' => 'submit',
'name' => 'deletehiddensessions',
'class' => 'btn btn-secondary',
'value' => get_string('deletehiddensessions', 'attendance'));
$table->data[1][] = html_writer::empty_tag('input', $attributes);
}
@ -368,7 +388,8 @@ class mod_attendance_renderer extends plugin_renderer_base {
$attributes = array(
'type' => 'submit',
'name' => 'ok',
'value' => get_string('ok'));
'value' => get_string('ok'),
'class' => 'btn btn-secondary');
$controls .= html_writer::empty_tag('input', $attributes);
} else {
$controls = get_string('youcantdo', 'attendance'); // You can't do anything.
@ -397,6 +418,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table .= html_writer::end_div();
$params = array(
'type' => 'submit',
'class' => 'btn btn-primary',
'value' => get_string('save', 'attendance'));
$table .= html_writer::tag('center', html_writer::empty_tag('input', $params));
$table = html_writer::tag('form', $table, array('method' => 'post', 'action' => $takedata->url_path(),
@ -409,7 +431,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$sessionstats[] = array();
foreach ($takedata->sessionlog as $userlog) {
foreach ($takedata->statuses as $status) {
if ($userlog->statusid == $status->id) {
if ($userlog->statusid == $status->id && in_array($userlog->studentid, array_keys($takedata->users))) {
$sessionstats[$status->id]++;
}
}
@ -479,7 +501,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
}
}
if (!empty($CFG->enablegroupmembersonly) and $takedata->cm->groupmembersonly) {
if (!empty($takedata->cm->groupingid)) {
if ($group == 0) {
$groups = array_keys(groups_get_all_groups($takedata->cm->course, 0, $takedata->cm->groupingid, 'g.id'));
} else {
@ -546,7 +568,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$controls .= $this->output->render($select);
}
if (count($takedata->sessions4copy) > 0) {
if (isset($takedata->sessions4copy) && count($takedata->sessions4copy) > 0) {
$controls .= html_writer::empty_tag('br');
$controls .= html_writer::empty_tag('br');
@ -615,6 +637,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
// Show a 'select all' row of radio buttons.
$row = new html_table_row();
$row->cells[] = '';
$row->attributes['class'] = 'setallstatusesrow';
foreach ($extrasearchfields as $field) {
$row->cells[] = '';
}
@ -682,6 +705,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
protected function render_attendance_take_grid(attendance_take_data $takedata) {
global $PAGE;
$table = new html_table();
for ($i = 0; $i < $takedata->pageparams->gridcols; $i++) {
$table->align[] = 'center';
@ -691,8 +715,16 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table->headspan = $takedata->pageparams->gridcols;
$head = array();
foreach ($takedata->statuses as $st) {
$head[] = html_writer::link("javascript:select_all_in(null, 'st" . $st->id . "', null);", $st->acronym,
array('title' => get_string('setallstatusesto', 'attendance', $st->description)));
$head[] = html_writer::link("#", $st->acronym, array('id' => 'checkstatus'.$st->id,
'title' => get_string('setallstatusesto', 'attendance', $st->description)));
// JS to select all radios of this status and prevent default behaviour of # link.
$PAGE->requires->js_amd_inline("
require(['jquery'], function($) {
$('#checkstatus".$st->id."').click(function(e) {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
e.preventDefault();
});
});");
}
$table->head[] = implode('&nbsp;&nbsp;', $head);
@ -853,17 +885,26 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
protected function render_attendance_user_data(attendance_user_data $userdata) {
global $USER;
$o = $this->render_user_report_tabs($userdata);
$table = new html_table();
if ($USER->id == $userdata->user->id) {
$table->attributes['class'] = 'userinfobox';
$table->colclasses = array('left side', '');
// Show different picture if it is a temporary user.
$table->data[0][] = $this->user_picture($userdata->user, array('size' => 100));
$table->data[0][] = $this->construct_user_data($userdata);
$o .= $this->construct_user_data($userdata);
$o .= html_writer::table($table);
} else {
$table = new html_table();
$table->attributes['class'] = 'userinfobox';
$table->colclasses = array('left side', '');
// Show different picture if it is a temporary user.
$table->data[0][] = $this->user_picture($userdata->user, array('size' => 100));
$table->data[0][] = $this->construct_user_data($userdata);
$o .= html_writer::table($table);
}
return $o;
}
@ -898,32 +939,93 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
private function construct_user_data(attendance_user_data $userdata) {
$o = html_writer::tag('h2', fullname($userdata->user));
global $USER;
$o = '';
if ($USER->id <> $userdata->user->id) {
$o = html_writer::tag('h2', fullname($userdata->user));
}
if ($userdata->pageparams->mode == mod_attendance_view_page_params::MODE_THIS_COURSE) {
$o .= html_writer::empty_tag('hr');
$o .= construct_user_data_stat($userdata->summary->get_all_sessions_summary_for($userdata->user->id),
$userdata->pageparams->view);
$o .= $this->render_attendance_filter_controls($userdata->filtercontrols);
$o .= $this->construct_user_sessions_log($userdata);
$o .= html_writer::empty_tag('hr');
$o .= construct_user_data_stat($userdata->summary->get_all_sessions_summary_for($userdata->user->id),
$userdata->pageparams->view);
} else {
$prevcid = 0;
$table = new html_table();
$table->head = array(get_string('course'),
get_string('pluginname', 'mod_attendance'),
get_string('sessionscompleted', 'attendance'),
get_string('pointssessionscompleted', 'attendance'),
get_string('percentagesessionscompleted', 'attendance'));
$table->align = array('left', 'left', 'center', 'center', 'center');
$table->colclasses = array('colcourse', 'colatt', 'colsessionscompleted',
'colpointssessionscompleted', 'colpercentagesessionscompleted');
$table2 = clone($table); // Duplicate table for ungraded sessions.
$totalattendance = 0;
$totalpercentage = 0;
foreach ($userdata->coursesatts as $ca) {
if ($prevcid != $ca->courseid) {
$o .= html_writer::empty_tag('hr');
$prevcid = $ca->courseid;
$o .= html_writer::tag('h3', $ca->coursefullname);
}
$row = new html_table_row();
$courseurl = new moodle_url('/course/view.php', array('id' => $ca->courseid));
$row->cells[] = html_writer::link($courseurl, $ca->coursefullname);
$attendanceurl = new moodle_url('/mod/attendance/view.php', array('id' => $ca->cmid,
'studentid' => $userdata->user->id,
'view' => ATT_VIEW_ALL));
$row->cells[] = html_writer::link($attendanceurl, $ca->attname);
$usersummary = new stdClass();
if (isset($userdata->summary[$ca->attid])) {
$o .= html_writer::tag('h4', $ca->attname);
$usersummary = $userdata->summary[$ca->attid]->get_all_sessions_summary_for($userdata->user->id);
$o .= construct_user_data_stat($usersummary, ATT_VIEW_ALL);
$row->cells[] = $usersummary->numtakensessions;
$row->cells[] = format_float($usersummary->takensessionspoints, 1, true, true) . ' / ' .
format_float($usersummary->takensessionsmaxpoints, 1, true, true);
if (empty($usersummary->numtakensessions)) {
$row->cells[] = '-';
} else {
$row->cells[] = format_float($usersummary->takensessionspercentage * 100) . '%';
}
}
if (empty($ca->attgrade)) {
$table2->data[] = $row;
} else {
$table->data[] = $row;
if ($usersummary->numtakensessions > 0) {
$totalattendance++;
$totalpercentage = $totalpercentage + format_float($usersummary->takensessionspercentage * 100);
}
}
}
$row = new html_table_row();
if (empty($totalattendance)) {
$average = '-';
} else {
$average = format_float($totalpercentage / $totalattendance).'%';
}
$col = new html_table_cell(get_string('averageattendancegraded', 'mod_attendance'));
$col->attributes['class'] = 'averageattendance';
$col->colspan = 4;
$col2 = new html_table_cell($average);
$col2->style = 'text-align: center';
$row->cells = array($col, $col2);
$table->data[] = $row;
if (!empty($table2->data) && !empty($table->data)) {
// Print graded header if both tables are being shown.
$o .= html_writer::div("<h3>".get_string('graded', 'mod_attendance')."</h3>");
}
if (!empty($table->data)) {
// Don't bother printing the table if no sessions are being shown.
$o .= html_writer::table($table);
}
if (!empty($table2->data)) {
// Don't print this if it doesn't contain any data.
$o .= html_writer::div("<h3>".get_string('ungraded', 'mod_attendance')."</h3>");
$o .= html_writer::table($table2);
}
}
@ -937,25 +1039,41 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
private function construct_user_sessions_log(attendance_user_data $userdata) {
global $OUTPUT;
global $OUTPUT, $USER;
$context = context_module::instance($userdata->filtercontrols->cm->id);
$shortform = false;
if ($USER->id == $userdata->user->id) {
// This is a user viewing their own stuff - hide non-relevant columns.
$shortform = true;
}
$table = new html_table();
$table->attributes['class'] = 'generaltable attwidth boxaligncenter';
$table->head = array(
'#',
get_string('sessiontypeshort', 'attendance'),
get_string('date'),
get_string('time'),
get_string('description', 'attendance'),
get_string('status', 'attendance'),
get_string('points', 'attendance'),
get_string('remarks', 'attendance')
);
$table->align = array('', '', '', 'left', 'left', 'center', 'center', 'center');
$table->size = array('1px', '1px', '1px', '1px', '*', '*', '1px', '*');
$table->head = array();
$table->align = array();
$table->size = array();
$table->colclasses = array();
if (!$shortform) {
$table->head[] = get_string('sessiontypeshort', 'attendance');
$table->align[] = '';
$table->size[] = '1px';
$table->colclasses[] = '';
}
$table->head[] = get_string('date');
$table->head[] = get_string('description', 'attendance');
$table->head[] = get_string('status', 'attendance');
$table->head[] = get_string('points', 'attendance');
$table->head[] = get_string('remarks', 'attendance');
$table->align = array_merge($table->align, array('', 'left', 'center', 'center', 'center'));
$table->colclasses = array_merge($table->colclasses, array('datecol', 'desccol', 'statuscol', 'pointscol', 'remarkscol'));
$table->size = array_merge($table->size, array('1px', '*', '*', '1px', '*'));
if (has_capability('mod/attendance:takeattendances', $context)) {
$table->head[] = get_string('action');
$table->align[] = '';
$table->size[] = '';
}
$statussetmaxpoints = attendance_get_statusset_maxpoints($userdata->statuses);
@ -965,32 +1083,33 @@ class mod_attendance_renderer extends plugin_renderer_base {
$i++;
$row = new html_table_row();
$row->cells[] = $i;
if ($sess->groupid) {
$sessiontypeshort = get_string('group') . ': ' . $userdata->groups[$sess->groupid]->name;
} else {
$sessiontypeshort = get_string('commonsession', 'attendance');
}
if (!$shortform) {
if ($sess->groupid) {
$sessiontypeshort = get_string('group') . ': ' . $userdata->groups[$sess->groupid]->name;
} else {
$sessiontypeshort = get_string('commonsession', 'attendance');
}
$row->cells[] = html_writer::tag('nobr', $sessiontypeshort);
$row->cells[] = userdate($sess->sessdate, get_string('strftimedmyw', 'attendance'));
$row->cells[] = $this->construct_time($sess->sessdate, $sess->duration);
$row->cells[] = html_writer::tag('nobr', $sessiontypeshort);
}
$row->cells[] = userdate($sess->sessdate, get_string('strftimedmyw', 'attendance')) .
" ". $this->construct_time($sess->sessdate, $sess->duration);
$row->cells[] = $sess->description;
if (isset($sess->statusid)) {
if (!empty($sess->statusid)) {
$status = $userdata->statuses[$sess->statusid];
$row->cells[] = $status->description;
$row->cells[] = format_float($status->grade, 1, true, true) . ' / ' .
format_float($statussetmaxpoints[$status->setnumber], 1, true, true);
$row->cells[] = $sess->remarks;
} else if ($sess->sessdate < $userdata->user->enrolmentstart) {
} else if (($sess->sessdate + $sess->duration) < $userdata->user->enrolmentstart) {
$cell = new html_table_cell(get_string('enrolmentstart', 'attendance',
userdate($userdata->user->enrolmentstart, '%d.%m.%Y')));
$cell->colspan = 2;
$cell->colspan = 3;
$row->cells[] = $cell;
} else if ($userdata->user->enrolmentend and $sess->sessdate > $userdata->user->enrolmentend) {
$cell = new html_table_cell(get_string('enrolmentend', 'attendance',
userdate($userdata->user->enrolmentend, '%d.%m.%Y')));
$cell->colspan = 2;
$cell->colspan = 3;
$row->cells[] = $cell;
} else {
if (attendance_can_student_mark($sess)) {
@ -1000,7 +1119,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$url = new moodle_url('/mod/attendance/attendance.php',
array('sessid' => $sess->id, 'sesskey' => sesskey()));
$cell = new html_table_cell(html_writer::link($url, get_string('submitattendance', 'attendance')));
$cell->colspan = 2;
$cell->colspan = 3;
$row->cells[] = $cell;
} else { // Student cannot mark their own attendace.
$row->cells[] = '?';
@ -1102,7 +1221,9 @@ class mod_attendance_renderer extends plugin_renderer_base {
$output .= html_writer::empty_tag('input', array('name' => 'returnto', 'type' => 'hidden', 'value' => s(me())));
$output .= html_writer::table($table).html_writer::tag('div', get_string('users').': '.count($reportdata->users));;
$output .= html_writer::tag('div',
html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('messageselectadd'))),
html_writer::empty_tag('input', array('type' => 'submit',
'value' => get_string('messageselectadd'),
'class' => 'btn btn-secondary')),
array('class' => 'buttons'));
$url = new moodle_url('/user/action_redir.php');
return html_writer::tag('form', $output, array('action' => $url->out(), 'method' => 'post'));
@ -1119,13 +1240,31 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return array Array of html_table_row objects
*/
protected function get_user_rows(attendance_report_data $reportdata) {
global $OUTPUT;
$rows = array();
$extrafields = get_extra_user_fields($reportdata->att->context);
$showextrauserdetails = $reportdata->pageparams->showextrauserdetails;
$params = $reportdata->pageparams->get_significant_params();
$text = get_string('users');
if ($extrafields) {
if ($showextrauserdetails) {
$params['showextrauserdetails'] = 0;
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/switch_minus',
get_string('hideextrauserdetails', 'attendance')), null, null);
} else {
$params['showextrauserdetails'] = 1;
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/switch_plus',
get_string('showextrauserdetails', 'attendance')), null, null);
$extrafields = array();
}
}
$usercolspan = 1 + count($extrafields);
$row = new html_table_row();
$row->cells[] = $this->build_header_cell('');
$row->cells[] = $this->build_header_cell(get_string('users'), false, false, $usercolspan);
$row->cells[] = $this->build_header_cell($text, false, false, $usercolspan);
$rows[] = $row;
$row = new html_table_row();
@ -1370,18 +1509,27 @@ class mod_attendance_renderer extends plugin_renderer_base {
'mod/attendance:changeattendances'
);
if (is_null($sess->lasttaken) and has_any_capability($capabilities, $reportdata->att->context)) {
$sesstext = html_writer::link($reportdata->url_take($sess->id, $sess->groupid), $sesstext);
$sesstext = html_writer::link($reportdata->url_take($sess->id, $sess->groupid), $sesstext,
array('class' => 'attendancereporttakelink'));
}
$sesstext .= html_writer::empty_tag('br', array('class' => 'attendancereportseparator'));
if (!empty($sess->description) &&
!empty(get_config('attendance', 'showsessiondescriptiononreport'))) {
$sesstext .= html_writer::tag('small', format_text($sess->description),
array('class' => 'attendancereportcommon'));
}
$sesstext .= html_writer::empty_tag('br');
if ($sess->groupid) {
if (empty($reportdata->groups[$sess->groupid])) {
$sesstext .= html_writer::tag('small', get_string('deletedgroup', 'attendance'));
$sesstext .= html_writer::tag('small', get_string('deletedgroup', 'attendance'),
array('class' => 'attendancereportgroup'));
} else {
$sesstext .= html_writer::tag('small', $reportdata->groups[$sess->groupid]->name);
$sesstext .= html_writer::tag('small', $reportdata->groups[$sess->groupid]->name,
array('class' => 'attendancereportgroup'));
}
} else {
$sesstext .= html_writer::tag('small', get_string('commonsession', 'attendance'));
$sesstext .= html_writer::tag('small', get_string('commonsession', 'attendance'),
array('class' => 'attendancereportcommon'));
}
$row->cells[] = $this->build_header_cell($sesstext, false, true, null, null, false);
@ -1608,14 +1756,28 @@ class mod_attendance_renderer extends plugin_renderer_base {
protected function render_attendance_preferences_data($prefdata) {
$this->page->requires->js('/mod/attendance/module.js');
$studentscanmark = false;
if (!empty(get_config('attendance', 'studentscanmark'))) {
$studentscanmark = true;
}
$table = new html_table();
$table->width = '100%';
$table->head = array('#',
get_string('acronym', 'attendance'),
get_string('description'),
get_string('points', 'attendance'),
get_string('action'));
get_string('points', 'attendance'));
$table->align = array('center', 'center', 'center', 'center', 'center', 'center');
if ($studentscanmark) {
$table->head[] = get_string('studentavailability', 'attendance').
$this->output->help_icon('studentavailability', 'attendance');
$table->align[] = 'center';
$table->head[] = get_string('setunmarked', 'attendance').
$this->output->help_icon('setunmarked', 'attendance');
$table->align[] = 'center';
}
$table->head[] = get_string('action');
$i = 1;
foreach ($prefdata->statuses as $st) {
@ -1629,14 +1791,24 @@ class mod_attendance_renderer extends plugin_renderer_base {
$emptydescription = $this->construct_notice(get_string('emptydescription', 'mod_attendance') , 'notifyproblem');
}
}
$table->data[$i][] = $i;
$table->data[$i][] = $this->construct_text_input('acronym['.$st->id.']', 2, 2, $st->acronym) . $emptyacronym;
$table->data[$i][] = $this->construct_text_input('description['.$st->id.']', 30, 30, $st->description) .
$cells = array();
$cells[] = $i;
$cells[] = $this->construct_text_input('acronym['.$st->id.']', 2, 2, $st->acronym) . $emptyacronym;
$cells[] = $this->construct_text_input('description['.$st->id.']', 30, 30, $st->description) .
$emptydescription;
$table->data[$i][] = $this->construct_text_input('grade['.$st->id.']', 4, 4, $st->grade);
$table->data[$i][] = $this->construct_preferences_actions_icons($st, $prefdata);
$cells[] = $this->construct_text_input('grade['.$st->id.']', 4, 4, $st->grade);
if ($studentscanmark) {
$checked = '';
if ($st->setunmarked) {
$checked = ' checked ';
}
$cells[] = $this->construct_text_input('studentavailability['.$st->id.']', 4, 5, $st->studentavailability);
$cells[] = '<input type="radio" name="setunmarked" value="'.$st->id.'"'.$checked.'>';
}
$cells[] = $this->construct_preferences_actions_icons($st, $prefdata);
$table->data[$i] = new html_table_row($cells);
$table->data[$i]->id = "statusrow".$i;
$i++;
}
@ -1644,6 +1816,9 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table->data[$i][] = $this->construct_text_input('newacronym', 2, 2);
$table->data[$i][] = $this->construct_text_input('newdescription', 30, 30);
$table->data[$i][] = $this->construct_text_input('newgrade', 4, 4);
if ($studentscanmark) {
$table->data[$i][] = $this->construct_text_input('newstudentavailability', 4, 5);
}
$table->data[$i][] = $this->construct_preferences_button(get_string('add', 'attendance'),
mod_attendance_preferences_page_params::ACTION_ADD);
@ -1696,7 +1871,8 @@ class mod_attendance_renderer extends plugin_renderer_base {
'name' => $name,
'size' => $size,
'maxlength' => $maxlength,
'value' => $value);
'value' => $value,
'class' => 'form-control');
return html_writer::empty_tag('input', $attributes);
}
@ -1745,6 +1921,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$attributes = array(
'type' => 'submit',
'value' => $text,
'class' => 'btn btn-secondary',
'onclick' => 'M.mod_attendance.set_preferences_action('.$action.')');
return html_writer::empty_tag('input', $attributes);
}

2
renderhelpers.php

@ -74,7 +74,7 @@ class user_sessions_cells_generator {
$this->construct_remarks_cell($this->reportdata->sessionslog[$this->user->id][$sess->id]->remarks);
}
} else {
if ($this->user->enrolmentstart > $sess->sessdate) {
if ($this->user->enrolmentstart > ($sess->sessdate + $sess->duration)) {
$starttext = get_string('enrolmentstart', 'attendance', userdate($this->user->enrolmentstart, '%d.%m.%Y'));
$this->construct_enrolments_info_cell($starttext);
} else if ($this->user->enrolmentend and $this->user->enrolmentend < $sess->sessdate) {

1
report.php

@ -46,6 +46,7 @@ $context = context_module::instance($cm->id);
require_capability('mod/attendance:viewreports', $context);
$pageparams->init($cm);
$pageparams->showextrauserdetails = optional_param('showextrauserdetails', $attrecord->showextrauserdetails, PARAM_INT);
$pageparams->showsessiondetails = optional_param('showsessiondetails', $attrecord->showsessiondetails, PARAM_INT);
$pageparams->sessiondetailspos = optional_param('sessiondetailspos', $attrecord->sessiondetailspos, PARAM_TEXT);

92
resetcalendar.php

@ -0,0 +1,92 @@
<?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/>.
/**
* Reset Calendar events.
*
* @package mod_attendance
* @copyright 2017 onwards Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->dirroot.'/mod/attendance/lib.php');
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
$action = optional_param('action', '', PARAM_ALPHA);
admin_externalpage_setup('managemodules');
$context = context_system::instance();
// Check permissions.
require_capability('mod/attendance:viewreports', $context);
$exportfilename = 'attendance-absentee.csv';
$PAGE->set_url('/mod/attendance/resetcalendar.php');
$PAGE->set_heading($SITE->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('resetcalendar', 'mod_attendance'));
$tabmenu = attendance_print_settings_tabs('resetcalendar');
echo $tabmenu;
if (get_config('attendance', 'enablecalendar')) {
// Check to see if all sessions have calendar events.
if ($action == 'create' && confirm_sesskey()) {
$sessions = $DB->get_recordset('attendance_sessions', array('caleventid' => 0));
foreach ($sessions as $session) {
attendance_create_calendar_event($session);
if ($session->caleventid) {
$DB->update_record('attendance_sessions', $session);
}
}
$sessions->close();
echo $OUTPUT->notification(get_string('eventscreated', 'mod_attendance'), 'notifysuccess');
} else {
if ($DB->record_exists('attendance_sessions', array('caleventid' => 0))) {
$createurl = new moodle_url('/mod/attendance/resetcalendar.php', array('action' => 'create'));
$returnurl = new moodle_url('/admin/settings.php', array('section' => 'modsettingattendance'));
echo $OUTPUT->confirm(get_string('resetcaledarcreate', 'mod_attendance'), $createurl, $returnurl);
} else {
echo $OUTPUT->box(get_string("noeventstoreset", "mod_attendance"));
}
}
} else {
if ($action == 'delete' && confirm_sesskey()) {
$caleventids = $DB->get_records_select_menu('attendance_sessions', 'caleventid > 0', array(),
'', 'caleventid, caleventid as id2');
$DB->delete_records_list('event', 'id', $caleventids);
$DB->execute("UPDATE {attendance_sessions} set caleventid = 0");
echo $OUTPUT->notification(get_string('eventsdeleted', 'mod_attendance'), 'notifysuccess');
} else {
// Check to see if there are any events that need to be deleted.
if ($DB->record_exists_select('attendance_sessions', 'caleventid > 0')) {
$deleteurl = new moodle_url('/mod/attendance/resetcalendar.php', array('action' => 'delete'));
$returnurl = new moodle_url('/admin/settings.php', array('section' => 'modsettingattendance'));
echo $OUTPUT->confirm(get_string('resetcaledardelete', 'mod_attendance'), $deleteurl, $returnurl);
} else {
echo $OUTPUT->box(get_string("noeventstoreset", "mod_attendance"));
}
}
}
echo $OUTPUT->footer();

9
sessions.php

@ -73,7 +73,7 @@ switch ($att->pageparams->action) {
}
if ($formdata = $mform->get_data()) {
$sessions = attendance_construct_sessions_data_for_add($formdata);
$sessions = attendance_construct_sessions_data_for_add($formdata, $att);
$att->add_sessions($sessions);
if (count($sessions) == 1) {
$message = get_string('sessiongenerated', 'attendance');
@ -99,6 +99,9 @@ switch ($att->pageparams->action) {
}
if ($formdata = $mform->get_data()) {
if (empty($formdata->autoassignstatus)) {
$formdata->autoassignstatus = 0;
}
$att->update_session_from_form_data($formdata, $sessionid);
mod_attendance_notifyqueue::notify_success(get_string('sessionupdated', 'attendance'));
@ -118,7 +121,7 @@ switch ($att->pageparams->action) {
$sessinfo = $att->get_session_info($sessionid);
$message = get_string('deletecheckfull', '', get_string('session', 'attendance'));
$message = get_string('deletecheckfull', 'attendance', get_string('session', 'attendance'));
$message .= str_repeat(html_writer::empty_tag('br'), 2);
$message .= userdate($sessinfo->sessdate, get_string('strftimedmyhm', 'attendance'));
$message .= html_writer::empty_tag('br');
@ -133,7 +136,7 @@ switch ($att->pageparams->action) {
exit;
case mod_attendance_sessions_page_params::ACTION_DELETE_SELECTED:
$confirm = optional_param('confirm', null, PARAM_INT);
$message = get_string('deletecheckfull', '', get_string('session', 'attendance'));
$message = get_string('deletecheckfull', 'attendance', get_string('sessions', 'attendance'));
if (isset($confirm) && confirm_sesskey()) {
$sessionsids = required_param('sessionsids', PARAM_ALPHANUMEXT);

88
settings.php

@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
require_once(dirname(__FILE__).'/lib.php');
require_once(dirname(__FILE__).'/locallib.php');
$tabmenu = attendance_print_settings_tabs();
$settings->add(new admin_setting_heading('attendance_header', '', $tabmenu));
@ -56,6 +57,42 @@ if ($ADMIN->fulltree) {
get_string('studentscanmarksessiontimeend', 'attendance'),
get_string('studentscanmarksessiontimeend_desc', 'attendance'), '60', PARAM_INT));
$settings->add(new admin_setting_configcheckbox('attendance/subnetactivitylevel',
get_string('subnetactivitylevel', 'attendance'),
get_string('subnetactivitylevel_desc', 'attendance'), 1));
$options = array(
ATT_VIEW_ALL => get_string('all', 'attendance'),
ATT_VIEW_ALLPAST => get_string('allpast', 'attendance'),
ATT_VIEW_NOTPRESENT => get_string('lowgrade', 'attendance'),
ATT_VIEW_MONTHS => get_string('months', 'attendance'),
ATT_VIEW_WEEKS => get_string('weeks', 'attendance'),
ATT_VIEW_DAYS => get_string('days', 'attendance')
);
$settings->add(new admin_setting_configselect('attendance/defaultview',
get_string('defaultview', 'attendance'),
get_string('defaultview_desc', 'attendance'), ATT_VIEW_WEEKS, $options));
$settings->add(new admin_setting_configcheckbox('attendance/multisessionexpanded',
get_string('multisessionexpanded', 'attendance'),
get_string('multisessionexpanded_desc', 'attendance'), 0));
$settings->add(new admin_setting_configcheckbox('attendance/showsessiondescriptiononreport',
get_string('showsessiondescriptiononreport', 'attendance'),
get_string('showsessiondescriptiononreport_desc', 'attendance'), 0));
$settings->add(new admin_setting_configcheckbox('attendance/studentrecordingexpanded',
get_string('studentrecordingexpanded', 'attendance'),
get_string('studentrecordingexpanded_desc', 'attendance'), 1));
$settings->add(new admin_setting_configcheckbox('attendance/enablecalendar',
get_string('enablecalendar', 'attendance'),
get_string('enablecalendar_desc', 'attendance'), 1));
$settings->add(new admin_setting_configcheckbox('attendance/enablewarnings',
get_string('enablewarnings', 'attendance'),
get_string('enablewarnings_desc', 'attendance'), 0));
$name = new lang_string('defaultsettings', 'mod_attendance');
$description = new lang_string('defaultsettings_help', 'mod_attendance');
@ -68,9 +105,60 @@ if ($ADMIN->fulltree) {
$description = new lang_string('defaultsessionsettings_help', 'mod_attendance');
$settings->add(new admin_setting_heading('defaultsessionsettings', $name, $description));
$settings->add(new admin_setting_configcheckbox('attendance/absenteereport_default',
get_string('includeabsentee', 'attendance'), '', 1));
$settings->add(new admin_setting_configcheckbox('attendance/studentscanmark_default',
get_string('studentscanmark', 'attendance'), '', 0));
$options = attendance_get_automarkoptions();
$settings->add(new admin_setting_configselect('attendance/automark_default',
get_string('automark', 'attendance'), '', 0, $options));
$settings->add(new admin_setting_configcheckbox('attendance/randompassword_default',
get_string('randompassword', 'attendance'), '', 0));
$settings->add(new admin_setting_configcheckbox('attendance/autoassignstatus',
get_string('autoassignstatus', 'attendance'), '', 0));
$settings->add(new admin_setting_configcheckbox('attendance/preventsharedip',
get_string('preventsharedip', 'attendance'), '', 0));
$settings->add(new admin_setting_configtext('attendance/preventsharediptime',
get_string('preventsharediptime', 'attendance'), get_string('preventsharediptime_help', 'attendance'), '', PARAM_RAW));
$name = new lang_string('defaultwarningsettings', 'mod_attendance');
$description = new lang_string('defaultwarningsettings_help', 'mod_attendance');
$settings->add(new admin_setting_heading('defaultwarningsettings', $name, $description));
$options = array();
for ($i = 1; $i <= 100; $i++) {
$options[$i] = "$i%";
}
$settings->add(new admin_setting_configselect('attendance/warningpercent',
get_string('warningpercent', 'attendance'), get_string('warningpercent_help', 'attendance'), 70, $options));
$options = array();
for ($i = 1; $i <= 50; $i++) {
$options[$i] = "$i";
}
$settings->add(new admin_setting_configselect('attendance/warnafter',
get_string('warnafter', 'attendance'), get_string('warnafter_help', 'attendance'), 5, $options));
$settings->add(new admin_setting_configselect('attendance/maxwarn',
get_string('maxwarn', 'attendance'), get_string('maxwarn_help', 'attendance'), 1, $options));
$settings->add(new admin_setting_configcheckbox('attendance/emailuser',
get_string('emailuser', 'attendance'), get_string('emailuser_help', 'attendance'), 1));
$settings->add(new admin_setting_configtext('attendance/emailsubject',
get_string('emailsubject', 'attendance'), get_string('emailsubject_help', 'attendance'),
get_string('emailsubject_default', 'attendance'), PARAM_RAW));
$settings->add(new admin_setting_configtextarea('attendance/emailcontent',
get_string('emailcontent', 'attendance'), get_string('emailcontent_help', 'attendance'),
get_string('emailcontent_default', 'attendance'), PARAM_RAW));
}

55
student_attendance_form.php

@ -46,6 +46,18 @@ class mod_attendance_student_attendance_form extends moodleform {
$attblock = $this->_customdata['attendance'];
$statuses = $attblock->get_statuses();
// Check if user has access to all statuses.
$disabledduetotime = false;
foreach ($statuses as $status) {
if ($status->studentavailability === '0') {
unset($statuses[$status->id]);
}
if (!empty($status->studentavailability) &&
time() > $attforsession->sessdate + ($status->studentavailability * 60)) {
unset($statuses[$status->id]);
$disabledduetotime = true;
}
}
$mform->addElement('hidden', 'sessid', null);
$mform->setType('sessid', PARAM_INT);
@ -70,15 +82,42 @@ class mod_attendance_student_attendance_form extends moodleform {
$mform->setType('studentpassword', PARAM_TEXT);
$mform->addRule('studentpassword', get_string('passwordrequired', 'attendance'), 'required');
}
// Create radio buttons for setting the attendance status.
$radioarray = array();
foreach ($statuses as $status) {
$radioarray[] =& $mform->createElement('radio', 'status', '', $status->description, $status->id, array());
}
// Add the radio buttons as a control with the user's name in front.
$mform->addGroup($radioarray, 'statusarray', $USER->firstname.' '.$USER->lastname.':', array(''), false);
$mform->addRule('statusarray', get_string('attendancenotset', 'attendance'), 'required', '', 'client', false, false);
if (!$attforsession->autoassignstatus) {
// Create radio buttons for setting the attendance status.
$radioarray = array();
foreach ($statuses as $status) {
$name = html_writer::span($status->description, 'statusdesc');
$radioarray[] =& $mform->createElement('radio', 'status', '', $name, $status->id, array());
}
if ($disabledduetotime) {
$warning = html_writer::span(get_string('somedisabledstatus', 'attendance'), 'somedisabledstatus');
$radioarray[] =& $mform->createElement('static', '', '', $warning);
}
// Add the radio buttons as a control with the user's name in front.
$radiogroup = $mform->addGroup($radioarray, 'statusarray', $USER->firstname.' '.$USER->lastname.':', array(''), false);
$radiogroup->setAttributes(array('class' => 'statusgroup'));
$mform->addRule('statusarray', get_string('attendancenotset', 'attendance'), 'required', '', 'client', false, false);
}
$this->add_action_buttons();
}
/**
* Validate Form.
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files) {
$errors = array();
if (!($this->_customdata['session']->autoassignstatus)) {
// Check if this status is allowed to be set.
if (empty($data['status'])) {
$errors['statusarray'] = get_string('invalidstatus', 'attendance');
}
}
return $errors;
}
}

65
styles.css

@ -186,3 +186,68 @@
.path-mod-attendance .attreport .narrow {
width: 1px;
}
.path-mod-attendance .attreport img.userpicture {
max-width: inherit;
}
.path-mod-attendance .student-password {
font-size: x-large;
text-align: center;
}
.path-mod-attendance .ungraded {
font-size: smaller;
font-style: italic;
}
#page-mod-attendance-sessions .statusgroup .statusdesc {
margin-right: 12px;
}
#page-mod-attendance-view .averageattendance {
font-weight: bold;
}
#page-mod-attendance-preferences .form-control {
width: inherit;
display: inherit;
}
@media (max-width: 767px) {
.path-mod-attendance .remarkscol {
display: none;
}
.path-mod-attendance .statusgroup .form-check-inline {
display: block;
padding-top: 10px;
padding-bottom: 10px;
}
#page-mod-attendance-view .colatt {
display: none;
}
.path-mod-attendance .attfiltercontrols,
.path-mod-attendance .attwidth {
width: 100%;
}
}
@media (max-width: 480px) {
.path-mod-attendance .desccol {
display: none;
}
.path-mod-attendance .pointscol {
display: none;
}
.path-mod-attendance .attfiltercontrols #currentdate {
display: none;
}
#page-mod-attendance-view .colsessionscompleted,
#page-mod-attendance-view .colpointssessionscompleted {
display: none;
}
}

3
take.php

@ -40,6 +40,9 @@ $pageparams->perpage = optional_param('perpage', get_config('attendance', 're
$cm = get_coursemodule_from_id('attendance', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$att = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST);
// Check this is a valid session for this attendance.
$session = $DB->get_record('attendance_sessions', array('id' => $pageparams->sessionid, 'attendanceid' => $att->id),
'*', MUST_EXIST);
require_login($course, true, $cm);
$context = context_module::instance($cm->id);

21
templates/attendance_password_icon.mustache

@ -0,0 +1,21 @@
{{!
@template attendance/attendance_password_icon
attendance_password icon.
Example context (json):
{
"title": "Help with something",
"url": "http://example.org/help",
"linktext": "",
"icon":{
"attributes": [
{"name": "src", "value": "../pix/key.svg"},
{"name": "alt", "value": "Password icon"}
]
}
}
}}
<span class="helptooltip">
<a href="{{url}}" title={{#quote}}{{title}}{{/quote}} aria-haspopup="true" target="_blank">{{#icon}}{{>core/pix_icon}}{{/icon}}{{#linktext}}{{.}}{{/linktext}}</a>
</span>

28
templates/attendance_password_icon_boost.mustache

@ -0,0 +1,28 @@
{{!
@template attendance/attendance_password_icon Boost Example.
This is an example of a template you could copy into a boost based theme to use proper popover.
At the moment we cannot specify different templates to use in plugin so we use
a cross-compatible link based pop-up for the password.
attendance_password icon.
Example context (json):
{
"title": "Help with something",
"url": "http://example.org/help",
"linktext": "",
"icon":{
"attributes": [
{"name": "class", "value": "iconhelp"},
{"name": "src", "value": "../../../pix/help.svg"},
{"name": "alt", "value": "Help icon"}
]
}
}
}}
<a class="btn btn-link p-a-0" role="button"
data-container="body" data-toggle="popover"
data-placement="{{#ltr}}left{{/ltr}}{{^ltr}}right{{/ltr}}" data-content="<span class='student-pass'>{{text}}</span> {{completedoclink}}"
data-html="true" tabindex="0" data-trigger="focus">
{{#pix}}key, attendance, {{alt}}{{/pix}}
</a>

4
tempusers.php

@ -60,8 +60,8 @@ if ($data = $mform->get_data()) {
$user->auth = 'manual';
$user->confirmed = 1;
$user->deleted = 1;
$user->email = time().'@ghost.user.de';
$user->username = time().'@ghost.user.de';
$user->email = time().'@attendance.danmarsden.com';
$user->username = time().'@attendance.danmarsden.com';
$user->idnumber = 'tempghost';
$user->mnethostid = $CFG->mnet_localhost_id;
$studentid = $DB->insert_record('user', $user);

39
tests/attendance_webservices_test.php

@ -46,6 +46,8 @@ class attendance_webservices_tests extends advanced_testcase {
/** @var stdClass */
protected $teacher;
/** @var array */
protected $students;
/** @var array */
protected $sessions;
/**
@ -85,6 +87,7 @@ class attendance_webservices_tests extends advanced_testcase {
$session->timemodified = time();
$session->statusset = 0;
$session->groupid = 0;
$session->absenteereport = 1;
// Creating two sessions.
$this->sessions[] = $session;
@ -94,9 +97,11 @@ class attendance_webservices_tests extends advanced_testcase {
/** Creating 10 students and 1 teacher. */
protected function create_and_enrol_users() {
$this->students = array();
for ($i = 0; $i < 10; $i++) {
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $this->course->id, 5); // Enrol as student.
$this->students[] = $student;
}
$this->teacher = $this->getDataGenerator()->create_user();
@ -135,6 +140,40 @@ class attendance_webservices_tests extends advanced_testcase {
$this->assertEquals(count($sessioninfo->users), 10);
}
public function test_get_session_with_group() {
$this->resetAfterTest(true);
// Create a group in our course, and add some students to it.
$group = new stdClass();
$group->courseid = $this->course->id;
$group = $this->getDataGenerator()->create_group($group);
for ($i = 0; $i < 5; $i++) {
$member = new stdClass;
$member->groupid = $group->id;
$member->userid = $this->students[$i]->id;
$this->getDataGenerator()->create_group_member($member);
}
// Add a session that's identical to the first, but with a group.
$session = $this->sessions[0];
$session->groupid = $group->id;
$session->sessdate += 3600; // Make sure it appears second in the list.
$this->attendance->add_sessions($this->sessions);
$courseswithsessions = attendance_handler::get_courses_with_today_sessions($this->teacher->id);
$course = array_pop($courseswithsessions);
$attendanceinstance = array_pop($course->attendance_instances);
$session = array_pop($attendanceinstance['today_sessions']);
$sessioninfo = attendance_handler::get_session($session->id);
$this->assertEquals($session->id, $sessioninfo->id);
$this->assertEquals($group->id, $sessioninfo->groupid);
$this->assertEquals(count($sessioninfo->users), 5);
}
public function test_update_user_status() {
$this->resetAfterTest(true);

16
tests/behat/attendance_mod.feature

@ -37,7 +37,7 @@ Feature: Teachers and Students can record session attendance
And I should see "Attendance"
And I log out
Scenario: Students can mark their own attendance
Scenario: Students can mark their own attendance and teacher can hide specific status from students.
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
@ -53,6 +53,20 @@ Feature: Teachers and Students can record session attendance
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Submit attendance"
And I should see "Excused"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Status set"
And I set the field with xpath "//*[@id='preferencesform']/table/tbody/tr[3]/td[5]/input" to "0"
And I press "Update"
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Submit attendance"
And I should not see "Excused"
And I set the field "Present" to "1"
And I press "Save changes"
And I should see "Self-recorded"

4
tests/behat/extra_features.feature

@ -181,14 +181,14 @@ Feature: Test the various new features in the attendance module
| id_sestime_endhour | 13 |
And I click on "submitbutton" "button"
When I click on "Take attendance" "link" in the "10am" "table_row"
When I click on "Take attendance" "link" in the "10AM" "table_row"
Then "Set status for all users to «Present»" "link" should exist
And "Set status for all users to «Late»" "link" should exist
And "Set status for all users to «Excused»" "link" should exist
And "Set status for all users to «Absent»" "link" should exist
When I follow "Sessions"
And I click on "Take attendance" "link" in the "12pm" "table_row"
And I click on "Take attendance" "link" in the "12PM" "table_row"
Then "Set status for all users to «Great»" "link" should exist
And "Set status for all users to «OK»" "link" should exist
And "Set status for all users to «Bad»" "link" should exist

39
tests/behat/report.feature

@ -14,6 +14,8 @@ Feature: Visiting reports
| course | user | role | timestart |
| C1 | student1 | student | ##yesterday## |
| C1 | teacher1 | editingteacher | ##yesterday## |
And the following config values are set as admin:
| enablewarnings | 1 | attendance |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
@ -27,6 +29,12 @@ Feature: Visiting reports
| id_sestime_starthour | 01 |
| id_sestime_endhour | 02 |
And I click on "id_submitbutton" "button"
And I follow "Warnings set"
And I press "Add warning"
And I set the following fields to these values:
| id_warningpercent | 84 |
| id_warnafter | 2 |
And I click on "id_submitbutton" "button"
And I log out
Scenario: Teacher takes attendance
@ -48,7 +56,7 @@ Feature: Visiting reports
And "-" "text" should exist in the "Student 1" "table_row"
When I follow "Attendance"
Then I click on "Take attendance" "link" in the "1am - 2am" "table_row"
Then I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
# Late
And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -74,7 +82,7 @@ Feature: Visiting reports
And I press "Save and display"
When I follow "Attendance"
Then I click on "Take attendance" "link" in the "1am - 2am" "table_row"
Then I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
# Excused
And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -114,7 +122,7 @@ Feature: Visiting reports
And I press "Save and display"
When I follow "Attendance"
Then I click on "Take attendance" "link" in the "1am - 2am" "table_row"
Then I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
# Excused
And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -126,10 +134,10 @@ Feature: Visiting reports
| id_sessiontype_1 | 1 |
| id_groups | Group1 |
And I click on "id_submitbutton" "button"
Then I should see "3am - 4am"
And "Group: Group1" "text" should exist in the "3am - 4am" "table_row"
Then I should see "3AM - 4AM"
And "Group: Group1" "text" should exist in the "3AM - 4AM" "table_row"
When I click on "Take attendance" "link" in the "3am - 4am" "table_row"
When I click on "Take attendance" "link" in the "3AM - 4AM" "table_row"
# Present
And I click on "td.cell.c3 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -144,7 +152,7 @@ Feature: Visiting reports
And I log out
Scenario: Teacher visit summary report
Scenario: Teacher visit summary report and absentee report
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
@ -154,7 +162,7 @@ Feature: Visiting reports
| id_grade_modgrade_point | 50 |
And I press "Save and display"
When I click on "Take attendance" "link" in the "1am - 2am" "table_row"
When I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
# Late
And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -164,9 +172,9 @@ Feature: Visiting reports
| id_sestime_starthour | 03 |
| id_sestime_endhour | 04 |
And I click on "id_submitbutton" "button"
Then I should see "3am - 4am"
Then I should see "3AM - 4AM"
When I click on "Take attendance" "link" in the "3am - 4am" "table_row"
When I click on "Take attendance" "link" in the "3AM - 4AM" "table_row"
# Present
And I click on "td.cell.c3 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -176,7 +184,7 @@ Feature: Visiting reports
| id_sestime_starthour | 05 |
| id_sestime_endhour | 06 |
And I click on "id_submitbutton" "button"
Then I should see "5am - 6am"
Then I should see "5AM - 6AM"
When I follow "Report"
And I click on "Summary" "link" in the "All" "table_row"
@ -186,6 +194,9 @@ Feature: Visiting reports
And "5 / 6" "text" should exist in the "Student 1" "table_row"
And "83.3%" "text" should exist in the "Student 1" "table_row"
And I follow "Absentee report"
And I should see "Student 1"
And I log out
Scenario: Student visit user report
@ -198,7 +209,7 @@ Feature: Visiting reports
| id_grade_modgrade_point | 50 |
And I press "Save and display"
When I click on "Take attendance" "link" in the "1am - 2am" "table_row"
When I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
# Late
And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -209,7 +220,7 @@ Feature: Visiting reports
| id_sestime_endhour | 04 |
And I click on "id_submitbutton" "button"
When I click on "Take attendance" "link" in the "3am - 4am" "table_row"
When I click on "Take attendance" "link" in the "3AM - 4AM" "table_row"
# Present
And I click on "td.cell.c3 input" "css_element" in the "Student 1" "table_row"
And I press "Save attendance"
@ -225,7 +236,7 @@ Feature: Visiting reports
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "All"
And I click on "All" "link" in the ".attfiltercontrols" "css_element"
Then "2" "text" should exist in the "Taken sessions" "table_row"
And "3 / 4" "text" should exist in the "Points over taken sessions:" "table_row"

109
update_form.php

@ -49,6 +49,7 @@ class mod_attendance_update_form extends moodleform {
if (!$sess = $DB->get_record('attendance_sessions', array('id' => $sessionid) )) {
error('No such session in this course');
}
$attendancesubnet = $DB->get_field('attendance', 'subnet', array('id' => $sess->attendanceid));
$defopts = array('maxfiles' => EDITOR_UNLIMITED_FILES, 'noclean' => true, 'context' => $modcontext);
$sess = file_prepare_standard_editor($sess, 'description', $defopts, $modcontext, 'mod_attendance', 'session', $sess->id);
@ -66,7 +67,19 @@ class mod_attendance_update_form extends moodleform {
'endhour' => $endhour, 'endminute' => $endminute),
'sdescription' => $sess->description_editor,
'studentscanmark' => $sess->studentscanmark,
'studentpassword' => $sess->studentpassword);
'studentpassword' => $sess->studentpassword,
'autoassignstatus' => $sess->autoassignstatus,
'subnet' => $sess->subnet,
'automark' => $sess->automark,
'absenteereport' => $sess->absenteereport,
'automarkcompleted' => 0,
'preventsharedip' => $sess->preventsharedip,
'preventsharediptime' => $sess->preventsharediptime);
if ($sess->subnet == $attendancesubnet) {
$data['usedefaultsubnet'] = 1;
} else {
$data['usedefaultsubnet'] = 0;
}
$mform->addElement('header', 'general', get_string('changesession', 'attendance'));
@ -86,28 +99,93 @@ class mod_attendance_update_form extends moodleform {
// Show which status set is in use.
$maxstatusset = attendance_get_max_statusset($this->_customdata['att']->id);
if ($maxstatusset > 0) {
$mform->addElement('static', 'statusset', get_string('usestatusset', 'mod_attendance'),
$mform->addElement('static', 'statussetstring', get_string('usestatusset', 'mod_attendance'),
attendance_get_setname($this->_customdata['att']->id, $sess->statusset));
}
$mform->addElement('hidden', 'statusset', $sess->statusset);
$mform->setType('statusset', PARAM_INT);
$mform->addElement('editor', 'sdescription', get_string('description', 'attendance'),
array('rows' => 1, 'columns' => 80), $defopts);
$mform->setType('sdescription', PARAM_RAW);
// If warnings allow selector for reporting.
if (!empty(get_config('attendance', 'enablewarnings'))) {
$mform->addElement('checkbox', 'absenteereport', '', get_string('includeabsentee', 'attendance'));
$mform->addHelpButton('absenteereport', 'includeabsentee', 'attendance');
}
// Students can mark own attendance.
if (!empty(get_config('attendance', 'studentscanmark'))) {
$mform->addElement('header', 'headerstudentmarking', get_string('studentmarking', 'attendance'), true);
$mform->setExpanded('headerstudentmarking');
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$options2 = attendance_get_automarkoptions();
$mform->addElement('select', 'automark', get_string('automark', 'attendance'), $options2);
$mform->setType('automark', PARAM_INT);
$mform->addHelpButton('automark', 'automark', 'attendance');
$mform->disabledif('automark', 'studentscanmark', 'notchecked');
$mform->addElement('text', 'studentpassword', get_string('studentpassword', 'attendance'));
$mform->setType('studentpassword', PARAM_TEXT);
$mform->addHelpButton('studentpassword', 'passwordgrp', 'attendance');
$mform->disabledif('studentpassword', 'studentscanmark', 'notchecked');
$mform->disabledif('studentpassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->disabledif('randompassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->addElement('checkbox', 'autoassignstatus', '', get_string('autoassignstatus', 'attendance'));
$mform->addHelpButton('autoassignstatus', 'autoassignstatus', 'attendance');
$mform->disabledif('autoassignstatus', 'studentscanmark', 'notchecked');
$mgroup = array();
$mgroup[] = & $mform->createElement('text', 'subnet', get_string('requiresubnet', 'attendance'));
$mform->setDefault('subnet', $this->_customdata['att']->subnet);
$mgroup[] = & $mform->createElement('checkbox', 'usedefaultsubnet', get_string('usedefaultsubnet', 'attendance'));
$mform->setDefault('usedefaultsubnet', 1);
$mform->setType('subnet', PARAM_TEXT);
$mform->addGroup($mgroup, 'subnetgrp', get_string('requiresubnet', 'attendance'), array(' '), false);
$mform->setAdvanced('subnetgrp');
$mform->addHelpButton('subnetgrp', 'requiresubnet', 'attendance');
$mform->disabledif('usedefaultsubnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'usedefaultsubnet', 'checked');
$mform->addElement('hidden', 'automarkcompleted', '0');
$mform->settype('automarkcompleted', PARAM_INT);
$mgroup3 = array();
$mgroup3[] = & $mform->createElement('checkbox', 'preventsharedip', '');
$mgroup3[] = & $mform->createElement('text', 'preventsharediptime',
get_string('preventsharediptime', 'attendance'), '', 'test');
$mgroup3[] = & $mform->createElement('static', 'preventsharediptimedesc', '',
get_string('preventsharedipminutes', 'attendance'));
$mform->addGroup($mgroup3, 'preventsharedgroup',
get_string('preventsharedip', 'attendance'), array(' '), false);
$mform->addHelpButton('preventsharedgroup', 'preventsharedip', 'attendance');
$mform->setAdvanced('preventsharedgroup');
$mform->setType('preventsharediptime', PARAM_INT);
$mform->disabledif('preventsharedgroup', 'studentscanmark', 'notchecked');
$mform->disabledif('preventsharedip', 'studentscanmark', 'notchecked');
$mform->disabledif('preventsharediptime', 'studentscanmark', 'notchecked');
$mform->disabledIf('preventsharediptime', 'preventsharedip', 'notchecked');
} else {
$mform->addElement('hidden', 'studentscanmark', '0');
$mform->settype('studentscanmark', PARAM_INT);
$mform->addElement('hidden', 'subnet', '0');
$mform->settype('subnet', PARAM_TEXT);
$mform->addElement('hidden', 'automark', '0');
$mform->settype('automark', PARAM_INT);
$mform->addElement('hidden', 'automarkcompleted', '0');
$mform->settype('automarkcompleted', PARAM_INT);
$mform->addElement('hidden', 'autoassignstatus', '0');
$mform->setType('autoassignstatus', PARAM_INT);
}
$mform->addElement('editor', 'sdescription', get_string('description', 'attendance'),
array('rows' => 1, 'columns' => 80), $defopts);
$mform->setType('sdescription', PARAM_RAW);
$mform->setDefaults($data);
$this->add_action_buttons(true);
@ -119,6 +197,7 @@ class mod_attendance_update_form extends moodleform {
* @param array $files
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$sesstarttime = $data['sestime']['starthour'] * HOURSECS + $data['sestime']['startminute'] * MINSECS;
@ -127,6 +206,24 @@ class mod_attendance_update_form extends moodleform {
$errors['sestime'] = get_string('invalidsessionendtime', 'attendance');
}
if (!empty($data['studentscanmark']) && $data['automark'] == ATTENDANCE_AUTOMARK_CLOSE) {
$cm = $this->_customdata['cm'];
// Check that the selected statusset has a status to use when unmarked.
$sql = 'SELECT id
FROM {attendance_statuses}
WHERE deleted = 0 AND (attendanceid = 0 or attendanceid = ?)
AND setnumber = ? AND setunmarked = 1';
$params = array($cm->instance, $data['statusset']);
if (!$DB->record_exists_sql($sql, $params)) {
$errors['automark'] = get_string('noabsentstatusset', 'attendance');
}
}
if (!empty($data['studentscanmark']) && !empty($data['preventsharedip']) &&
empty($data['preventsharediptime'])) {
$errors['preventsharedgroup'] = get_string('iptimemissing', 'attendance');
}
return $errors;
}
}

4
version.php

@ -23,9 +23,9 @@
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017050200;
$plugin->version = 2017050227;
$plugin->requires = 2017042100;
$plugin->release = '3.3.4';
$plugin->release = '3.3.15';
$plugin->maturity = MATURITY_STABLE;
$plugin->cron = 0;
$plugin->component = 'mod_attendance';

203
warnings.php

@ -0,0 +1,203 @@
<?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/>.
/**
* Allows default warnings to be modified.
*
* @package mod_attendance
* @copyright 2017 Dan Marsden http://danmarsden.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->dirroot.'/mod/attendance/lib.php');
require_once($CFG->dirroot.'/mod/attendance/locallib.php');
$action = optional_param('action', '', PARAM_ALPHA);
$notid = optional_param('notid', 0, PARAM_INT);
$id = optional_param('id', 0, PARAM_INT);
$url = new moodle_url('/mod/attendance/warnings.php');
// This page is used for configuring default set and for configuring attendance level set.
if (empty($id)) {
// This is the default status set - show appropriate admin stuff and check admin permissions.
admin_externalpage_setup('managemodules');
$output = $PAGE->get_renderer('mod_attendance');
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('defaultwarnings', 'mod_attendance'));
$tabmenu = attendance_print_settings_tabs('defaultwarnings');
echo $tabmenu;
} else {
// This is an attendance level config.
$cm = get_coursemodule_from_id('attendance', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$att = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST);
require_login($course, false, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/attendance:changepreferences', $context);
$att = new mod_attendance_structure($att, $cm, $course, $PAGE->context);
$PAGE->set_url($url);
$PAGE->set_title($course->shortname. ": ".$att->name);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add($att->name);
$output = $PAGE->get_renderer('mod_attendance');
$tabs = new attendance_tabs($att, attendance_tabs::TAB_WARNINGS);
echo $output->header();
echo $output->heading(get_string('attendanceforthecourse', 'attendance').' :: ' .format_string($course->fullname));
echo $output->render($tabs);
}
$mform = new mod_attendance_add_warning_form($url, array('notid' => $notid, 'id' => $id));
if ($data = $mform->get_data()) {
if (empty($data->notid)) {
// Insert new record.
$notify = new stdClass();
if (empty($id)) {
$notify->idnumber = 0;
} else {
$notify->idnumber = $att->id;
}
$notify->warningpercent = $data->warningpercent;
$notify->warnafter = $data->warnafter;
$notify->maxwarn = $data->maxwarn;
$notify->emailuser = empty($data->emailuser) ? 0 : $data->emailuser;
$notify->emailsubject = $data->emailsubject;
$notify->emailcontent = $data->emailcontent['text'];
$notify->emailcontentformat = $data->emailcontent['format'];
$notify->thirdpartyemails = '';
if (!empty($data->thirdpartyemails)) {
$notify->thirdpartyemails = implode(',', $data->thirdpartyemails);
}
$existingrecord = $DB->record_exists('attendance_warning', array('idnumber' => $notify->idnumber,
'warningpercent' => $notify->warningpercent,
'warnafter' => $notify->warnafter));
if (empty($existingrecord)) {
$DB->insert_record('attendance_warning', $notify);
echo $OUTPUT->notification(get_string('warningupdated', 'mod_attendance'), 'success');
} else {
echo $OUTPUT->notification(get_string('warningfailed', 'mod_attendance'), 'warning');
}
} else {
$notify = $DB->get_record('attendance_warning', array('id' => $data->notid));
if (!empty($id) && $data->idnumber != $att->id) {
// Someone is trying to update a record for a different attendance.
print_error('invalidcoursemodule');
} else {
$notify = new stdClass();
$notify->id = $data->notid;
$notify->idnumber = $data->idnumber;
$notify->warningpercent = $data->warningpercent;
$notify->warnafter = $data->warnafter;
$notify->maxwarn = $data->maxwarn;
$notify->emailuser = empty($data->emailuser) ? 0 : $data->emailuser;
$notify->emailsubject = $data->emailsubject;
$notify->emailcontentformat = $data->emailcontent['format'];
$notify->emailcontent = $data->emailcontent['text'];
$notify->thirdpartyemails = '';
if (!empty($data->thirdpartyemails)) {
$notify->thirdpartyemails = implode(',', $data->thirdpartyemails);
}
$existingrecord = $DB->get_record('attendance_warning', array('idnumber' => $notify->idnumber,
'warningpercent' => $notify->warningpercent, 'warnafter' => $notify->warnafter));
if (empty($existingrecord) || $existingrecord->id == $notify->id) {
$DB->update_record('attendance_warning', $notify);
echo $OUTPUT->notification(get_string('warningupdated', 'mod_attendance'), 'success');
} else {
echo $OUTPUT->notification(get_string('warningfailed', 'mod_attendance'), 'error');
}
}
}
}
if ($action == 'delete' && !empty($notid)) {
if (!optional_param('confirm', false, PARAM_BOOL)) {
$cancelurl = $url;
$url->params(array('action' => 'delete', 'notid' => $notid, 'sesskey' => sesskey(), 'confirm' => true, 'id' => $id));
echo $OUTPUT->confirm(get_string('deletewarningconfirm', 'mod_attendance'), $url, $cancelurl);
echo $OUTPUT->footer();
exit;
} else {
require_sesskey();
$params = array('id' => $notid);
if (!empty($att)) {
// Add id/level to array.
$params['idnumber'] = $att->id;
}
$DB->delete_records('attendance_warning', $params);
echo $OUTPUT->notification(get_string('warningdeleted', 'mod_attendance'), 'success');
}
}
if ($action == 'update' && !empty($notid)) {
$existing = $DB->get_record('attendance_warning', array('id' => $notid));
$content = $existing->emailcontent;
$existing->emailcontent = array();
$existing->emailcontent['text'] = $content;
$existing->emailcontent['format'] = $existing->emailcontentformat;
$existing->notid = $existing->id;
$existing->id = $id;
$mform->set_data($existing);
$mform->display();
} else if ($action == 'add' && confirm_sesskey()) {
$mform->display();
} else {
if (empty($id)) {
$warningdesc = get_string('warningdesc', 'mod_attendance');
$idnumber = 0;
} else {
$warningdesc = get_string('warningdesc_course', 'mod_attendance');
$idnumber = $att->id;
}
echo $OUTPUT->box($warningdesc, 'generalbox attendancedesc', 'notice');
$existingnotifications = $DB->get_records('attendance_warning',
array('idnumber' => $idnumber),
'warningpercent');
if (!empty($existingnotifications)) {
$table = new html_table();
$table->head = array(get_string('warningthreshold', 'mod_attendance'),
get_string('numsessions', 'mod_attendance'),
get_string('emailsubject', 'mod_attendance'),
'');
foreach ($existingnotifications as $notification) {
$url->params(array('action' => 'delete', 'notid' => $notification->id, 'id' => $id));
$actionbuttons = $OUTPUT->action_icon($url, new pix_icon('t/delete',
get_string('delete', 'attendance')), null, null);
$url->params(array('action' => 'update', 'notid' => $notification->id, 'id' => $id));
$actionbuttons .= $OUTPUT->action_icon($url, new pix_icon('t/edit',
get_string('update', 'attendance')), null, null);
$table->data[] = array($notification->warningpercent, $notification->warnafter,
$notification->emailsubject, $actionbuttons);
}
echo html_writer::table($table);
}
$addurl = new moodle_url('/mod/attendance/warnings.php', array('action' => 'add', 'id' => $id));
echo $OUTPUT->single_button($addurl, get_string('addwarning', 'mod_attendance'));
}
echo $OUTPUT->footer();

5
yui/build/moodle-mod_attendance-groupfilter/moodle-mod_attendance-groupfilter-debug.js

@ -1,6 +1,7 @@
YUI.add('moodle-mod_attendance-groupfilter', function (Y, NAME) {
/*global M*/
/* global M */
// eslint-disable-next-line camelcase
M.mod_attendance = M.mod_attendance || {};
M.mod_attendance.groupfilter = {
groupmappings: null,
@ -15,7 +16,7 @@ M.mod_attendance.groupfilter = {
/**
* Update the user list with those found in the selected group.
*/
update_user_list: function() {
update_user_list: function() { // eslint-disable-line camelcase
"use strict";
var groupid, userlist, users, userid, opt;

5
yui/build/moodle-mod_attendance-groupfilter/moodle-mod_attendance-groupfilter.js

@ -1,6 +1,7 @@
YUI.add('moodle-mod_attendance-groupfilter', function (Y, NAME) {
/*global M*/
/* global M */
// eslint-disable-next-line camelcase
M.mod_attendance = M.mod_attendance || {};
M.mod_attendance.groupfilter = {
groupmappings: null,
@ -15,7 +16,7 @@ M.mod_attendance.groupfilter = {
/**
* Update the user list with those found in the selected group.
*/
update_user_list: function() {
update_user_list: function() { // eslint-disable-line camelcase
"use strict";
var groupid, userlist, users, userid, opt;

5
yui/src/groupfilter/js/groupfilter.js

@ -1,4 +1,5 @@
/*global M*/
/* global M */
// eslint-disable-next-line camelcase
M.mod_attendance = M.mod_attendance || {};
M.mod_attendance.groupfilter = {
groupmappings: null,
@ -13,7 +14,7 @@ M.mod_attendance.groupfilter = {
/**
* Update the user list with those found in the selected group.
*/
update_user_list: function() {
update_user_list: function() { // eslint-disable-line camelcase
"use strict";
var groupid, userlist, users, userid, opt;

Loading…
Cancel
Save