2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Import attendance sessions class.
20 * @package mod_attendance
21 * @author Chris Wharton <chriswharton@catalyst.net.nz>
22 * @copyright 2017 Catalyst IT
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 namespace mod_attendance\import
;
28 defined('MOODLE_INTERNAL') ||
die();
30 use csv_import_reader
;
31 use mod_attendance_notifyqueue
;
32 use mod_attendance_structure
;
36 * Import attendance sessions.
38 * @package mod_attendance
39 * @author Chris Wharton <chriswharton@catalyst.net.nz>
40 * @copyright 2017 Catalyst IT
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 /** @var string $error The errors message from reading the xml */
46 protected $error = '';
48 /** @var array $sessions The sessions info */
49 protected $sessions = array();
51 /** @var array $mappings The mappings info */
52 protected $mappings = array();
54 /** @var int The id of the csv import */
55 protected $importid = 0;
57 /** @var csv_import_reader|null $importer */
58 protected $importer = null
;
60 /** @var array $foundheaders */
61 protected $foundheaders = array();
63 /** @var bool $useprogressbar Control whether importing should use progress bars or not. */
64 protected $useprogressbar = false
;
66 /** @var \core\progress\display_if_slow|null $progress The progress bar instance. */
67 protected $progress = null
;
70 * Store an error message for display later
74 public function fail($msg) {
80 * Get the CSV import id
82 * @return string The import id.
84 public function get_importid() {
85 return $this->importid
;
89 * Get the list of headers required for import.
91 * @return array The headers (lang strings)
93 public static function list_required_headers() {
95 get_string('course', 'attendance'),
96 get_string('groups', 'attendance'),
97 get_string('sessiondate', 'attendance'),
98 get_string('from', 'attendance'),
99 get_string('to', 'attendance'),
100 get_string('description', 'attendance'),
101 get_string('repeaton', 'attendance'),
102 get_string('repeatevery', 'attendance'),
103 get_string('repeatuntil', 'attendance'),
104 get_string('studentscanmark', 'attendance'),
105 get_string('passwordgrp', 'attendance'),
106 get_string('randompassword', 'attendance'),
107 get_string('subnet', 'attendance'),
108 get_string('automark', 'attendance'),
109 get_string('autoassignstatus', 'attendance'),
110 get_string('absenteereport', 'attendance')
115 * Get the list of headers found in the import.
117 * @return array The found headers (names from import)
119 public function list_found_headers() {
120 return $this->foundheaders
;
124 * Read the data from the mapping form.
126 * @param array $data The mapping data.
128 protected function read_mapping_data($data) {
131 'course' => $data->header0
,
132 'groups' => $data->header1
,
133 'sessiondate' => $data->header2
,
134 'from' => $data->header3
,
135 'to' => $data->header4
,
136 'description' => $data->header5
,
137 'repeaton' => $data->header6
,
138 'repeatevery' => $data->header7
,
139 'repeatuntil' => $data->header8
,
140 'studentscanmark' => $data->header9
,
141 'passwordgrp' => $data->header10
,
142 'randompassword' => $data->header11
,
143 'subnet' => $data->header12
,
144 'automark' => $data->header13
,
145 'autoassignstatus' => $data->header14
,
146 'absenteereport' => $data->header15
159 'studentscanmark' => 9,
161 'randompassword' => 11,
164 'autoassignstatus' => 14,
165 'absenteereport' => 15
171 * Get the a column from the imported data.
173 * @param array $row The imported raw row
174 * @param int $index The column index we want
175 * @return string The column data.
177 protected function get_column_data($row, $index) {
181 return isset($row[$index]) ?
$row[$index] : '';
185 * Constructor - parses the raw text for sanity.
187 * @param string $text The raw csv text.
188 * @param string $encoding The encoding of the csv file.
189 * @param string $delimiter The specified delimiter for the file.
190 * @param string $importid The id of the csv import.
191 * @param array $mappingdata The mapping data from the import form.
192 * @param bool $useprogressbar Whether progress bar should be displayed, to avoid html output on CLI.
194 public function __construct($text = null
, $encoding = null
, $delimiter = null
, $importid = 0,
195 $mappingdata = null
, $useprogressbar = false
) {
198 require_once($CFG->libdir
. '/csvlib.class.php');
200 $pluginconfig = get_config('attendance');
205 if ($text === null
) {
208 $this->importid
= csv_import_reader
::get_new_iid($type);
210 $this->importer
= new csv_import_reader($this->importid
, $type);
212 if (! $this->importer
->load_csv_content($text, $encoding, $delimiter)) {
213 $this->fail(get_string('invalidimportfile', 'attendance'));
214 $this->importer
->cleanup();
218 $this->importid
= $importid;
220 $this->importer
= new csv_import_reader($this->importid
, $type);
223 if (! $this->importer
->init()) {
224 $this->fail(get_string('invalidimportfile', 'attendance'));
225 $this->importer
->cleanup();
229 $this->foundheaders
= $this->importer
->get_columns();
230 $this->useprogressbar
= $useprogressbar;
235 while ($row = $this->importer
->next()) {
236 // This structure mimics what the UI form returns.
237 $mapping = $this->read_mapping_data($mappingdata);
239 $session = new stdClass();
240 $session->course
= $this->get_column_data($row, $mapping['course']);
241 if (empty($session->course
)) {
242 \mod_attendance_notifyqueue
::notify_problem(get_string('error:sessioncourseinvalid', 'attendance'));
246 // Handle multiple group assignments per session. Expect semicolon separated group names.
247 $groups = $this->get_column_data($row, $mapping['groups']);
248 if (! empty($groups)) {
249 $session->groups
= explode(';', $groups);
250 $session->sessiontype
= \mod_attendance_structure
::SESSION_GROUP
;
252 $session->sessiontype
= \mod_attendance_structure
::SESSION_COMMON
;
255 // Expect standardised date format, eg YYYY-MM-DD.
256 $sessiondate = strtotime($this->get_column_data($row, $mapping['sessiondate']));
257 if ($sessiondate === false
) {
258 \mod_attendance_notifyqueue
::notify_problem(get_string('error:sessiondateinvalid', 'attendance'));
261 $session->sessiondate
= $sessiondate;
263 // Expect standardised time format, eg HH:MM.
264 $from = $this->get_column_data($row, $mapping['from']);
266 \mod_attendance_notifyqueue
::notify_problem(get_string('error:sessionstartinvalid', 'attendance'));
269 $from = explode(':', $from);
270 $session->sestime
['starthour'] = $from[0];
271 $session->sestime
['startminute'] = $from[1];
273 $to = $this->get_column_data($row, $mapping['to']);
275 \mod_attendance_notifyqueue
::notify_problem(get_string('error:sessionendinvalid', 'attendance'));
278 $to = explode(':', $to);
279 $session->sestime
['endhour'] = $to[0];
280 $session->sestime
['endminute'] = $to[1];
282 // Wrap the plain text description in html tags.
283 $session->sdescription
['text'] = '<p>' . $this->get_column_data($row, $mapping['description']) . '</p>';
284 $session->sdescription
['format'] = FORMAT_HTML
;
285 $session->sdescription
['itemid'] = 0;
286 $session->passwordgrp
= $this->get_column_data($row, $mapping['passwordgrp']);
287 $session->subnet
= $this->get_column_data($row, $mapping['subnet']);
288 // Set session subnet restriction. Use the default activity level subnet if there isn't one set for this session.
289 if (empty($session->subnet
)) {
290 $session->usedefaultsubnet
= '1';
292 $session->usedefaultsubnet
= '';
295 if ($mapping['studentscanmark'] == -1) {
296 $session->studentscanmark
= $pluginconfig->studentscanmark_default
;
298 $session->studentscanmark
= $this->get_column_data($row, $mapping['studentscanmark']);
300 if ($mapping['randompassword'] == -1) {
301 $session->randompassword
= $pluginconfig->randompassword_default
;
303 $session->randompassword
= $this->get_column_data($row, $mapping['randompassword']);
305 if ($mapping['automark'] == -1) {
306 $session->automark
= $pluginconfig->automark_default
;
308 $session->automark
= $this->get_column_data($row, $mapping['automark']);
310 if ($mapping['autoassignstatus'] == -1) {
311 $session->autoassignstatus
= $pluginconfig->autoassignstatus
;
313 $session->autoassignstatus
= $this->get_column_data($row, $mapping['autoassignstatus']);
315 if ($mapping['absenteereport'] == -1) {
316 $session->absenteereport
= $pluginconfig->absenteereport_default
;
318 $session->absenteereport
= $this->get_column_data($row, $mapping['absenteereport']);
320 $session->statusset
= 0;
322 $sessions[] = $session;
324 $this->sessions
= $sessions;
326 $this->importer
->close();
327 if ($this->sessions
== null
) {
328 $this->fail(get_string('invalidimportfile', 'attendance'));
331 // We are calling from browser, display progress bar.
332 if ($this->useprogressbar
=== true
) {
333 $this->progress
= new \core\progress\
display_if_slow(get_string('processingfile', 'attendance'));
334 $this->progress
->start_html();
336 // Avoid html output on CLI scripts.
337 $this->progress
= new \core\progress
\none
();
339 $this->progress
->start_progress('', count($this->sessions
));
340 raise_memory_limit(MEMORY_EXTRA
);
341 $this->progress
->end_progress();
348 * @return array of errors from parsing the xml.
350 public function get_error() {
355 * Create sessions using the CSV data.
359 public function import() {
362 // Count of sessions added.
365 foreach ($this->sessions
as $session) {
367 // Check course shortname matches.
368 if ($DB->record_exists('course', array(
369 'shortname' => $session->course
372 $course = $DB->get_record('course', array(
373 'shortname' => $session->course
376 // Check course has activities.
377 if ($DB->record_exists('attendance', array(
378 'course' => $course->id
380 // Translate group names to group IDs. They are unique per course.
381 if ($session->sessiontype
=== \mod_attendance_structure
::SESSION_GROUP
) {
382 foreach ($session->groups
as $groupname) {
383 $gid = groups_get_group_by_name($course->id
, $groupname);
384 if ($gid === false
) {
385 \mod_attendance_notifyqueue
::notify_problem(get_string('sessionunknowngroup',
386 'attendance', $groupname));
391 $session->groups
= $groupids;
394 // Get activities in course.
395 $activities = $DB->get_recordset('attendance', array(
396 'course' => $course->id
399 foreach ($activities as $activity) {
400 // Build the session data.
401 $cm = get_coursemodule_from_instance('attendance', $activity->id
, $course->id
);
402 if (!empty($cm->deletioninprogress
)) {
403 // Don't do anything if this attendance is in recycle bin.
406 $att = new mod_attendance_structure($activity, $cm, $course);
407 $sessions = attendance_construct_sessions_data_for_add($session, $att);
409 foreach ($sessions as $index => $sess) {
410 // Check for duplicate sessions.
411 if ($this->session_exists($sess)) {
412 mod_attendance_notifyqueue
::notify_message(get_string('sessionduplicate', 'attendance', (array(
413 'course' => $session->course
,
414 'activity' => $cm->name
416 unset($sessions[$index]);
421 if (! empty($sessions)) {
422 $att->add_sessions($sessions);
425 $activities->close();
427 mod_attendance_notifyqueue
::notify_problem(get_string('error:coursehasnoattendance',
428 'attendance', $session->course
));
431 mod_attendance_notifyqueue
::notify_problem(get_string('error:coursenotfound', 'attendance', $session->course
));
435 $message = get_string('sessionsgenerated', 'attendance', $okcount);
437 mod_attendance_notifyqueue
::notify_message($message);
439 mod_attendance_notifyqueue
::notify_success($message);
442 // Trigger a sessions imported event.
443 $event = \mod_attendance\event\sessions_imported
::create(array(
445 'context' => \context_system
::instance(),
455 * Check if an identical session exists.
457 * @param stdClass $session
460 private function session_exists(stdClass
$session) {
463 $check = clone $session;
465 // Remove the properties that aren't useful to check.
466 unset($check->description
);
467 unset($check->descriptionitemid
);
468 unset($check->timemodified
);
469 $check = (array) $check;
471 if ($DB->record_exists('attendance_sessions', $check)) {