New Feature: allow automarking of attendance using logs table.
authorDan Marsden <dan@danmarsden.com>
Fri, 9 Jun 2017 01:25:17 +0000 (13:25 +1200)
committerDan Marsden <dan@danmarsden.com>
Tue, 13 Jun 2017 23:45:31 +0000 (11:45 +1200)
CHANGELOG.md [new file with mode: 0644]
add_form.php
classes/structure.php
classes/task/auto_mark.php
db/upgrade.php
lang/en/attendance.php
locallib.php
settings.php
update_form.php
version.php

diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644 (file)
index 0000000..168aff3
--- /dev/null
@@ -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.
+
+---
+
index 338b06d..13819b2 100644 (file)
@@ -132,7 +132,12 @@ class mod_attendance_add_form extends moodleform {
             $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');
@@ -150,6 +155,8 @@ class mod_attendance_add_form extends moodleform {
             $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);
             }
index ceee72d..c271091 100644 (file)
@@ -525,7 +525,7 @@ class mod_attendance_structure {
             '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();
@@ -1163,4 +1163,41 @@ class mod_attendance_structure {
 
         return null;
     }
+
+    /**
+     * Gets the status to use when auto-marking.
+     *
+     * @param int $time the time the user first accessed the course.
+     * @param int $sessionid the related sessionid to check.
+     * @return int the statusid to assign to this user.
+     */
+    public function get_automark_status($time, $sessionid) {
+        $statuses = $this->get_statuses();
+        // Statuses are returned highest grade first, find the first high grade we can assign to this user.
+
+        // Get status to use when unmarked.
+        $session = $this->sessioninfo[$sessionid];
+        $duration = $session->duration;
+        if (empty($duration)) {
+            $duration = get_config('attendance', 'studentscanmarksessiontimeend') * 60;
+        }
+        if ($time > $session->sessdate + $duration) {
+            // This session closed after the users access - use the unmarked state.
+            foreach ($statuses as $status) {
+                if (!empty($status->setunmarked)) {
+                    return $status->id;
+                }
+            }
+        } else {
+            foreach ($statuses as $status) {
+                if ($status->studentavailability !== '0' &&
+                    $this->sessioninfo[$sessionid]->sessdate + ($status->studentavailability * 60) > $time) {
+
+                    // Found first status we could set.
+                    return $status->id;
+                }
+            }
+        }
+        return;
+    }
 }
index 9a86c47..54a6f7b 100644 (file)
@@ -44,13 +44,22 @@ class auto_mark extends \core\task\scheduled_task {
         $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.
@@ -84,6 +93,42 @@ class auto_mark extends \core\task\scheduled_task {
                 }
                 $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);
@@ -95,15 +140,23 @@ class auto_mark extends \core\task\scheduled_task {
 
                 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]);
                 }
@@ -111,8 +164,7 @@ class auto_mark extends \core\task\scheduled_task {
                 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');
@@ -120,17 +172,31 @@ class auto_mark extends \core\task\scheduled_task {
 
                 $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) {
index 574516a..fb1bb3a 100644 (file)
@@ -315,5 +315,21 @@ function xmldb_attendance_upgrade($oldversion=0) {
         upgrade_mod_savepoint(true, 2016121311, 'attendance');
     }
 
+    if ($oldversion < 2016121314) {
+        // 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, 2016121314, 'attendance');
+    }
     return $result;
 }
index aa778ce..c69ae83 100644 (file)
@@ -62,8 +62,12 @@ $string['attendancesuccess'] = 'Attendance has been successfully taken';
 $string['attendanceupdated'] = 'Attendance successfully updated';
 $string['attforblockdirstillexists'] = 'old mod/attforblock directory still exists - you must delete this directory on your server before running this upgrade.';
 $string['attrecords'] = 'Attendances records';
-$string['automark'] = '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';
@@ -201,6 +205,7 @@ $string['newdate'] = 'New date';
 $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.';
index 5a82d32..b7ba6e5 100644 (file)
@@ -39,6 +39,9 @@ define('ATT_SORT_DEFAULT', 0);
 define('ATT_SORT_LASTNAME', 1);
 define('ATT_SORT_FIRSTNAME', 2);
 
+define('ATTENDANCE_AUTOMARK_DISABLED', 0);
+define('ATTENDANCE_AUTOMARK_ALL', 1);
+define('ATTENDANCE_AUTOMARK_CLOSE', 2);
 /**
  * Get statuses,
  *
@@ -618,7 +621,7 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
             $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)) {
index a2d0032..41131fd 100644 (file)
@@ -84,8 +84,13 @@ if ($ADMIN->fulltree) {
     $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));
index 0197dc6..a1dfe53 100644 (file)
@@ -111,7 +111,12 @@ class mod_attendance_update_form extends moodleform {
             $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');
@@ -120,6 +125,8 @@ class mod_attendance_update_form extends moodleform {
             $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'));
index 375a1cd..0d790ae 100644 (file)
@@ -23,9 +23,9 @@
  */
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version  = 2016121313;
+$plugin->version  = 2016121314;
 $plugin->requires = 2016111800;
-$plugin->release = '3.2.11';
+$plugin->release = '3.2.12';
 $plugin->maturity  = MATURITY_STABLE;
 $plugin->cron     = 0;
 $plugin->component = 'mod_attendance';