Browse Source

New Feature: allow automarking of attendance using logs table.

MOODLE_33_STABLE
Dan Marsden 8 years ago
parent
commit
9ae22ff659
  1. 36
      CHANGELOG.md
  2. 9
      add_form.php
  3. 39
      classes/structure.php
  4. 106
      classes/task/auto_mark.php
  5. 16
      db/upgrade.php
  6. 9
      lang/en/attendance.php
  7. 5
      locallib.php
  8. 9
      settings.php
  9. 9
      update_form.php
  10. 2
      version.php

36
CHANGELOG.md

@ -0,0 +1,36 @@
### [Unreleased]
- New Feature: Allow automatic marking using site logs.
- Improvement: Allow default view for teachers to be set at admin level.
### Date: 2017-May-23
### Release: 2017052301
- New Feature: New site Level/course category report with average course attendance.
- New Feature: Allow unmarked students to be automatically marked when session closes.
---
### Date: 2017-May-11
### Release: 2017051104
- New Feature: Allow subnet mask to be set at the attendance session level.
- New Feature: Allow certain statuses to be hidden from students when self-marking attendance.
- New Feature: Allow student password to be viewed on session list page.
- Improvement: Improve usablity by grouping settings on session add form.
- Bug fix - fix issue with displaying dates when site hosted on Windows server.
- Bug fix - improve compliance with Moodle coding guidelines.
---
### Date: 2017-Apr-21
### Release: 2017042100
- Feature: Allow a random self-marking password to be used when creating session.
- Improvement: #63 use core useridentity setting when showing list of users.
- Improvement: #258 Add link to attendance on student overview report.
- Improvement: allow student self-marking to be restricted to the session time.
- Improvement: allow admin to set default values when teachers creating new sessions.
- Bug fix - improve compliance with Moodle coding guidelines - phpdocs etc.
---

9
add_form.php

@ -132,7 +132,12 @@ class mod_attendance_add_form extends moodleform {
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance')); $mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance'); $mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$mform->addElement('checkbox', 'automark', get_string('automark', 'attendance')); $options = array(
ATTENDANCE_AUTOMARK_DISABLED => get_string('noautomark', 'attendance'),
ATTENDANCE_AUTOMARK_ALL => get_string('automarkall', 'attendance'),
ATTENDANCE_AUTOMARK_CLOSE => get_string('automarkclose', 'attendance'));
$mform->addElement('select', 'automark', get_string('automark', 'attendance'), $options);
$mform->setType('automark', PARAM_INT); $mform->setType('automark', PARAM_INT);
$mform->addHelpButton('automark', 'automark', 'attendance'); $mform->addHelpButton('automark', 'automark', 'attendance');
$mform->disabledif('automark', 'studentscanmark', 'notchecked'); $mform->disabledif('automark', 'studentscanmark', 'notchecked');
@ -150,6 +155,8 @@ class mod_attendance_add_form extends moodleform {
$mform->addHelpButton('passwordgrp', 'passwordgrp', 'attendance'); $mform->addHelpButton('passwordgrp', 'passwordgrp', 'attendance');
$mform->disabledif('randompassword', 'studentscanmark', 'notchecked'); $mform->disabledif('randompassword', 'studentscanmark', 'notchecked');
$mform->disabledif('studentpassword', 'randompassword', 'checked'); $mform->disabledif('studentpassword', 'randompassword', 'checked');
$mform->disabledif('studentpassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->disabledif('randompassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
if (isset($pluginconfig->studentscanmark_default)) { if (isset($pluginconfig->studentscanmark_default)) {
$mform->setDefault('studentscanmark', $pluginconfig->studentscanmark_default); $mform->setDefault('studentscanmark', $pluginconfig->studentscanmark_default);
} }

39
classes/structure.php

@ -525,7 +525,7 @@ class mod_attendance_structure {
'objectid' => $this->id, 'objectid' => $this->id,
'context' => $this->context, 'context' => $this->context,
'other' => array('info' => $info, 'sessionid' => $sessionid, 'other' => array('info' => $info, 'sessionid' => $sessionid,
'action' => mod_attendance_sessions_page_params::ACTION_UPDATE))); 'action' => mod_attendance_sessions_page_params::ACTION_UPDATE)));
$event->add_record_snapshot('course_modules', $this->cm); $event->add_record_snapshot('course_modules', $this->cm);
$event->add_record_snapshot('attendance_sessions', $sess); $event->add_record_snapshot('attendance_sessions', $sess);
$event->trigger(); $event->trigger();
@ -1163,4 +1163,41 @@ class mod_attendance_structure {
return null; return null;
} }
/**
* Gets the status to use when auto-marking.
*
* @param int $time the time the user first accessed the course.
* @param int $sessionid the related sessionid to check.
* @return int the statusid to assign to this user.
*/
public function get_automark_status($time, $sessionid) {
$statuses = $this->get_statuses();
// Statuses are returned highest grade first, find the first high grade we can assign to this user.
// Get status to use when unmarked.
$session = $this->sessioninfo[$sessionid];
$duration = $session->duration;
if (empty($duration)) {
$duration = get_config('attendance', 'studentscanmarksessiontimeend') * 60;
}
if ($time > $session->sessdate + $duration) {
// This session closed after the users access - use the unmarked state.
foreach ($statuses as $status) {
if (!empty($status->setunmarked)) {
return $status->id;
}
}
} else {
foreach ($statuses as $status) {
if ($status->studentavailability !== '0' &&
$this->sessioninfo[$sessionid]->sessdate + ($status->studentavailability * 60) > $time) {
// Found first status we could set.
return $status->id;
}
}
}
return;
}
} }

