Compare commits

...

72 Commits

Author SHA1 Message Date
Morgan Harris dbe59d1bb7 Allow group sessions in webservices (#364) 6 years ago
Dan Marsden 13bbd26bda Fix #363 typo in key defintion - causes failure in Oracle upgrades. 6 years ago
Dan Marsden 6eb87c01f7 Fix #350 - update description in calendar even on session update. 6 years ago
Dan Marsden 9238d3d5d0 Fix #332 tidy up delete message in attendance plugin. 6 years ago
Dan Marsden e69110fa14 Fix #340 Check duration of session when checking if a student enrolled before a session. 6 years ago
Dan Marsden 2cb6ca75fb
coding guideline - remove extra carriage return 6 years ago
Dan Marsden aa028de126
remove incorrect space. 6 years ago
Dan Marsden 1ffcc8a6ec Fix #359 - increase number of allowed status items in the logs. 6 years ago
Dan Marsden 9169ac515a Fix #358 check index exists before dropping. 6 years ago
Tõnis Tartes 2183c68d3b ISSUE-336 - Viewing single session table total counts in also users w… (#337) 6 years ago
Dan Marsden 56aaba5e99 Don't save values if not numeric. 6 years ago
Dan Marsden 204d9d7319 Use correct travis branch. 6 years ago
Dan Marsden 907fbd676a Fix #328 correct validation check for statusset and not marked status. 6 years ago
Dan Marsden 2382b1c9fd Fix some coding guideline issues. 7 years ago
Dan Marsden c270e0f9b4 Fixes #316 provide option to prevent sharing ip for this session without a timeframe. 7 years ago
Dan Marsden f8830ab322 Fix for session add/update when studentscanmark is empty but automark set to close 7 years ago
Dan Marsden ca8d80302e Fix #317 typo in html entity, and entities not rendering inside select options. 7 years ago
Neill Magill c1f2008c17 Cog menu on teacher page under Boost based themes 7 years ago
Dan Marsden 74af6ba2cc Prevent text_to_html from adding divs to email subject. 7 years ago
Dan Marsden 90b0d53afb Fixes #302 make sure user lang is used when sending mail. 7 years ago
Dan Marsden 45e7a28fc2 Feature: Prevent students from sharing device while self-marking. 7 years ago
Dan Marsden 07ff5091df Add class to set status for all users row. 7 years ago
Dan Marsden 29efe3ed57 Check if userto is empty (extra comma in thirdpartyusers field) 7 years ago
Dan Marsden a41e6b5d04 Fix issue when checking if a status set has an absent flag when updating a session. 7 years ago
Dan Marsden abf205abfa bump version. 7 years ago
Dan Marsden 72db494ea8 add setting to allow sessiont description to show in report. 7 years ago
Dan Marsden dcbfd04c4d Show warning if teacher using mark on close and no status available. 7 years ago
Dan Marsden c38993ea46 Fix import column matching. 7 years ago
Dan Marsden edee40553c Remove deprecated calls to notify constants. 7 years ago
Dan Marsden e25513b1b2 remove spacing. 7 years ago
Dan Marsden 643a20778c Add missing string and fix upgrade previous value. 7 years ago
Nick Phillips 1f71e7f740 Make display of user 'extrafields' optional in report. (#294) 7 years ago
Nick Phillips 9529211734 Fix userpic max-width in attreport. Some themes (e.g. adaptable) (#295) 7 years ago
Dan Marsden f8df4ad29d include description format when creating event. 7 years ago
Nick Phillips 9e4916aad7 Add description to event, and group to event name if present. (#296) 7 years ago
Dan Marsden ae42f9f5c7 remove reference to user.de domain. 7 years ago
Dan Marsden 7f81249d9a fix codechecker issue. 7 years ago
Dan Marsden cd03de4b72 codechecker fix. 7 years ago
Dan Marsden 2cc321cd2c codechecker stuff. 7 years ago
Dan Marsden 78270c41ae fix phpdocs. 7 years ago
Dan Marsden 05d701e832 bump version for plugins db release. 7 years ago
Dan Marsden 3c58ce2bb7 Fix link on subnet error. 7 years ago
Dan Marsden e6fd12779a Revert "Fix #290 cross-db compatible method to only include sessions with absenteereport set." 7 years ago
Dan Marsden 3c6068aa8f Revert "Fix #290 correct $where statement." 7 years ago
Dan Marsden 430e51bb20 Fix #292 studentpassword undefined warning 7 years ago
Dan Marsden 8e52d83c20 Split out graded and ungraded sessions on students all courses list. 7 years ago
Dan Marsden 322cd6bb68 Fix #290 correct $where statement. 7 years ago
Dan Marsden d856edd627 Fix #290 cross-db compatible method to only include sessions with absenteereport set. 7 years ago
Dan Marsden e9a1639d1d Fixes #289 sorted grid layout select all fix. thank to Andrew Debevec for report. 7 years ago
Dan Marsden db93178214 Hide option to automark if standard log store disabled. 7 years ago
Dan Marsden f7f4ee7e52 Fix issue that occurs when updating an individual warning. 7 years ago
Dan Marsden 4b68c74140 Put magic number into constant. 7 years ago
Dan Marsden 0cc5332c5b Prevent sessions from being added to attendance activites in recycle bin. 7 years ago
Dan Marsden 2a9f3a1ea7 Fix #287 prevent restore from setting takenby when no userdata restored. 7 years ago
Dan Marsden 1fab8e9691 Exclude ungraded attendance activities from student all courses average. 7 years ago
Dan Marsden d78f96b8c8 set hideif rules correctly on full group elements. 7 years ago
Alicecj b220a27ff1 mod/attendance: Replace disabledif with hideif 7 years ago
Dan Marsden 2ddfd29843 Remove repeat fields - not implemented yet. 7 years ago
Dan Marsden 10f85fffbd Add missing fields to csv and set defaults when not mapped. 7 years ago
Dan Marsden 8d88758039 Correct list of groups for each session. 7 years ago
Dan Marsden 06904c98d0 Fix #281 Copy Moodle 3.3 messaging tool into attendance to allow multi-user messaging. 7 years ago
Dan Marsden 394cc3ded7 remove old comment. 7 years ago
Dan Marsden 82e60a7719 Improve auto-marking when no password. 7 years ago
James Voong 515f27d08c Added ability to automatically mark all students with a status 7 years ago
Dan Marsden 9567221f9c include absenteereport field in externalib structure. 7 years ago
Dan Marsden 38a6542a4f Add absenteereport to class when adding session. 7 years ago
Dan Marsden 6aee345330 Add missing backup field, and new field in tests. 7 years ago
Dan Marsden 7614a9690a Add ability to exlude sessions from absentee report calculations. 7 years ago
Dan Marsden 3039f82e80 fix travis config. 7 years ago
Dan Marsden 8de82ae5ab Add output buffering level for progress bar. 7 years ago
Chris Wharton 1ed5a2d245 Allow bulk importing of attendance sessions 7 years ago
Dan Marsden fbf4eea35b Flag 3.4 branch as stable release. 7 years ago
  1. 7
      .travis.yml
  2. 92
      add_form.php
  3. 37
      attendance.php
  4. 9
      backup/moodle2/backup_attendance_stepslib.php
  5. 12
      backup/moodle2/restore_attendance_stepslib.php
  6. 4
      calendar.js
  7. 2
      classes/add_warning_form.php
  8. 3
      classes/attendance_webservices_handler.php
  9. 7
      classes/calendar_helpers.php
  10. 93
      classes/event/session_ip_shared.php
  11. 92
      classes/event/sessions_imported.php
  12. 86
      classes/form/import/sessions.php
  13. 73
      classes/form/import/sessions_confirm.php
  14. 493
      classes/import/sessions.php
  15. 6
      classes/notifyqueue.php
  16. 6
      classes/report_page_params.php
  17. 53
      classes/structure.php
  18. 12
      classes/task/notify.php
  19. 12
      db/install.xml
  20. 79
      db/upgrade.php
  21. 4
      defaultstatus.php
  22. 1
      export.php
  23. 4
      externallib.php
  24. 94
      import/sessions.php
  25. 50
      lang/en/attendance.php
  26. 3
      lib.php
  27. 143
      locallib.php
  28. 1
      manage.php
  29. 59
      message.html
  30. 181
      messageselect.php
  31. 3
      preferences.php
  32. 108
      renderer.php
  33. 4
      renderhelpers.php
  34. 2
      report.php
  35. 8
      sessions.php
  36. 23
      settings.php
  37. 38
      student_attendance_form.php
  38. 9
      styles.css
  39. 5
      tempusers.php
  40. 39
      tests/attendance_webservices_test.php
  41. 14
      tests/behat/report.feature
  42. 71
      update_form.php
  43. 8
      version.php
  44. 2
      warnings.php

7
.travis.yml

@ -1,6 +1,6 @@
language: php
sudo: false
sudo: true
addons:
firefox: "47.0.1"
@ -20,14 +20,15 @@ php:
- 7.2
env:
global:
- MOODLE_BRANCH=master
- MOODLE_BRANCH=MOODLE_34_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 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"

92
add_form.php

@ -125,6 +125,17 @@ class mod_attendance_add_form extends moodleform {
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)) {
@ -175,15 +186,12 @@ class mod_attendance_add_form extends moodleform {
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$options = array(
ATTENDANCE_AUTOMARK_DISABLED => get_string('noautomark', 'attendance'),
ATTENDANCE_AUTOMARK_ALL => get_string('automarkall', 'attendance'),
ATTENDANCE_AUTOMARK_CLOSE => get_string('automarkclose', '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->hideif('automark', 'studentscanmark', 'notchecked');
$mform->setDefault('automark', $this->_customdata['att']->automark);
$mgroup = array();
@ -193,13 +201,19 @@ class mod_attendance_add_form extends moodleform {
$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->hideif('passwordgrp', 'studentscanmark', 'notchecked');
$mform->hideif('studentpassword', 'randompassword', 'checked');
$mform->hideif('passwordgrp', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->addElement('checkbox', 'autoassignstatus', '', get_string('autoassignstatus', 'attendance'));
$mform->addHelpButton('autoassignstatus', 'autoassignstatus', 'attendance');
$mform->hideif('autoassignstatus', 'studentscanmark', 'notchecked');
if (isset($pluginconfig->autoassignstatus)) {
$mform->setDefault('autoassignstatus', $pluginconfig->autoassignstatus);
}
if (isset($pluginconfig->studentscanmark_default)) {
$mform->setDefault('studentscanmark', $pluginconfig->studentscanmark_default);
}
@ -225,16 +239,47 @@ class mod_attendance_add_form extends moodleform {
$mform->setAdvanced('subnetgrp');
$mform->addHelpButton('subnetgrp', 'requiresubnet', 'attendance');
$mform->disabledif('usedefaultsubnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'usedefaultsubnet', 'checked');
$mform->hideif('subnetgrp', 'studentscanmark', 'notchecked');
$mform->hideif('subnet', 'usedefaultsubnet', 'checked');
$mgroup3 = array();
$options = attendance_get_sharedipoptions();
$mgroup3[] = & $mform->createElement('select', 'preventsharedip',
get_string('preventsharedip', 'attendance'), $options);
$mgroup3[] = & $mform->createElement('text', 'preventsharediptime',
get_string('preventsharediptime', 'attendance'), '', 'test');
$mform->addGroup($mgroup3, 'preventsharedgroup', get_string('preventsharedip', 'attendance'), array(' '), false);
$mform->addHelpButton('preventsharedgroup', 'preventsharedip', 'attendance');
$mform->setAdvanced('preventsharedgroup');
$mform->setType('preventsharedip', PARAM_INT);
$mform->setType('preventsharediptime', PARAM_INT);
$mform->hideif('preventsharedgroup', 'studentscanmark', 'notchecked');
$mform->hideIf('preventsharediptime', 'preventsharedip', 'noteq', ATTENDANCE_SHAREDIP_MINUTES);
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'));
@ -246,6 +291,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;
@ -283,6 +329,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;
}

37
attendance.php

@ -47,7 +47,8 @@ if (!attendance_can_student_mark($attforsession)) {
// Check if subnet is set and if the user is in the allowed range.
if (!empty($attforsession->subnet) && !address_in_subnet(getremoteaddr(), $attforsession->subnet)) {
notice(get_string('subnetwrong', 'attendance'));
$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));
@ -75,16 +96,23 @@ if ($mform->is_cancelled()) {
$url = new moodle_url('/mod/attendance/attendance.php', array('sessid' => $id, 'sesskey' => sesskey()));
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)) {
$success = $att->take_from_student($fromform);
$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();

9
backup/moodle2/backup_attendance_stepslib.php

@ -44,7 +44,7 @@ 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', 'intro', 'introformat', 'grade', 'showsessiondetails', 'sessiondetailspos', 'subnet'));
'name', 'intro', 'introformat', 'grade', 'showextrauserdetails', 'showsessiondetails', 'sessiondetailspos', 'subnet'));
$statuses = new backup_nested_element('statuses');
$status = new backup_nested_element('status', array('id'), array(
@ -56,9 +56,10 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
$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',
'subnet', 'automark', 'automarkcompleted', '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');

12
backup/moodle2/restore_attendance_stepslib.php

@ -126,17 +126,25 @@ 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);

4
calendar.js

@ -68,9 +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.
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.
hide_blank_weeks: true // Enable, to demonstrate how we handle changing height, using changeContent.
});
calendar.cfg.setProperty("start_weekday", M.attendance.cal_start_weekday);

2
classes/add_warning_form.php

@ -52,7 +52,7 @@ class mod_attendance_add_warning_form extends moodleform {
$mform->setDefault('warningpercent', $config->warningpercent);
$options = array();
for ($i = 1; $i <= 50; $i++) {
for ($i = 1; $i <= ATTENDANCE_MAXWARNAFTER; $i++) {
$options[$i] = "$i";
}
$mform->addElement('select', 'warnafter', get_string('warnafter', 'mod_attendance'), $options);

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),

7
classes/calendar_helpers.php

@ -52,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;
@ -108,6 +114,7 @@ function attendance_update_calendar_event($caleventid, $timeduration, $timestart
$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'
);
}
}

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)) {

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;
}

53
classes/structure.php

@ -76,6 +76,9 @@ class mod_attendance_structure {
/** @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;
@ -125,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));
}
@ -477,6 +483,9 @@ class mod_attendance_structure {
if (!isset($sess->studentscanmark)) {
$sess->studentscanmark = 0;
}
if (!isset($sess->autoassignstatus)) {
$sess->autoassignstatus = 0;
}
if (!isset($sess->studentpassword)) {
$sess->studentpassword = '';
}
@ -484,6 +493,14 @@ class mod_attendance_structure {
$sess->subnet = '';
}
if (!isset($sess->preventsharedip)) {
$sess->preventsharedip = 0;
}
if (!isset($sess->preventsharediptime)) {
$sess->preventsharediptime = '';
}
$event->add_record_snapshot('attendance_sessions', $sess);
$event->trigger();
}
@ -515,15 +532,24 @@ 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 {
@ -533,10 +559,18 @@ class mod_attendance_structure {
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);
if (empty($sess->caleventid)) {
// This shouldn't really happen, but just in case to prevent fatal error.
attendance_create_calendar_event($sess);
@ -575,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)) {
@ -1001,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
@ -1016,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
@ -1058,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
@ -1067,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
@ -1096,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

12
classes/task/notify.php

@ -91,11 +91,13 @@ class notify extends \core\task\scheduled_task {
$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);
email_to_user($user, $from, $record->emailsubject, $emailcontent, $emailcontent);
force_current_language($oldforcelang);
$sentnotifications[$record->userid][] = $record->aid;
$numsentusers++;
}
@ -106,6 +108,10 @@ class notify extends \core\task\scheduled_task {
$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])) {
@ -133,6 +139,7 @@ class notify extends \core\task\scheduled_task {
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');
@ -140,6 +147,7 @@ class notify extends \core\task\scheduled_task {
$emailsubject = get_string('thirdpartyemailsubject', 'attendance');
email_to_user($user, $from, $emailsubject, $emailcontent, $emailcontent);
force_current_language($oldforcelang);
$numsentthird++;
}
if (!empty($numsentthird)) {

12
db/install.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/attendance/db" VERSION="20170620" COMMENT="XMLDB file for Moodle mod/attendance"
<XMLDB PATH="mod/attendance/db" VERSION="20190125" COMMENT="XMLDB file for Moodle mod/attendance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -16,6 +16,7 @@
<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,11 +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>
@ -60,10 +65,11 @@
<FIELD NAME="sessionid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="studentid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="statusid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="link with attendance_status table"/>
<FIELD NAME="statusset" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="statusset" TYPE="char" LENGTH="1333" NOTNULL="false" SEQUENCE="false"/>
<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"/>

79
db/upgrade.php

@ -386,7 +386,7 @@ function xmldb_attendance_upgrade($oldversion=0) {
// 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'));
$table->add_key('level_id', XMLDB_KEY_UNIQUE, array('idnumber', 'warningpercent', 'warnafter'));
// Conditionally launch create table for attendance_warning.
$dbman->create_table($table);
@ -401,7 +401,7 @@ function xmldb_attendance_upgrade($oldversion=0) {
$DB->execute("DROP INDEX ". $name);
}
}
$index = new xmldb_key('level_id', XMLDB_KEY_UNIQUE, array('idnumber, warningpercent', 'warnafter'));
$index = new xmldb_key('level_id', XMLDB_KEY_UNIQUE, array('idnumber', 'warningpercent', 'warnafter'));
$dbman->add_key($table, $index);
}
// Attendance savepoint reached.
@ -426,7 +426,9 @@ function xmldb_attendance_upgrade($oldversion=0) {
$table = new xmldb_table('attendance_warning_done');
$index = new xmldb_index('notifyid_userid', XMLDB_INDEX_UNIQUE, array('notifyid', 'userid'));
$dbman->drop_index($table, $index);
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);
@ -453,5 +455,76 @@ function xmldb_attendance_upgrade($oldversion=0) {
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017082200, 'attendance');
}
if ($oldversion < 2017112001) {
$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, 2017112001, 'attendance');
}
if ($oldversion < 2017112002) {
$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, 2017112002, 'attendance');
}
if ($oldversion < 2017112005) {
$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, 2017112005, 'attendance');
}
if ($oldversion < 2017112007) {
$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, 2017112007, 'attendance');
}
if ($oldversion < 2017112008) {
// Changing precision of field statusset on table attendance_log to (1333).
$table = new xmldb_table('attendance_log');
$field = new xmldb_field('statusset', XMLDB_TYPE_CHAR, '1333', null, null, null, null, 'statusid');
// Launch change of precision for field statusset.
$dbman->change_field_precision($table, $field);
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2017112008, 'attendance');
}
return $result;
}

4
defaultstatus.php

@ -76,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'));
@ -113,7 +113,7 @@ switch ($action) {
if ($unmarkedstatus == $id) {
$setunmarked = true;
}
if (!isset($studentavailability[$id])) {
if (!isset($studentavailability[$id]) || !is_numeric($studentavailability[$id])) {
$studentavailability[$id] = 0;
}
$errors[$id] = attendance_update_status($status, $acronym[$id], $description[$id], $grade[$id],

1
export.php

@ -44,6 +44,7 @@ $att = new mod_attendance_structure($att, $cm, $course, $context);
$PAGE->set_url($att->url_export());
$PAGE->set_title($course->shortname. ": ".$att->name);
$PAGE->set_heading($course->fullname);
$PAGE->force_settings_menu(true);
$PAGE->set_cacheable(true);
$PAGE->navbar->add(get_string('export', 'attendance'));

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();

50
lang/en/attendance.php

@ -74,6 +74,7 @@ If "Set unmarked at end of session" any students who have not marked their atten
$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';
@ -92,10 +93,13 @@ $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['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.
@ -105,7 +109,11 @@ 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['defaultdisplaymode'] = 'Default display mode';
$string['defaultwarnings'] = 'Default warning set';
@ -124,6 +132,7 @@ $string['defaultview_desc'] = 'This is the default view shown to teachers on fir
$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';
@ -173,11 +182,18 @@ $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 ';
@ -187,6 +203,8 @@ $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';
@ -200,13 +218,20 @@ $string['gradebookexplanation_help'] = 'The Attendance module displays your curr
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';
@ -215,9 +240,11 @@ $string['incorrectpassword'] = 'You have entered an incorrect password and your
$string['indetail'] = 'In detail...';
$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';
@ -248,6 +275,7 @@ $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';
@ -302,7 +330,13 @@ $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['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';
@ -366,8 +400,10 @@ $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['sessionsgenerated'] = '{$a} sessions were successfully generated';
@ -375,28 +411,33 @@ $string['sessionsids'] = 'IDs of sessions: ';
$string['sessionsnotfound'] = 'There is no sessions in the selected timespan';
$string['sessionstartdate'] = 'Session start date';
$string['sessionstotal'] = 'Total number of sessions';
$string['sessiontype'] = 'Type';
$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['setperiod'] = 'Specified time in minutes to release IP';
$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['statusdeleted'] = 'Status deleted';
$string['statuses'] = 'Statuses';
@ -425,6 +466,7 @@ $string['studentscanmarksessiontime_desc'] = 'If checked students can only recor
$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.';
@ -454,6 +496,8 @@ $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['usedefaultsubnet'] = 'Use default';
@ -473,3 +517,7 @@ $string['warningthreshold'] = 'Warning threshold';
$string['week'] = 'week(s)';
$string['weeks'] = 'Weeks';
$string['youcantdo'] = 'You can\'t do anything';
$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.';

3
lib.php

@ -490,6 +490,9 @@ function attendance_print_settings_tabs($selected = 'settings') {
$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();

143
locallib.php

@ -43,6 +43,13 @@ define('ATTENDANCE_AUTOMARK_DISABLED', 0);
define('ATTENDANCE_AUTOMARK_ALL', 1);
define('ATTENDANCE_AUTOMARK_CLOSE', 2);
define('ATTENDANCE_SHAREDIP_DISABLED', 0);
define('ATTENDANCE_SHAREDIP_MINUTES', 1);
define('ATTENDANCE_SHAREDIP_FORCE', 2);
// Max number of sessions available in the warnings set form to trigger warnings.
define('ATTENDANCE_MAXWARNAFTER', 100);
/**
* Get statuses,
*
@ -92,7 +99,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.')';
@ -422,6 +429,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)) {
@ -437,6 +445,39 @@ function attendance_can_student_mark($sess) {
}
}
}
// Check if another student has marked attendance from this IP address recently.
if ($canmark && !empty($sess->preventsharedip)) {
if ($sess->preventsharedip == ATTENDANCE_SHAREDIP_MINUTES) {
$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);
} else {
// Assume ATTENDANCE_SHAREDIP_FORCED.
$sql = 'sessionid = ? AND studentid <> ? ipaddress = ?';
$params = array($sess->id, $USER->id, 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;
}
@ -534,6 +575,12 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
$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'))) {
@ -573,6 +620,8 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
$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)) {
@ -581,17 +630,27 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
$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;
@ -612,14 +671,19 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
$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 if (!empty($formdata->studentpassword)) {
@ -634,6 +698,12 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
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;
@ -765,7 +835,7 @@ function attendance_get_users_to_notify($courseids = array(), $orderby = '', $al
GROUP BY attendanceid, setnumber) stm
ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
{$joingroup}
WHERE 1 = 1 {$where}
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
@ -823,3 +893,68 @@ function attendance_template_variables($record) {
}
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;
}
/**
* Get available sharedip options.
*
* @return array
*/
function attendance_get_sharedipoptions() {
$options = array();
$options[ATTENDANCE_SHAREDIP_DISABLED] = get_string('no');
$options[ATTENDANCE_SHAREDIP_FORCE] = get_string('yes');
$options[ATTENDANCE_SHAREDIP_MINUTES] = get_string('setperiod', 'attendance');
return $options;
}

1
manage.php

@ -75,6 +75,7 @@ $PAGE->set_url($att->url_manage());
$PAGE->set_title($course->shortname. ": ".$att->name);
$PAGE->set_heading($course->fullname);
$PAGE->set_cacheable(true);
$PAGE->force_settings_menu(true);
$PAGE->navbar->add($att->name);
$output = $PAGE->get_renderer('mod_attendance');

59
message.html

@ -0,0 +1,59 @@
<form id="theform" method="post" action="messageselect.php">
<input type="hidden" name="id" value="<?php p($id) ?>" />
<input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
<input type="hidden" name="returnto" value="<?php p($returnto) ?>" />
<input type="hidden" name="deluser" value="" />
<?php echo $OUTPUT->box_start(); ?>
<table border="0" cellpadding="5">
<tr valign="top">
<td align="right"><b>
<?php print_string("messagebody"); ?>:
</b></td>
<td align="left">
<?php print_textarea(true, 15, 65, 1, 1, "messagebody", $messagebody); ?>
</td>
</tr>
<tr valign="top">
<td align="right"><label for="menuformat"><b><?php print_string("formattexttype"); ?>:</b></label></td>
<td>
<?php
print_string('formathtml');
echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
?>
</td>
</tr>
<tr><td align="center" colspan="2">
<input type="submit" name="send" value="<?php print_string('sendmessage', 'message'); ?>" />
<input type="submit" name="preview" value="<?php print_string('preview'); ?>" />
</td></tr>
</table>
<?php echo $OUTPUT->box_end(); ?>
<table align="center"><tr><th colspan="4" scope="row"><?php print_string('currentlyselectedusers'); ?></th></tr>
<?php
if (count($SESSION->emailto[$id])) {
foreach ($SESSION->emailto[$id] as $user) {
echo '<tr><td>'.fullname($user,true).'</td>';
// Check to see if we should be showing the email address.
if ($user->maildisplay == 0) { // 0 = don't display my email to anyone.
echo '<td>' . get_string('emaildisplayhidden') . '</td><td>';
} else {
echo '<td>'.$user->email.'</td><td>';
}
if (empty($user->email)) {
$error = get_string('emailempty');
}
if (!empty($error)) {
echo $OUTPUT->pix_icon('t/emailno', $error);
unset($error);
}
echo '</td><td><input type="submit" onClick="this.form.deluser.value='.$user->id.';" value="' . get_string('remove') . '" /></td></tr>';
}
}
else {
echo '<tr><td colspan="3" align="center">'.get_string('nousersyet').'</td></tr>';
}
?>
</table>
</form>

181
messageselect.php

@ -0,0 +1,181 @@
<?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/>.
/**
* Copied from Moodle 3.3 messageselect.php - allows sending messages to multiple users.
*
* @copyright 1999 Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package mod_attendance
*/
require_once('../../config.php');
require_once($CFG->dirroot.'/message/lib.php');
$id = required_param('id', PARAM_INT);
$messagebody = optional_param('messagebody', '', PARAM_CLEANHTML);
$send = optional_param('send', '', PARAM_BOOL);
$preview = optional_param('preview', '', PARAM_BOOL);
$edit = optional_param('edit', '', PARAM_BOOL);
$returnto = optional_param('returnto', '', PARAM_LOCALURL);
$format = optional_param('format', FORMAT_MOODLE, PARAM_INT);
$deluser = optional_param('deluser', 0, PARAM_INT);
$url = new moodle_url('/user/messageselect.php', array('id' => $id));
if ($messagebody !== '') {
$url->param('messagebody', $messagebody);
}
if ($send !== '') {
$url->param('send', $send);
}
if ($preview !== '') {
$url->param('preview', $preview);
}
if ($edit !== '') {
$url->param('edit', $edit);
}
if ($returnto !== '') {
$url->param('returnto', $returnto);
}
if ($format !== FORMAT_MOODLE) {
$url->param('format', $format);
}
if ($deluser !== 0) {
$url->param('deluser', $deluser);
}
$PAGE->set_url($url);
if (!$course = $DB->get_record('course', array('id' => $id))) {
print_error('invalidcourseid');
}
require_login($course);
$coursecontext = context_course::instance($id); // Course context.
$systemcontext = context_system::instance(); // SYSTEM context.
require_capability('moodle/course:bulkmessaging', $coursecontext);
if (empty($SESSION->emailto)) {
$SESSION->emailto = array();
}
if (!array_key_exists($id, $SESSION->emailto)) {
$SESSION->emailto[$id] = array();
}
if ($deluser) {
if (array_key_exists($id, $SESSION->emailto) && array_key_exists($deluser, $SESSION->emailto[$id])) {
unset($SESSION->emailto[$id][$deluser]);
}
}
if (empty($SESSION->emailselect[$id]) || $messagebody) {
$SESSION->emailselect[$id] = array('messagebody' => $messagebody);
}
$messagebody = $SESSION->emailselect[$id]['messagebody'];
$count = 0;
if ($data = data_submitted()) {
require_sesskey();
$namefields = get_all_user_name_fields(true);
foreach ($data as $k => $v) {
if (preg_match('/^(user|teacher)(\d+)$/', $k, $m)) {
if (!array_key_exists($m[2], $SESSION->emailto[$id])) {
if ($user = $DB->get_record_select('user', "id = ?", array($m[2]), 'id, '.
$namefields . ', idnumber, email, mailformat, lastaccess, lang, '.
'maildisplay, auth, suspended, deleted, emailstop, username')) {
$SESSION->emailto[$id][$m[2]] = $user;
$count++;
}
}
}
}
}
if ($course->id == SITEID) {
$strtitle = get_string('sitemessage');
$PAGE->set_pagelayout('admin');
} else {
$strtitle = get_string('coursemessage');
$PAGE->set_pagelayout('incourse');
}
$link = null;
if (has_capability('moodle/course:viewparticipants', $coursecontext) ||
has_capability('moodle/site:viewparticipants', $systemcontext)) {
$link = new moodle_url("/user/index.php", array('id' => $course->id));
}
$PAGE->navbar->add(get_string('participants'), $link);
$PAGE->navbar->add($strtitle);
$PAGE->set_title($strtitle);
$PAGE->set_heading($strtitle);
echo $OUTPUT->header();
if ($count) {
if ($count == 1) {
$heading = get_string('addedrecip', 'moodle', $count);
} else {
$heading = get_string('addedrecips', 'moodle', $count);
}
echo $OUTPUT->heading($heading);
}
if (!empty($messagebody) && !$edit && !$deluser && ($preview || $send)) {
require_sesskey();
if (count($SESSION->emailto[$id])) {
if (!empty($preview)) {
echo '<form method="post" action="messageselect.php" style="margin: 0 20px;">
<input type="hidden" name="returnto" value="'.s($returnto).'" />
<input type="hidden" name="id" value="'.$id.'" />
<input type="hidden" name="format" value="'.$format.'" />
<input type="hidden" name="sesskey" value="' . sesskey() . '" />
';
echo "<h3>".get_string('previewhtml')."</h3>";
echo "<div class=\"messagepreview\">\n".format_text($messagebody, $format)."\n</div>\n";
echo '<p align="center"><input type="submit" name="send" value="'.get_string('sendmessage', 'message').'" />'."\n";
echo '<input type="submit" name="edit" value="'.get_string('update').'" /></p>';
echo "\n</form>";
} else if (!empty($send)) {
$fails = array();
foreach ($SESSION->emailto[$id] as $user) {
if (!message_post_message($USER, $user, $messagebody, $format)) {
$user->fullname = fullname($user);
$fails[] = get_string('messagedselecteduserfailed', 'moodle', $user);
};
}
if (empty($fails)) {
echo $OUTPUT->heading(get_string('messagedselectedusers'));
unset($SESSION->emailto[$id]);
unset($SESSION->emailselect[$id]);
} else {
echo $OUTPUT->heading(get_string('messagedselectedcountusersfailed', 'moodle', count($fails)));
echo '<ul>';
foreach ($fails as $f) {
echo '<li>', $f, '</li>';
}
echo '</ul>';
}
echo '<p align="center"><a href="index.php?id='.$id.'">'.get_string('backtoparticipants').'</a></p>';
}
echo $OUTPUT->footer();
exit;
} else {
echo $OUTPUT->notification(get_string('nousersyet'));
}
}
echo '<p align="center"><a href="'.$returnto.'">'.get_string("keepsearching").'</a>'.
((count($SESSION->emailto[$id])) ? ', '.get_string('usemessageform') : '').'</p>';
if ((!empty($send) || !empty($preview) || !empty($edit)) && (empty($messagebody))) {
echo $OUTPUT->notification(get_string('allfieldsrequired'));
}
if (count($SESSION->emailto[$id])) {
require_sesskey();
require("message.html");
}
$PAGE->requires->yui_module('moodle-core-formchangechecker',
'M.core_formchangechecker.init',
array(array(
'formid' => 'theform'
))
);
$PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
echo $OUTPUT->footer();

3
preferences.php

@ -52,6 +52,7 @@ $att = new mod_attendance_structure($att, $cm, $course, $context, $pageparams);
$PAGE->set_url($att->url_preferences());
$PAGE->set_title($course->shortname. ": ".$att->name.' - '.get_string('settings', 'attendance'));
$PAGE->set_heading($course->fullname);
$PAGE->force_settings_menu(true);
$PAGE->set_cacheable(true);
$PAGE->navbar->add(get_string('settings', 'attendance'));
@ -104,7 +105,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'));

108
renderer.php

@ -431,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]++;
}
}
@ -567,7 +567,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');
@ -636,6 +636,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[] = '';
}
@ -703,6 +704,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';
@ -712,8 +714,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);
@ -941,7 +951,6 @@ class mod_attendance_renderer extends plugin_renderer_base {
$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'),
@ -951,6 +960,8 @@ class mod_attendance_renderer extends plugin_renderer_base {
$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) {
@ -975,20 +986,46 @@ class mod_attendance_renderer extends plugin_renderer_base {
}
}
$table->data[] = $row;
if ($usersummary->numtakensessions > 0) {
$totalattendance++;
$totalpercentage = $totalpercentage + 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();
$average = format_float($totalpercentage / $totalattendance).'%';
$col = new html_table_cell(get_string('averageattendance', 'mod_attendance'));
if (empty($totalattendance)) {
$average = '-';
} else {
$average = format_float($totalpercentage / $totalattendance).'%';
}
$col = new html_table_cell(get_string('averageattendancegraded', 'mod_attendance'));
$col->attributes['class'] = 'averageattendance';
$row->cells = array($col, '', '', '', $average);
$col->colspan = 4;
$col2 = new html_table_cell($average);
$col2->style = 'text-align: center';
$row->cells = array($col, $col2);
$table->data[] = $row;
$o .= html_writer::table($table);
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);
}
}
return $o;
@ -1063,7 +1100,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$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 = 3;
@ -1177,8 +1214,6 @@ class mod_attendance_renderer extends plugin_renderer_base {
if ($bulkmessagecapability) { // Require that the user can bulk message users.
// Display check boxes that will allow the user to send a message to the students that have been checked.
$output = html_writer::empty_tag('input', array('name' => 'sesskey', 'type' => 'hidden', 'value' => sesskey()));
$output .= html_writer::empty_tag('input', array('name' => 'formaction', 'type' => 'hidden',
'value' => 'messageselect.php'));
$output .= html_writer::empty_tag('input', array('name' => 'id', 'type' => 'hidden', 'value' => $COURSE->id));
$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));;
@ -1187,7 +1222,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
'value' => get_string('messageselectadd'),
'class' => 'btn btn-secondary')),
array('class' => 'buttons'));
$url = new moodle_url('/user/action_redir.php');
$url = new moodle_url('/mod/attendance/messageselect.php');
return html_writer::tag('form', $output, array('action' => $url->out(), 'method' => 'post'));
} else {
return html_writer::table($table).html_writer::tag('div', get_string('users').': '.count($reportdata->users));
@ -1202,13 +1237,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();
@ -1453,18 +1506,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);

