### [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
$mform->addElement('checkbox', 'studentscanmark', '', get_string('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->addHelpButton('automark', 'automark', 'attendance');
$mform->disabledif('automark', '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);
if (isset($pluginconfig->studentscanmark_default)) {
$mform->setDefault('studentscanmark', $pluginconfig->studentscanmark_default);
}
'objectid' => $this->id,
'context' => $this->context,
'other' => array('info' => $info, 'sessionid' => $sessionid,
- 'action' => mod_attendance_sessions_page_params::ACTION_UPDATE)));
+ 'action' => mod_attendance_sessions_page_params::ACTION_UPDATE)));
$event->add_record_snapshot('course_modules', $this->cm);
$event->add_record_snapshot('attendance_sessions', $sess);
$event->trigger();
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;
+ }
}
$cachecm = array();
$cacheatt = array();
$cachecourse = array();
+ $now = time(); // Store current time to use in queries so they all match nicely.
+
$sessions = $DB->get_recordset_select('attendance_sessions',
- 'automark = 1 AND automarkcompleted = 0 AND sessdate < ? ', array(time()));
+ 'automark > 0 AND automarkcompleted < 2 AND sessdate < ? ', array($now));
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 < time()) {
+ if ($session->sessdate + $session->duration < $now || // If session is over.
+ // OR if session is currently open and automark is set to do all.
+ ($session->sessdate < $now && $session->automark == 1)) {
+
+ $userfirstaccess = array();
$donesomething = false; // Only trigger grades/events when an update actually occurs.
+ $sessionover = false; // Is this session over?
+ if ($session->sessdate + $session->duration < $now) {
+ $sessionover = true;
+ }
// Store cm/att/course in cachefields so we don't make unnecessary db calls.
// Would probably be nice to grab this stuff outside of the loop.
}
$pageparams->sessionid = $session->id;
+ if ($session->automark == 1) {
+ $userfirstacess = array();
+ // If set to do full automarking, get all users that have accessed course during session open.
+ $id = $DB->sql_concat('userid', 'ip'); // Users may access from multiple ip, make the first field unique.
+ $sql = "SELECT $id, userid, ip, min(timecreated) as timecreated
+ FROM {logstore_standard_log}
+ WHERE courseid = ? AND timecreated > ? AND timecreated < ?
+ GROUP BY userid, ip";
+
+ $timestart = $session->sessdate;
+ if (empty($session->lasttakenby) && $session->lasttaken > $timestart) {
+ // If the last time session was taken it was done automatically, use the last time taken
+ // as the start time for the logs we are interested in to help with performance.
+ $timestart = $session->lasttaken;
+ }
+ $duration = $session->duration;
+ if (empty($duration)) {
+ $duration = get_config('attendance', 'studentscanmarksessiontimeend') * 60;
+ }
+ $timeend = $timestart + $duration;
+ $logusers = $DB->get_recordset_sql($sql, array($courseid, $timestart, $timeend));
+ // Check if user access is in allowed subnet.
+ foreach ($logusers as $loguser) {
+ if (!empty($session->subnet) && !address_in_subnet($loguser->ip, $session->subnet)) {
+ // This record isn't in the right subnet.
+ continue;
+ }
+ if (empty($userfirstaccess[$loguser->userid]) ||
+ $userfirstaccess[$loguser->userid] > $loguser->timecreated) {
+ // Users may have accessed from mulitple ip addresses, find the earliest access.
+ $userfirstaccess[$loguser->userid] = $loguser->timecreated;
+ }
+ }
+ $logusers->close();
+ }
+
// Get all unmarked students.
$att = new \mod_attendance_structure($cacheatt[$session->attendanceid],
$cachecm[$session->attendanceid], $cachecourse[$courseid], $context, $pageparams);
foreach ($existinglog as $log) {
if (empty($log->statusid)) {
- // Status needs updating.
- $existinglog->statusid = $setunmarked;
- $existinglog->timetaken = time();
- $existinglog->takenby = 0;
- $existinglog->remarks = get_string('autorecorded', 'attendance');
-
- $DB->update_record('attendance_log', $existinglog);
- $updated++;
- $donesomething = true;
+ if ($sessionover || !empty($userfirstaccess[$log->studentid])) {
+ // Status needs updating.
+ if ($sessionover) {
+ $log->statusid = $setunmarked;
+ } else if (!empty($userfirstaccess[$log->studentid])) {
+ $log->statusid = $att->get_automark_status($userfirstaccess[$log->studentid], $session->id);
+ }
+ if (!empty($log->statusid)) {
+ $log->timetaken = $now;
+ $log->takenby = 0;
+ $log->remarks = get_string('autorecorded', 'attendance');
+
+ $DB->update_record('attendance_log', $log);
+ $updated++;
+ $donesomething = true;
+ }
+ }
}
unset($users[$log->studentid]);
}
mtrace($updated . " session status updated");
$newlog = new \stdClass();
- $newlog->statusid = $setunmarked;
- $newlog->timetaken = time();
+ $newlog->timetaken = $now;
$newlog->takenby = 0;
$newlog->sessionid = $session->id;
$newlog->remarks = get_string('autorecorded', 'attendance');
$added = 0;
foreach ($users as $user) {
- $newlog->studentid = $user->id;
- $DB->insert_record('attendance_log', $newlog);
- $added++;
- $donesomething = true;
+ if ($sessionover || !empty($userfirstaccess[$user->id])) {
+ if ($sessionover) {
+ $newlog->statusid = $setunmarked;
+ } else if (!empty($userfirstaccess[$user->id])) {
+ $newlog->statusid = $att->get_automark_status($userfirstaccess[$user->id], $session->id);
+ }
+ if (!empty($newlog->statusid)) {
+ $newlog->studentid = $user->id;
+ $DB->insert_record('attendance_log', $newlog);
+ $added++;
+ $donesomething = true;
+ }
+ }
}
mtrace($added . " session status inserted");
// Update lasttaken time and automarkcompleted for this session.
- $session->lasttaken = $newlog->timetaken;
+ $session->lasttaken = $now;
$session->lasttakenby = 0;
- $session->automarkcompleted = 1;
+ if ($sessionover) {
+ $session->automarkcompleted = 2;
+ } else {
+ $session->automarkcompleted = 1;
+ }
+
$DB->update_record('attendance_sessions', $session);
if ($donesomething) {
upgrade_mod_savepoint(true, 2017052201, 'attendance');
}
+ if ($oldversion < 2017060900) {
+ // 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, 2017060900, 'attendance');
+ }
return $result;
}
$string['attendanceupdated'] = 'Attendance successfully updated';
$string['attforblockdirstillexists'] = 'old mod/attforblock directory still exists - you must delete this directory on your server before running this upgrade.';
$string['attrecords'] = 'Attendances records';
-$string['automark'] = 'Automatically set status on close of session.';
-$string['automark_help'] = 'When session closes, automatically set status for unreported students as configured by status set.';
+$string['automark'] = 'Automatic marking';
+$string['automarkall'] = 'Yes';
+$string['automarkclose'] = 'Set unmarked at end of session';
+$string['automark_help'] = 'Allows marking to be completed automatically.
+If "Yes" students will be automatically marked depending on their first access to the course.
+If "Set unmarked at end of session" any students who have not marked their attendance will be set to the unmarked status selected.';
$string['automarktask'] = 'Check for closed attendance sessions that require auto marking';
$string['autorecorded'] = 'system auto recorded';
$string['averageattendance'] = 'Average attendance';
$string['newduration'] = 'New duration';
$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['noautomark'] = 'Disabled';
$string['noattforuser'] = 'No attendance records exist for the user';
$string['nodescription'] = 'Regular class session';
$string['nogroups'] = 'You can\'t add group sessions. No groups exists in course.';
define('ATT_SORT_LASTNAME', 1);
define('ATT_SORT_FIRSTNAME', 2);
+define('ATTENDANCE_AUTOMARK_DISABLED', 0);
+define('ATTENDANCE_AUTOMARK_ALL', 1);
+define('ATTENDANCE_AUTOMARK_CLOSE', 2);
/**
* Get statuses,
*
$sess->studentscanmark = 1;
if (!empty($formdata->randompassword)) {
$sess->studentpassword = attendance_random_string();
- } else {
+ } else if (!empty($formdata->studentpassword)) {
$sess->studentpassword = $formdata->studentpassword;
}
if (!empty($formdata->usedefaultsubnet)) {
$settings->add(new admin_setting_configcheckbox('attendance/studentscanmark_default',
get_string('studentscanmark', 'attendance'), '', 0));
- $settings->add(new admin_setting_configcheckbox('attendance/automark_default',
- get_string('automark', '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'));
+
+ $settings->add(new admin_setting_configselect('attendance/automark_default',
+ get_string('automark', 'attendance'), '', 0, $options));
$settings->add(new admin_setting_configcheckbox('attendance/randompassword_default',
get_string('randompassword', 'attendance'), '', 0));
$mform->addElement('checkbox', 'studentscanmark', '', get_string('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->addHelpButton('automark', 'automark', 'attendance');
$mform->disabledif('automark', 'studentscanmark', 'notchecked');
$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);
$mgroup = array();
$mgroup[] = & $mform->createElement('text', 'subnet', get_string('requiresubnet', 'attendance'));
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017052300;
+$plugin->version = 2017060900;
$plugin->requires = 2017042100;
-$plugin->release = '3.3.6';
+$plugin->release = '3.3.7';
$plugin->maturity = MATURITY_ALPHA;
$plugin->cron = 0;
$plugin->component = 'mod_attendance';