From c1f1f9f7655c33680496875239f5ee6b665dba3e Mon Sep 17 00:00:00 2001 From: Chris Wharton Date: Thu, 16 Nov 2017 15:40:29 +1300 Subject: [PATCH] Allow bulk importing of attendance sessions The administrator can upload a CSV file containing attendance information for courses that have existing attendance activities. --- classes/event/sessions_imported.php | 98 +++++ classes/form/import/sessions.php | 87 +++++ classes/form/import/sessions_confirm.php | 75 ++++ classes/import/sessions.php | 444 +++++++++++++++++++++++ import/sessions.php | 91 +++++ lang/en/attendance.php | 27 +- lib.php | 3 + version.php | 2 +- 8 files changed, 825 insertions(+), 2 deletions(-) create mode 100644 classes/event/sessions_imported.php create mode 100644 classes/form/import/sessions.php create mode 100644 classes/form/import/sessions_confirm.php create mode 100644 classes/import/sessions.php create mode 100644 import/sessions.php diff --git a/classes/event/sessions_imported.php b/classes/event/sessions_imported.php new file mode 100644 index 0000000..e33ecd4 --- /dev/null +++ b/classes/event/sessions_imported.php @@ -0,0 +1,98 @@ +. + +/** + * This file contains an event for when an attendance sessions is imported. + * + * @package mod_attendance + * @author Chris Wharton + * @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 + * @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' + ); + } +} diff --git a/classes/form/import/sessions.php b/classes/form/import/sessions.php new file mode 100644 index 0000000..37935de --- /dev/null +++ b/classes/form/import/sessions.php @@ -0,0 +1,87 @@ +. + +/** + * This file contains the form for importing sessions from a file. + * + * @package mod_attendance + * @author Chris Wharton + * @copyright 2017 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_attendance\form\import; + +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 + * @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); + } +} diff --git a/classes/form/import/sessions_confirm.php b/classes/form/import/sessions_confirm.php new file mode 100644 index 0000000..ff67e4c --- /dev/null +++ b/classes/form/import/sessions_confirm.php @@ -0,0 +1,75 @@ +. + +/** + * Import attendance sessions. + * + * @package mod_attendance + * @author Chris Wharton + * @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 + * @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')); + } +} diff --git a/classes/import/sessions.php b/classes/import/sessions.php new file mode 100644 index 0000000..799454a --- /dev/null +++ b/classes/import/sessions.php @@ -0,0 +1,444 @@ +. +namespace mod_attendance\import; + +use csv_import_reader; +use mod_attendance_notifyqueue; +use mod_attendance_structure; +use stdClass; + +/** + * Import attendance sessions. + * + * @package mod_attendance + * @author Chris Wharton + * @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(); + + protected $mappings = array(); + + protected $importid = 0; + + protected $importer = null; + + 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 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 + * data array 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 + ); + } 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 + ); + } + } + + /** + * Get the a column from the imported data. + * + * @param + * array The imported raw row + * @param + * 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'); + + $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'] = '

' . $this->get_column_data($row, $mapping['description']) . '

'; + $session->sdescription['format'] = FORMAT_HTML; + $session->sdescription['itemid'] = 0; + + $session->repeaton = $this->get_column_data($row, $mapping['repeaton']); + $session->repeatevery = $this->get_column_data($row, $mapping['repeatevery']); + $session->repeatuntil = $this->get_column_data($row, $mapping['repeatuntil']); + $session->studentscanmark = $this->get_column_data($row, $mapping['studentscanmark']); + $session->passwordgrp = $this->get_column_data($row, $mapping['passwordgrp']); + $session->randompassword = $this->get_column_data($row, $mapping['randompassword']); + + // Set session subnet restriction. Use the default activity level subnet if there isn't one set for this session. + $session->subnet = $this->get_column_data($row, $mapping['subnet']); + if (empty($session->subnet)) { + $session->usedefaultsubnet = '1'; + } else { + $session->usedefaultsubnet = ''; + } + + $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) { + // 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); + $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; + } +} + diff --git a/import/sessions.php b/import/sessions.php new file mode 100644 index 0000000..1fa9f1a --- /dev/null +++ b/import/sessions.php @@ -0,0 +1,91 @@ +. + +/** + * Import attendance sessions. + * + * @package mod_attendance + * @author Chris Wharton + * @copyright 2017 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +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(); diff --git a/lang/en/attendance.php b/lang/en/attendance.php index f60e08b..f7c44b9 100644 --- a/lang/en/attendance.php +++ b/lang/en/attendance.php @@ -92,10 +92,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})?
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. @@ -106,6 +109,8 @@ The sessions begin on the date of the base session and continue until the \'repe * Repeat until: Select the last day of class (the last day you want to take attendance). '; $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'; @@ -173,11 +178,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.
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.'; +$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 +199,7 @@ $string['eventscreated'] = 'Calendar events created'; $string['eventsdeleted'] = 'Calendar events deleted'; $string['eventsessionadded'] = 'Session added'; $string['eventsessiondeleted'] = 'Session deleted'; +$string['eventsessionsimported'] = 'Sessions imported'; $string['eventsessionupdated'] = 'Session updated'; $string['eventstatusadded'] = 'Status added'; $string['eventstatusupdated'] = 'Status updated'; @@ -200,6 +213,8 @@ $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. @@ -207,6 +222,10 @@ $string['hiddensessions_help'] = 'Sessions are hidden if they are scheduled befo 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['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,6 +234,7 @@ $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'; @@ -303,6 +323,7 @@ $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['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.

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 +387,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,13 +398,13 @@ $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'; @@ -397,6 +420,7 @@ $string['somedisabledstatus'] = '(Some options have been removed as the session $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 +449,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.'; diff --git a/lib.php b/lib.php index c2af403..110a420 100644 --- a/lib.php +++ b/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(); diff --git a/version.php b/version.php index 243e293..4237d06 100644 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016121322; +$plugin->version = 2016121323; $plugin->requires = 2016111800; $plugin->release = '3.2.17'; $plugin->maturity = MATURITY_STABLE;