4
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) {
@ -327,7 +327,7 @@ function attendance_strftimehm($time) {
// Some Lang packs use %p to suffix with AM/PM but not all strftime support this.
// Check if %p is in use and make sure it's being respected.
if (stripos($format, '%p')) {
// Check if $userdate did something with %p by checking userdate against the same format without %p
// Check if $userdate did something with %p by checking userdate against the same format without %p.
$formatwithoutp = str_ireplace('%p', '', $format);
if (userdate($time, $formatwithoutp) == $userdate) {
// The date is the same with and without %p - we have a problem.

2
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);
@ -55,6 +56,7 @@ $PAGE->set_url($att->url_report());
$PAGE->set_pagelayout('report');
$PAGE->set_title($course->shortname. ": ".$att->name.' - '.get_string('report', 'attendance'));
$PAGE->set_heading($course->fullname);
$PAGE->force_settings_menu(true);
$PAGE->set_cacheable(true);
$PAGE->navbar->add(get_string('report', 'attendance'));

8
sessions.php

@ -58,6 +58,7 @@ $att = new mod_attendance_structure($att, $cm, $course, $context, $pageparams);
$PAGE->set_url($att->url_sessions(array('action' => $pageparams->action)));
$PAGE->set_title($course->shortname. ": ".$att->name);
$PAGE->set_heading($course->fullname);
$PAGE->force_settings_menu(true);
$PAGE->set_cacheable(true);
$PAGE->navbar->add($att->name);
@ -99,6 +100,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 +122,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 +137,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);

23
settings.php

@ -78,6 +78,10 @@ if ($ADMIN->fulltree) {
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));
@ -101,13 +105,13 @@ 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 = array(
ATTENDANCE_AUTOMARK_DISABLED => get_string('noautomark', 'attendance'),
ATTENDANCE_AUTOMARK_ALL => get_string('automarkall', 'attendance'),
ATTENDANCE_AUTOMARK_CLOSE => get_string('automarkclose', 'attendance'));
$options = attendance_get_automarkoptions();
$settings->add(new admin_setting_configselect('attendance/automark_default',
get_string('automark', 'attendance'), '', 0, $options));
@ -115,6 +119,17 @@ if ($ADMIN->fulltree) {
$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));
$options = attendance_get_sharedipoptions();
$settings->add(new admin_setting_configselect('attendance/preventsharedip',
get_string('preventsharedip', 'attendance'),
'', ATTENDANCE_SHAREDIP_DISABLED, $options));
$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));

