diff --git a/add_form.php b/add_form.php index ff847dd..ec04bd8 100644 --- a/add_form.php +++ b/add_form.php @@ -121,10 +121,23 @@ class mod_attendance_add_form extends moodleform { $mform->setType('statusset', PARAM_INT); } + $mform->addElement('editor', 'sdescription', get_string('description', 'attendance'), array('rows' => 1, 'columns' => 80), + array('maxfiles' => EDITOR_UNLIMITED_FILES, 'noclean' => true, 'context' => $modcontext)); + $mform->setType('sdescription', PARAM_RAW); + // Students can mark own attendance. if (!empty(get_config('attendance', 'studentscanmark'))) { + $mform->addElement('header', 'headerstudentmarking', get_string('studentmarking', 'attendance'), true); + $mform->setExpanded('headerstudentmarking'); $mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance')); $mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance'); + + $mform->addElement('checkbox', 'automark', get_string('automark', 'attendance')); + $mform->setType('automark', PARAM_INT); + $mform->addHelpButton('automark', 'automark', 'attendance'); + $mform->disabledif('automark', 'studentscanmark', 'notchecked'); + $mform->setDefault('automark', $this->_customdata['att']->automark); + $mgroup = array(); $mgroup[] = & $mform->createElement('text', 'studentpassword', get_string('studentpassword', 'attendance')); @@ -143,6 +156,9 @@ class mod_attendance_add_form extends moodleform { if (isset($pluginconfig->randompassword_default)) { $mform->setDefault('randompassword', $pluginconfig->randompassword_default); } + if (isset($pluginconfig->automark_default)) { + $mform->setDefault('automark', $pluginconfig->automark_default); + } $mform->addElement('text', 'subnet', get_string('requiresubnet', 'attendance')); $mform->setType('subnet', PARAM_TEXT); $mform->addHelpButton('subnet', 'requiresubnet', 'attendance'); @@ -152,14 +168,12 @@ class mod_attendance_add_form extends moodleform { } else { $mform->addElement('hidden', 'studentscanmark', '0'); $mform->settype('studentscanmark', PARAM_INT); + $mform->addElement('hidden', 'automark', '0'); + $mform->setType('automark', PARAM_INT); $mform->addElement('hidden', 'subnet', ''); $mform->setType('subnet', PARAM_TEXT); } - $mform->addElement('editor', 'sdescription', get_string('description', 'attendance'), array('rows' => 1, 'columns' => 80), - array('maxfiles' => EDITOR_UNLIMITED_FILES, 'noclean' => true, 'context' => $modcontext)); - $mform->setType('sdescription', PARAM_RAW); - // For multiple sessions. $mform->addElement('header', 'headeraddmultiplesessions', get_string('addmultiplesessions', 'attendance')); diff --git a/backup/moodle2/backup_attendance_stepslib.php b/backup/moodle2/backup_attendance_stepslib.php index 9be2593..1d67563 100644 --- a/backup/moodle2/backup_attendance_stepslib.php +++ b/backup/moodle2/backup_attendance_stepslib.php @@ -48,13 +48,13 @@ class backup_attendance_activity_structure_step extends backup_activity_structur $statuses = new backup_nested_element('statuses'); $status = new backup_nested_element('status', array('id'), array( - 'acronym', 'description', 'grade', 'studentavailability', 'visible', 'deleted', 'setnumber')); + 'acronym', 'description', 'grade', 'studentavailability', 'setunmarked', 'visible', 'deleted', 'setnumber')); $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', 'statusset', 'caleventid')); + 'subnet', 'automark', 'automarkcompleted', 'statusset', 'caleventid')); // XML nodes declaration - user data. $logs = new backup_nested_element('logs'); diff --git a/classes/structure.php b/classes/structure.php index 44c3302..757dfa7 100644 --- a/classes/structure.php +++ b/classes/structure.php @@ -70,6 +70,12 @@ class mod_attendance_structure { /** @var string subnets (IP range) for student self selection. */ public $subnet; + /** @var string subnets (IP range) for student self selection. */ + public $automark; + + /** @var boolean flag set when automarking is complete. */ + public $automarkcompleted; + /** @var int Define if session details should be shown in reports */ public $showsessiondetails; @@ -416,6 +422,10 @@ class mod_attendance_structure { foreach ($sessions as $sess) { $sess->attendanceid = $this->id; + $sess->automarkcompleted = 0; + if (!isset($sess->automark)) { + $sess->automark = 0; + } $sess->id = $DB->insert_record('attendance_sessions', $sess); $description = file_save_draft_area_files($sess->descriptionitemid, @@ -483,12 +493,17 @@ class mod_attendance_structure { $sess->studentscanmark = 0; $sess->studentpassword = ''; $sess->subnet = ''; + $sess->automark = 0; + $sess->automarkcompleted = 0; if (!empty(get_config('attendance', 'studentscanmark')) && !empty($formdata->studentscanmark)) { $sess->studentscanmark = $formdata->studentscanmark; $sess->studentpassword = $formdata->studentpassword; $sess->subnet = $formdata->subnet; + if (!empty($formdata->automark)) { + $sess->automark = $formdata->automark; + } } $sess->timemodified = time(); @@ -751,7 +766,7 @@ class mod_attendance_structure { // Add the 'temporary' users to this list. $tempusers = $DB->get_records('attendance_tempusers', array('courseid' => $this->course->id)); foreach ($tempusers as $tempuser) { - $users[] = self::tempuser_to_user($tempuser); + $users[$tempuser->studentid] = self::tempuser_to_user($tempuser); } return $users; diff --git a/classes/task/auto_mark.php b/classes/task/auto_mark.php new file mode 100644 index 0000000..9a86c47 --- /dev/null +++ b/classes/task/auto_mark.php @@ -0,0 +1,155 @@ +. + +/** + * Attendance task - auto mark. + * + * @package mod_attendance + * @copyright 2017 onwards Dan Marsden http://danmarsden.com + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_attendance\task; +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/attendance/locallib.php'); +/** + * get_scores class, used to get scores for submitted files. + * + * @package mod_attendance + * @copyright 2017 onwards Dan Marsden http://danmarsden.com + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class auto_mark extends \core\task\scheduled_task { + public function get_name() { + // Shown in admin screens. + return get_string('automarktask', 'mod_attendance'); + } + public function execute() { + global $DB; + // Create some cache vars - might be nice to restructure this and make a smaller number of sql calls. + $cachecm = array(); + $cacheatt = array(); + $cachecourse = array(); + $sessions = $DB->get_recordset_select('attendance_sessions', + 'automark = 1 AND automarkcompleted = 0 AND sessdate < ? ', array(time())); + + 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()) { + $donesomething = false; // Only trigger grades/events when an update actually occurs. + + // Store cm/att/course in cachefields so we don't make unnecessary db calls. + // Would probably be nice to grab this stuff outside of the loop. + // Make sure this status set has something to setunmarked. + $setunmarked = $DB->get_field('attendance_statuses', 'id', + array('attendanceid' => $session->attendanceid, 'setnumber' => $session->statusset, + 'setunmarked' => 1, 'deleted' => 0)); + if (empty($setunmarked)) { + mtrace("No unmarked status configured for session id: ".$session->id); + continue; + } + if (empty($cacheatt[$session->attendanceid])) { + $cacheatt[$session->attendanceid] = $DB->get_record('attendance', array('id' => $session->attendanceid)); + } + if (empty($cachecm[$session->attendanceid])) { + $cachecm[$session->attendanceid] = get_coursemodule_from_instance('attendance', + $session->attendanceid, $cacheatt[$session->attendanceid]->course); + } + $courseid = $cacheatt[$session->attendanceid]->course; + if (empty($cachecourse[$courseid])) { + $cachecourse[$courseid] = $DB->get_record('course', array('id' => $courseid)); + } + $context = \context_module::instance($cachecm[$session->attendanceid]->id); + + $pageparams = new \mod_attendance_take_page_params(); + $pageparams->group = $session->groupid; + if (empty($session->groupid)) { + $pageparams->grouptype = 0; + } else { + $pageparams->grouptype = 1; + } + $pageparams->sessionid = $session->id; + + // Get all unmarked students. + $att = new \mod_attendance_structure($cacheatt[$session->attendanceid], + $cachecm[$session->attendanceid], $cachecourse[$courseid], $context, $pageparams); + + $users = $att->get_users($session->groupid, 0); + + $existinglog = $DB->get_recordset('attendance_log', array('sessionid' => $session->id)); + $updated = 0; + + foreach ($existinglog as $log) { + if (empty($log->statusid)) { + // 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; + } + unset($users[$log->studentid]); + } + $existinglog->close(); + mtrace($updated . " session status updated"); + + $newlog = new \stdClass(); + $newlog->statusid = $setunmarked; + $newlog->timetaken = time(); + $newlog->takenby = 0; + $newlog->sessionid = $session->id; + $newlog->remarks = get_string('autorecorded', 'attendance'); + $newlog->statusset = implode(',', array_keys( (array)$att->get_statuses())); + + $added = 0; + foreach ($users as $user) { + $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->lasttakenby = 0; + $session->automarkcompleted = 1; + $DB->update_record('attendance_sessions', $session); + + if ($donesomething) { + if ($att->grade != 0) { + $att->update_users_grade(array_keys($users)); + } + + $params = array( + 'sessionid' => $att->pageparams->sessionid, + 'grouptype' => $att->pageparams->grouptype); + $event = \mod_attendance\event\attendance_taken::create(array( + 'objectid' => $att->id, + 'context' => $att->context, + 'other' => $params)); + $event->add_record_snapshot('course_modules', $att->cm); + $event->add_record_snapshot('attendance_sessions', $session); + $event->trigger(); + } + } + } + } +} \ No newline at end of file diff --git a/db/install.xml b/db/install.xml index 95dac93..f1900bb 100644 --- a/db/install.xml +++ b/db/install.xml @@ -39,6 +39,8 @@ + + @@ -80,6 +82,7 @@ + diff --git a/db/tasks.php b/db/tasks.php new file mode 100644 index 0000000..0abad70 --- /dev/null +++ b/db/tasks.php @@ -0,0 +1,36 @@ +. + +/** + * Attendance module tasks. + * + * @package mod_attendance + * @copyright 2017 Dan Marsden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = array( + array( + 'classname' => 'mod_attendance\task\auto_mark', + 'blocking' => 0, + 'minute' => '8', + 'hour' => '*', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*') +); \ No newline at end of file diff --git a/db/upgrade.php b/db/upgrade.php index 1f6f23d..574516a 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -285,5 +285,35 @@ function xmldb_attendance_upgrade($oldversion=0) { upgrade_mod_savepoint(true, 2016121310, 'attendance'); } + if ($oldversion < 2016121311) { + // Define field setunmarked to be added to attendance_statuses. + $table = new xmldb_table('attendance_statuses'); + $field = new xmldb_field('setunmarked', XMLDB_TYPE_INTEGER, '2', null, null, null, null, 'studentavailability'); + + // Conditionally launch add field setunmarked. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field setunmarked to be added to attendance_statuses. + $table = new xmldb_table('attendance_sessions'); + $field = new xmldb_field('automark', XMLDB_TYPE_INTEGER, '1', null, true, null, '0', 'subnet'); + + // Conditionally launch add field automark. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('automarkcompleted', XMLDB_TYPE_INTEGER, '1', null, true, null, '0', 'automark'); + + // Conditionally launch add field automarkcompleted. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Attendance savepoint reached. + upgrade_mod_savepoint(true, 2016121311, 'attendance'); + } + return $result; } diff --git a/defaultstatus.php b/defaultstatus.php index ba8a146..b1531f2 100644 --- a/defaultstatus.php +++ b/defaultstatus.php @@ -101,6 +101,7 @@ switch ($action) { $description = required_param_array('description', PARAM_TEXT); $grade = required_param_array('grade', PARAM_RAW); $studentavailability = required_param_array('studentavailability', PARAM_RAW); + $unmarkedstatus = optional_param('setunmarked', null, PARAM_INT); foreach ($grade as &$val) { $val = unformat_float($val); } @@ -108,8 +109,12 @@ switch ($action) { foreach ($acronym as $id => $v) { $status = $statuses[$id]; + $setunmarked = false; + if ($unmarkedstatus == $id) { + $setunmarked = true; + } $errors[$id] = attendance_update_status($status, $acronym[$id], $description[$id], $grade[$id], - null, null, null, $studentavailability[$id]); + null, null, null, $studentavailability[$id], $setunmarked); } echo $OUTPUT->notification(get_string('eventstatusupdated', 'attendance'), 'success'); diff --git a/lang/en/attendance.php b/lang/en/attendance.php index 61a3bf0..51a5c91 100644 --- a/lang/en/attendance.php +++ b/lang/en/attendance.php @@ -388,4 +388,11 @@ $string['studentavailability_help'] = 'When students are marking their own atten
If empty, this status will always be available, If set to 0 it will always be hidden to students.'; $string['somedisabledstatus'] = '(Some options have been removed as the session has started.)'; -$string['invalidstatus'] = 'You have selected an invalid status, please try again'; \ No newline at end of file +$string['invalidstatus'] = 'You have selected an invalid status, please try again'; +$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['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['studentmarking'] = 'Student recording'; +$string['automarktask'] = 'Check for closed attendance sessions that require auto marking'; +$string['autorecorded'] = 'system auto recorded'; \ No newline at end of file diff --git a/locallib.php b/locallib.php index 57866fa..05257b8 100644 --- a/locallib.php +++ b/locallib.php @@ -270,6 +270,8 @@ function attendance_add_status($status) { if (!empty($status->acronym) && !empty($status->description)) { $status->deleted = 0; $status->visible = 1; + $status->setunmarked = 0; + $id = $DB->insert_record('attendance_statuses', $status); $status->id = $id; @@ -328,10 +330,11 @@ function attendance_remove_status($status, $context = null, $cm = null) { * @param stdClass $context * @param stdClass $cm * @param int $studentavailability + * @param bool $setunmarked * @return array */ function attendance_update_status($status, $acronym, $description, $grade, $visible, - $context = null, $cm = null, $studentavailability = null) { + $context = null, $cm = null, $studentavailability = null, $setunmarked = false) { global $DB; if (empty($context)) { @@ -369,6 +372,11 @@ function attendance_update_status($status, $acronym, $description, $grade, $visi $status->studentavailability = $studentavailability; $updated[] = $studentavailability; } + if ($setunmarked) { + $status->setunmarked = 1; + } else { + $status->setunmarked = 0; + } $DB->update_record('attendance_statuses', $status); $event = \mod_attendance\event\status_updated::create(array( @@ -563,6 +571,8 @@ function attendance_construct_sessions_data_for_add($formdata) { if (isset($formdata->studentscanmark)) { // Students will be able to mark their own attendance. $sess->studentscanmark = 1; $sess->subnet = $formdata->subnet; + $sess->automark = $formdata->automark; + $sess->automarkcompleted = 0; if (!empty($formdata->randompassword)) { $sess->studentpassword = attendance_random_string(); } else { @@ -571,6 +581,8 @@ function attendance_construct_sessions_data_for_add($formdata) { } else { $sess->studentpassword = ''; $sess->subnet = ''; + $sess->automark = 0; + $sess->automarkcompleted = 0; } $sess->statusset = $formdata->statusset; @@ -593,6 +605,8 @@ function attendance_construct_sessions_data_for_add($formdata) { $sess->studentscanmark = 0; $sess->subnet = ''; $sess->studentpassword = ''; + $sess->automark = 0; + $sess->automarkcompleted = 0; if (isset($formdata->studentscanmark) && !empty($formdata->studentscanmark)) { // Students will be able to mark their own attendance. @@ -603,6 +617,9 @@ function attendance_construct_sessions_data_for_add($formdata) { $sess->studentpassword = $formdata->studentpassword; } $sess->subnet = $formdata->subnet; + if (!empty($formdata->automark)) { + $sess->automark = $formdata->automark; + } } $sess->statusset = $formdata->statusset; diff --git a/preferences.php b/preferences.php index 56762c6..21bda0b 100644 --- a/preferences.php +++ b/preferences.php @@ -129,6 +129,8 @@ switch ($att->pageparams->action) { $description = required_param_array('description', PARAM_TEXT); $grade = required_param_array('grade', PARAM_RAW); $studentavailability = optional_param_array('studentavailability', null, PARAM_RAW); + $unmarkedstatus = optional_param('setunmarked', null, PARAM_INT); + foreach ($grade as &$val) { $val = unformat_float($val); } @@ -136,8 +138,12 @@ switch ($att->pageparams->action) { foreach ($acronym as $id => $v) { $status = $statuses[$id]; + $setunmarked = false; + if ($unmarkedstatus == $id) { + $setunmarked = true; + } $errors[$id] = attendance_update_status($status, $acronym[$id], $description[$id], $grade[$id], - null, $att->context, $att->cm, $studentavailability[$id]); + null, $att->context, $att->cm, $studentavailability[$id], $setunmarked); } attendance_update_users_grade($att); break; diff --git a/renderer.php b/renderer.php index 706bf05..6bc75da 100644 --- a/renderer.php +++ b/renderer.php @@ -1617,7 +1617,10 @@ class mod_attendance_renderer extends plugin_renderer_base { if ($studentscanmark) { $table->head[] = get_string('studentavailability', 'attendance'). $this->output->help_icon('studentavailability', 'attendance'); + $table->align[] = 'center'; + $table->head[] = get_string('setunmarked', 'attendance'). + $this->output->help_icon('setunmarked', 'attendance'); $table->align[] = 'center'; } $table->head[] = get_string('action'); @@ -1641,7 +1644,12 @@ class mod_attendance_renderer extends plugin_renderer_base { $emptydescription; $cells[] = $this->construct_text_input('grade['.$st->id.']', 4, 4, $st->grade); if ($studentscanmark) { + $checked = ''; + if ($st->setunmarked) { + $checked = ' checked '; + } $cells[] = $this->construct_text_input('studentavailability['.$st->id.']', 4, 5, $st->studentavailability); + $cells[] = ''; } $cells[] = $this->construct_preferences_actions_icons($st, $prefdata); diff --git a/settings.php b/settings.php index 4b9ec09..3760ec3 100644 --- a/settings.php +++ b/settings.php @@ -71,6 +71,9 @@ 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)); + $settings->add(new admin_setting_configcheckbox('attendance/randompassword_default', get_string('randompassword', 'attendance'), '', 0)); } diff --git a/update_form.php b/update_form.php index f149b46..2ec4029 100644 --- a/update_form.php +++ b/update_form.php @@ -67,7 +67,9 @@ class mod_attendance_update_form extends moodleform { 'sdescription' => $sess->description_editor, 'studentscanmark' => $sess->studentscanmark, 'studentpassword' => $sess->studentpassword, - 'subnet' => $sess->subnet); + 'subnet' => $sess->subnet, + 'automark' => $sess->automark, + 'automarkcompleted' => 0); $mform->addElement('header', 'general', get_string('changesession', 'attendance')); @@ -91,11 +93,23 @@ class mod_attendance_update_form extends moodleform { attendance_get_setname($this->_customdata['att']->id, $sess->statusset)); } + $mform->addElement('editor', 'sdescription', get_string('description', 'attendance'), + array('rows' => 1, 'columns' => 80), $defopts); + $mform->setType('sdescription', PARAM_RAW); + // Students can mark own attendance. if (!empty(get_config('attendance', 'studentscanmark'))) { + $mform->addElement('header', 'headerstudentmarking', get_string('studentmarking', 'attendance'), true); + $mform->setExpanded('headerstudentmarking'); + $mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark', 'attendance')); $mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance'); + $mform->addElement('checkbox', 'automark', get_string('automark', 'attendance')); + $mform->setType('automark', PARAM_INT); + $mform->addHelpButton('automark', 'automark', 'attendance'); + $mform->disabledif('automark', 'studentscanmark', 'notchecked'); + $mform->addElement('text', 'studentpassword', get_string('studentpassword', 'attendance')); $mform->setType('studentpassword', PARAM_TEXT); $mform->addHelpButton('studentpassword', 'passwordgrp', 'attendance'); @@ -105,17 +119,21 @@ class mod_attendance_update_form extends moodleform { $mform->setType('subnet', PARAM_TEXT); $mform->addHelpButton('subnet', 'requiresubnet', 'attendance'); $mform->disabledif('subnet', 'studentscanmark', 'notchecked'); + + $mform->addElement('hidden', 'automarkcompleted', '0'); + $mform->settype('automarkcompleted', PARAM_INT); + } else { $mform->addElement('hidden', 'studentscanmark', '0'); $mform->settype('studentscanmark', PARAM_INT); $mform->addElement('hidden', 'subnet', '0'); $mform->settype('subnet', PARAM_TEXT); + $mform->addElement('hidden', 'automark', '0'); + $mform->settype('automark', PARAM_INT); + $mform->addElement('hidden', 'automarkcompleted', '0'); + $mform->settype('automarkcompleted', PARAM_INT); } - $mform->addElement('editor', 'sdescription', get_string('description', 'attendance'), - array('rows' => 1, 'columns' => 80), $defopts); - $mform->setType('sdescription', PARAM_RAW); - $mform->setDefaults($data); $this->add_action_buttons(true); diff --git a/version.php b/version.php index 2520c16..491864d 100644 --- a/version.php +++ b/version.php @@ -23,9 +23,9 @@ */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016121310; +$plugin->version = 2016121311; $plugin->requires = 2016111800; -$plugin->release = '3.2.9'; +$plugin->release = '3.2.10'; $plugin->maturity = MATURITY_STABLE; $plugin->cron = 0; $plugin->component = 'mod_attendance';