106
classes/task/auto_mark.php

@ -44,13 +44,22 @@ class auto_mark extends \core\task\scheduled_task {
$cachecm = array(); $cachecm = array();
$cacheatt = array(); $cacheatt = array();
$cachecourse = array(); $cachecourse = array();
$now = time(); // Store current time to use in queries so they all match nicely.
$sessions = $DB->get_recordset_select('attendance_sessions', $sessions = $DB->get_recordset_select('attendance_sessions',
'automark = 1 AND automarkcompleted = 0 AND sessdate < ? ', array(time())); 'automark > 0 AND automarkcompleted < 2 AND sessdate < ? ', array($now));
foreach ($sessions as $session) { foreach ($sessions as $session) {
// Would be nice to change duration field to a timestamp so we don't need this step. if ($session->sessdate + $session->duration < $now || // If session is over.
if ($session->sessdate + $session->duration < time()) { // OR if session is currently open and automark is set to do all.
($session->sessdate < $now && $session->automark == 1)) {
$userfirstaccess = array();
$donesomething = false; // Only trigger grades/events when an update actually occurs. $donesomething = false; // Only trigger grades/events when an update actually occurs.
$sessionover = false; // Is this session over?
if ($session->sessdate + $session->duration < $now) {
$sessionover = true;
}
// Store cm/att/course in cachefields so we don't make unnecessary db calls. // Store cm/att/course in cachefields so we don't make unnecessary db calls.
// Would probably be nice to grab this stuff outside of the loop. // Would probably be nice to grab this stuff outside of the loop.
@ -84,6 +93,42 @@ class auto_mark extends \core\task\scheduled_task {
} }
$pageparams->sessionid = $session->id; $pageparams->sessionid = $session->id;
if ($session->automark == 1) {
$userfirstacess = array();
// If set to do full automarking, get all users that have accessed course during session open.
$id = $DB->sql_concat('userid', 'ip'); // Users may access from multiple ip, make the first field unique.
$sql = "SELECT $id, userid, ip, min(timecreated) as timecreated
FROM {logstore_standard_log}
WHERE courseid = ? AND timecreated > ? AND timecreated < ?
GROUP BY userid, ip";
$timestart = $session->sessdate;
if (empty($session->lasttakenby) && $session->lasttaken > $timestart) {
// If the last time session was taken it was done automatically, use the last time taken
// as the start time for the logs we are interested in to help with performance.
$timestart = $session->lasttaken;
}
$duration = $session->duration;
if (empty($duration)) {
$duration = get_config('attendance', 'studentscanmarksessiontimeend') * 60;
}
$timeend = $timestart + $duration;
$logusers = $DB->get_recordset_sql($sql, array($courseid, $timestart, $timeend));
// Check if user access is in allowed subnet.
foreach ($logusers as $loguser) {
if (!empty($session->subnet) && !address_in_subnet($loguser->ip, $session->subnet)) {
// This record isn't in the right subnet.
continue;
}
if (empty($userfirstaccess[$loguser->userid]) ||
$userfirstaccess[$loguser->userid] > $loguser->timecreated) {
// Users may have accessed from mulitple ip addresses, find the earliest access.
$userfirstaccess[$loguser->userid] = $loguser->timecreated;
}
}
$logusers->close();
}
// Get all unmarked students. // Get all unmarked students.
$att = new \mod_attendance_structure($cacheatt[$session->attendanceid], $att = new \mod_attendance_structure($cacheatt[$session->attendanceid],
$cachecm[$session->attendanceid], $cachecourse[$courseid], $context, $pageparams); $cachecm[$session->attendanceid], $cachecourse[$courseid], $context, $pageparams);
@ -95,15 +140,23 @@ class auto_mark extends \core\task\scheduled_task {
foreach ($existinglog as $log) { foreach ($existinglog as $log) {
if (empty($log->statusid)) { if (empty($log->statusid)) {
// Status needs updating. if ($sessionover || !empty($userfirstaccess[$log->studentid])) {
$existinglog->statusid = $setunmarked; // Status needs updating.
$existinglog->timetaken = time(); if ($sessionover) {
$existinglog->takenby = 0; $log->statusid = $setunmarked;
$existinglog->remarks = get_string('autorecorded', 'attendance'); } else if (!empty($userfirstaccess[$log->studentid])) {
$log->statusid = $att->get_automark_status($userfirstaccess[$log->studentid], $session->id);
$DB->update_record('attendance_log', $existinglog); }
$updated++; if (!empty($log->statusid)) {
$donesomething = true; $log->timetaken = $now;
$log->takenby = 0;
$log->remarks = get_string('autorecorded', 'attendance');
$DB->update_record('attendance_log', $log);
$updated++;
$donesomething = true;
}
}
} }
unset($users[$log->studentid]); unset($users[$log->studentid]);
} }
@ -111,8 +164,7 @@ class auto_mark extends \core\task\scheduled_task {
mtrace($updated . " session status updated"); mtrace($updated . " session status updated");
$newlog = new \stdClass(); $newlog = new \stdClass();
$newlog->statusid = $setunmarked; $newlog->timetaken = $now;
$newlog->timetaken = time();
$newlog->takenby = 0; $newlog->takenby = 0;
$newlog->sessionid = $session->id; $newlog->sessionid = $session->id;
$newlog->remarks = get_string('autorecorded', 'attendance'); $newlog->remarks = get_string('autorecorded', 'attendance');
@ -120,17 +172,31 @@ class auto_mark extends \core\task\scheduled_task {
$added = 0; $added = 0;
foreach ($users as $user) { foreach ($users as $user) {
$newlog->studentid = $user->id; if ($sessionover || !empty($userfirstaccess[$user->id])) {
$DB->insert_record('attendance_log', $newlog); if ($sessionover) {
$added++; $newlog->statusid = $setunmarked;
$donesomething = true; } else if (!empty($userfirstaccess[$user->id])) {
$newlog->statusid = $att->get_automark_status($userfirstaccess[$user->id], $session->id);
}
if (!empty($newlog->statusid)) {
$newlog->studentid = $user->id;
$DB->insert_record('attendance_log', $newlog);
$added++;
$donesomething = true;
}
}
} }
mtrace($added . " session status inserted"); mtrace($added . " session status inserted");
// Update lasttaken time and automarkcompleted for this session. // Update lasttaken time and automarkcompleted for this session.
$session->lasttaken = $newlog->timetaken; $session->lasttaken = $now;
$session->lasttakenby = 0; $session->lasttakenby = 0;
$session->automarkcompleted = 1; if ($sessionover) {
$session->automarkcompleted = 2;
} else {
$session->automarkcompleted = 1;
}
$DB->update_record('attendance_sessions', $session); $DB->update_record('attendance_sessions', $session);
if ($donesomething) { if ($donesomething) {

16
db/upgrade.php

@ -315,5 +315,21 @@ function xmldb_attendance_upgrade($oldversion=0) {
upgrade_mod_savepoint(true, 2017050206, 'attendance'); upgrade_mod_savepoint(true, 2017050206, 'attendance');
} }
if ($oldversion < 2017050209) {
// Automark values changed.
$default = get_config('attendance', 'automark_default');
if (!empty($default)) { // Change default if set.
set_config('automark_default', 2, 'attendance');
}
// Update any sessions set to use automark = 1.
$sql = "UPDATE {attendance_sessions} SET automark = 2 WHERE automark = 1";
$DB->execute($sql);
// Update automarkcompleted to 2 if already complete.
$sql = "UPDATE {attendance_sessions} SET automarkcompleted = 2 WHERE automarkcompleted = 1";
$DB->execute($sql);
upgrade_mod_savepoint(true, 2017050209, 'attendance');
}
return $result; return $result;
} }

9
lang/en/attendance.php

@ -62,8 +62,12 @@ $string['attendancesuccess'] = 'Attendance has been successfully taken';
$string['attendanceupdated'] = 'Attendance successfully updated'; $string['attendanceupdated'] = 'Attendance successfully updated';
$string['attforblockdirstillexists'] = 'old mod/attforblock directory still exists - you must delete this directory on your server before running this upgrade.'; $string['attforblockdirstillexists'] = 'old mod/attforblock directory still exists - you must delete this directory on your server before running this upgrade.';
$string['attrecords'] = 'Attendances records'; $string['attrecords'] = 'Attendances records';
$string['automark'] = 'Automatically set status on close of session.'; $string['automark'] = 'Automatic marking';
$string['automark_help'] = 'When session closes, automatically set status for unreported students as configured by status set.'; $string['automarkall'] = 'Yes';
$string['automarkclose'] = 'Set unmarked at end of session';
$string['automark_help'] = 'Allows marking to be completed automatically.
If "Yes" students will be automatically marked depending on their first access to the course.
If "Set unmarked at end of session" any students who have not marked their attendance will be set to the unmarked status selected.';
$string['automarktask'] = 'Check for closed attendance sessions that require auto marking'; $string['automarktask'] = 'Check for closed attendance sessions that require auto marking';
$string['autorecorded'] = 'system auto recorded'; $string['autorecorded'] = 'system auto recorded';
$string['averageattendance'] = 'Average attendance'; $string['averageattendance'] = 'Average attendance';
@ -201,6 +205,7 @@ $string['newdate'] = 'New date';
$string['newduration'] = 'New duration'; $string['newduration'] = 'New duration';
$string['newstatusset'] = 'New set of statuses'; $string['newstatusset'] = 'New set of statuses';
$string['noattendanceusers'] = 'It is not possible to export any data as there are no students enrolled in the course.'; $string['noattendanceusers'] = 'It is not possible to export any data as there are no students enrolled in the course.';
$string['noautomark'] = 'Disabled';
$string['noattforuser'] = 'No attendance records exist for the user'; $string['noattforuser'] = 'No attendance records exist for the user';
$string['nodescription'] = 'Regular class session'; $string['nodescription'] = 'Regular class session';
$string['nogroups'] = 'You can\'t add group sessions. No groups exists in course.'; $string['nogroups'] = 'You can\'t add group sessions. No groups exists in course.';

5
locallib.php

@ -39,6 +39,9 @@ define('ATT_SORT_DEFAULT', 0);
define('ATT_SORT_LASTNAME', 1); define('ATT_SORT_LASTNAME', 1);
define('ATT_SORT_FIRSTNAME', 2); define('ATT_SORT_FIRSTNAME', 2);
define('ATTENDANCE_AUTOMARK_DISABLED', 0);
define('ATTENDANCE_AUTOMARK_ALL', 1);
define('ATTENDANCE_AUTOMARK_CLOSE', 2);
/** /**
* Get statuses, * Get statuses,
* *
@ -618,7 +621,7 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
$sess->studentscanmark = 1; $sess->studentscanmark = 1;
if (!empty($formdata->randompassword)) { if (!empty($formdata->randompassword)) {
$sess->studentpassword = attendance_random_string(); $sess->studentpassword = attendance_random_string();
} else { } else if (!empty($formdata->studentpassword)) {
$sess->studentpassword = $formdata->studentpassword; $sess->studentpassword = $formdata->studentpassword;
} }
if (!empty($formdata->usedefaultsubnet)) { if (!empty($formdata->usedefaultsubnet)) {

9
settings.php

@ -84,8 +84,13 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_configcheckbox('attendance/studentscanmark_default', $settings->add(new admin_setting_configcheckbox('attendance/studentscanmark_default',
get_string('studentscanmark', 'attendance'), '', 0)); get_string('studentscanmark', 'attendance'), '', 0));
$settings->add(new admin_setting_configcheckbox('attendance/automark_default', $options = array(
get_string('automark', 'attendance'), '', 0)); ATTENDANCE_AUTOMARK_DISABLED => get_string('noautomark', 'attendance'),
ATTENDANCE_AUTOMARK_ALL => get_string('automarkall', 'attendance'),
ATTENDANCE_AUTOMARK_CLOSE => get_string('automarkclose', 'attendance'));
$settings->add(new admin_setting_configselect('attendance/automark_default',
get_string('automark', 'attendance'), '', 0, $options));
$settings->add(new admin_setting_configcheckbox('attendance/randompassword_default', $settings->add(new admin_setting_configcheckbox('attendance/randompassword_default',
get_string('randompassword', 'attendance'), '', 0)); get_string('randompassword', 'attendance'), '', 0));

9
update_form.php

@ -111,7 +111,12 @@ class mod_attendance_update_form extends moodleform {
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance')); $mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance'); $mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$mform->addElement('checkbox', 'automark', get_string('automark', 'attendance')); $options2 = array(
ATTENDANCE_AUTOMARK_DISABLED => get_string('noautomark', 'attendance'),
ATTENDANCE_AUTOMARK_ALL => get_string('automarkall', 'attendance'),
ATTENDANCE_AUTOMARK_CLOSE => get_string('automarkclose', 'attendance'));
$mform->addElement('select', 'automark', get_string('automark', 'attendance'), $options2);
$mform->setType('automark', PARAM_INT); $mform->setType('automark', PARAM_INT);
$mform->addHelpButton('automark', 'automark', 'attendance'); $mform->addHelpButton('automark', 'automark', 'attendance');
$mform->disabledif('automark', 'studentscanmark', 'notchecked'); $mform->disabledif('automark', 'studentscanmark', 'notchecked');
@ -120,6 +125,8 @@ class mod_attendance_update_form extends moodleform {
$mform->setType('studentpassword', PARAM_TEXT); $mform->setType('studentpassword', PARAM_TEXT);
$mform->addHelpButton('studentpassword', 'passwordgrp', 'attendance'); $mform->addHelpButton('studentpassword', 'passwordgrp', 'attendance');
$mform->disabledif('studentpassword', 'studentscanmark', 'notchecked'); $mform->disabledif('studentpassword', 'studentscanmark', 'notchecked');
$mform->disabledif('studentpassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mform->disabledif('randompassword', 'automark', 'eq', ATTENDANCE_AUTOMARK_ALL);
$mgroup = array(); $mgroup = array();
$mgroup[] = & $mform->createElement('text', 'subnet', get_string('requiresubnet', 'attendance')); $mgroup[] = & $mform->createElement('text', 'subnet', get_string('requiresubnet', 'attendance'));

2
version.php

@ -23,7 +23,7 @@
*/ */
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017050208; $plugin->version = 2017050209;
$plugin->requires = 2017042100; $plugin->requires = 2017042100;
$plugin->release = '3.3.7'; $plugin->release = '3.3.7';
$plugin->maturity = MATURITY_STABLE; $plugin->maturity = MATURITY_STABLE;

Loading…
Cancel
Save