38
student_attendance_form.php

@ -82,21 +82,23 @@ 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) {
$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);
}
if (!$attforsession->autoassignstatus) {
// 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);
// 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();
}
@ -109,9 +111,11 @@ class mod_attendance_student_attendance_form extends moodleform {
*/
public function validation($data, $files) {
$errors = array();
// Check if this status is allowed to be set.
if (empty($data['status'])) {
$errors['statusarray'] = get_string('invalidstatus', 'attendance');
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;

9
styles.css

@ -186,10 +186,19 @@
.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;
}

5
tempusers.php

@ -42,6 +42,7 @@ require_capability('mod/attendance:managetemporaryusers', $context);
$PAGE->set_title($course->shortname.": ".$att->name.' - '.get_string('tempusers', 'attendance'));
$PAGE->set_heading($course->fullname);
$PAGE->force_settings_menu(true);
$PAGE->set_cacheable(true);
$PAGE->navbar->add(get_string('tempusers', 'attendance'));
@ -60,8 +61,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);

14
tests/behat/report.feature

@ -22,8 +22,6 @@ Feature: Visiting reports
And I add a "Attendance" to section "1" and I fill the form with:
| Name | Attendance |
And I follow "Attendance"
And I follow "Add a block"
And I follow "Administration"
And I follow "Add session"
And I set the following fields to these values:
| id_sestime_starthour | 01 |
@ -41,7 +39,7 @@ Feature: Visiting reports
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Edit settings"
And I navigate to "Edit settings" in current page administration
Then I set the following fields to these values:
| id_grade_modgrade_type | Point |
| id_grade_modgrade_point | 50 |
@ -75,7 +73,7 @@ Feature: Visiting reports
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Edit settings"
And I navigate to "Edit settings" in current page administration
Then I set the following fields to these values:
| id_grade_modgrade_type | Point |
| id_grade_modgrade_point | 50 |
@ -88,7 +86,7 @@ Feature: Visiting reports
And I press "Save attendance"
When I follow "Attendance"
And I follow "Edit settings"
And I navigate to "Edit settings" in current page administration
Then I set the following fields to these values:
| id_grade_modgrade_type | Point |
| id_grade_modgrade_point | 70 |
@ -114,7 +112,7 @@ Feature: Visiting reports
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Edit settings"
And I navigate to "Edit settings" in current page administration
And I set the following fields to these values:
| id_grade_modgrade_type | Point |
| id_grade_modgrade_point | 50 |
@ -156,7 +154,7 @@ Feature: Visiting reports
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Edit settings"
And I navigate to "Edit settings" in current page administration
And I set the following fields to these values:
| id_grade_modgrade_type | Point |
| id_grade_modgrade_point | 50 |
@ -203,7 +201,7 @@ Feature: Visiting reports
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Attendance"
And I follow "Edit settings"
And I navigate to "Edit settings" in current page administration
Then I set the following fields to these values:
| id_grade_modgrade_type | Point |
| id_grade_modgrade_point | 50 |

71
update_form.php

@ -68,9 +68,13 @@ class mod_attendance_update_form extends moodleform {
'sdescription' => $sess->description_editor,
'studentscanmark' => $sess->studentscanmark,
'studentpassword' => $sess->studentpassword,
'autoassignstatus' => $sess->autoassignstatus,
'subnet' => $sess->subnet,
'automark' => $sess->automark,
'automarkcompleted' => 0);
'absenteereport' => $sess->absenteereport,
'automarkcompleted' => 0,
'preventsharedip' => $sess->preventsharedip,
'preventsharediptime' => $sess->preventsharediptime);
if ($sess->subnet == $attendancesubnet) {
$data['usedefaultsubnet'] = 1;
} else {
@ -95,14 +99,22 @@ 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);
@ -111,22 +123,22 @@ class mod_attendance_update_form extends moodleform {
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$options2 = array(
ATTENDANCE_AUTOMARK_DISABLED => get_string('noautomark', 'attendance'),
ATTENDANCE_AUTOMARK_ALL => get_string('automarkall', 'attendance'),
ATTENDANCE_AUTOMARK_CLOSE => get_string('automarkclose', '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->hideif('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->hideif('studentpassword', 'studentscanmark', 'notchecked');
$mform->hideif('studentpassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->hideif('randompassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->addElement('checkbox', 'autoassignstatus', '', get_string('autoassignstatus', 'attendance'));
$mform->addHelpButton('autoassignstatus', 'autoassignstatus', 'attendance');
$mform->hideif('autoassignstatus', 'studentscanmark', 'notchecked');
$mgroup = array();
$mgroup[] = & $mform->createElement('text', 'subnet', get_string('requiresubnet', 'attendance'));
@ -139,13 +151,25 @@ class mod_attendance_update_form extends moodleform {
$mform->setAdvanced('subnetgrp');
$mform->addHelpButton('subnetgrp', 'requiresubnet', 'attendance');
$mform->disabledif('usedefaultsubnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'studentscanmark', 'notchecked');
$mform->disabledif('subnet', 'usedefaultsubnet', 'checked');
$mform->hideif('subnetgrp', 'studentscanmark', 'notchecked');
$mform->hideif('subnet', 'usedefaultsubnet', 'checked');
$mform->addElement('hidden', 'automarkcompleted', '0');
$mform->settype('automarkcompleted', PARAM_INT);
$mgroup3 = array();
$options = attendance_get_sharedipoptions();
$mgroup3[] = & $mform->createElement('select', 'preventsharedip',
get_string('preventsharedip', 'attendance'), $options);
$mgroup3[] = & $mform->createElement('text', 'preventsharediptime',
get_string('preventsharediptime', 'attendance'), '', 'test');
$mform->addGroup($mgroup3, 'preventsharedgroup',
get_string('preventsharedip', 'attendance'), array(' '), false);
$mform->addHelpButton('preventsharedgroup', 'preventsharedip', 'attendance');
$mform->setAdvanced('preventsharedgroup');
$mform->setType('preventsharediptime', PARAM_INT);
$mform->hideif('preventsharedgroup', 'studentscanmark', 'notchecked');
$mform->hideIf('preventsharediptime', 'preventsharedip', 'noteq', ATTENDANCE_SHAREDIP_MINUTES);
} else {
$mform->addElement('hidden', 'studentscanmark', '0');
$mform->settype('studentscanmark', PARAM_INT);
@ -155,6 +179,8 @@ class mod_attendance_update_form extends moodleform {
$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->setDefaults($data);
@ -168,6 +194,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;
@ -176,6 +203,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;
}
}

8
version.php

@ -23,9 +23,9 @@
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017102700;
$plugin->requires = 2017102700; // Requires 3.4
$plugin->release = '3.4.1';
$plugin->maturity = MATURITY_ALPHA;
$plugin->version = 2017112008;
$plugin->requires = 2017102700; // Requires 3.4.
$plugin->release = '3.4.5';
$plugin->maturity = MATURITY_STABLE;
$plugin->cron = 0;
$plugin->component = 'mod_attendance';

2
warnings.php

@ -124,7 +124,7 @@ if ($data = $mform->get_data()) {
$notify->thirdpartyemails = implode(',', $data->thirdpartyemails);
}
$existingrecord = $DB->get_record('attendance_warning', array('idnumber' => $notify->idnumber,
'warningpercent' => $notify->warningpercent));
'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');

Loading…
Cancel
Save