add setting to allow sessiont description to show in report.
[moodle-mod_attendance.git] / locallib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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.
13 //
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/>.
16
17 /**
18 * local functions and constants for module attendance
19 *
20 * @package mod_attendance
21 * @copyright 2011 Artem Andreev <andreev.artem@gmail.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25 defined('MOODLE_INTERNAL') || die();
26
27 require_once($CFG->libdir . '/gradelib.php');
28 require_once(dirname(__FILE__).'/renderhelpers.php');
29
30 define('ATT_VIEW_DAYS', 1);
31 define('ATT_VIEW_WEEKS', 2);
32 define('ATT_VIEW_MONTHS', 3);
33 define('ATT_VIEW_ALLPAST', 4);
34 define('ATT_VIEW_ALL', 5);
35 define('ATT_VIEW_NOTPRESENT', 6);
36 define('ATT_VIEW_SUMMARY', 7);
37
38 define('ATT_SORT_DEFAULT', 0);
39 define('ATT_SORT_LASTNAME', 1);
40 define('ATT_SORT_FIRSTNAME', 2);
41
42 define('ATTENDANCE_AUTOMARK_DISABLED', 0);
43 define('ATTENDANCE_AUTOMARK_ALL', 1);
44 define('ATTENDANCE_AUTOMARK_CLOSE', 2);
45
46 // Max number of sessions available in the warnings set form to trigger warnings.
47 define('ATTENDANCE_MAXWARNAFTER', 100);
48
49 /**
50 * Get statuses,
51 *
52 * @param int $attid
53 * @param bool $onlyvisible
54 * @param int $statusset
55 * @return array
56 */
57 function attendance_get_statuses($attid, $onlyvisible=true, $statusset = -1) {
58 global $DB;
59
60 // Set selector.
61 $params = array('aid' => $attid);
62 $setsql = '';
63 if ($statusset >= 0) {
64 $params['statusset'] = $statusset;
65 $setsql = ' AND setnumber = :statusset ';
66 }
67
68 if ($onlyvisible) {
69 $statuses = $DB->get_records_select('attendance_statuses', "attendanceid = :aid AND visible = 1 AND deleted = 0 $setsql",
70 $params, 'setnumber ASC, grade DESC');
71 } else {
72 $statuses = $DB->get_records_select('attendance_statuses', "attendanceid = :aid AND deleted = 0 $setsql",
73 $params, 'setnumber ASC, grade DESC');
74 }
75
76 return $statuses;
77 }
78
79 /**
80 * Get the name of the status set.
81 *
82 * @param int $attid
83 * @param int $statusset
84 * @param bool $includevalues
85 * @return string
86 */
87 function attendance_get_setname($attid, $statusset, $includevalues = true) {
88 $statusname = get_string('statusset', 'mod_attendance', $statusset + 1);
89 if ($includevalues) {
90 $statuses = attendance_get_statuses($attid, true, $statusset);
91 $statusesout = array();
92 foreach ($statuses as $status) {
93 $statusesout[] = $status->acronym;
94 }
95 if ($statusesout) {
96 if (count($statusesout) > 6) {
97 $statusesout = array_slice($statusesout, 0, 6);
98 $statusesout[] = '&helip;';
99 }
100 $statusesout = implode(' ', $statusesout);
101 $statusname .= ' ('.$statusesout.')';
102 }
103 }
104
105 return $statusname;
106 }
107
108 /**
109 * Get users courses and the relevant attendances.
110 *
111 * @param int $userid
112 * @return array
113 */
114 function attendance_get_user_courses_attendances($userid) {
115 global $DB;
116
117 $usercourses = enrol_get_users_courses($userid);
118
119 list($usql, $uparams) = $DB->get_in_or_equal(array_keys($usercourses), SQL_PARAMS_NAMED, 'cid0');
120
121 $sql = "SELECT att.id as attid, att.course as courseid, course.fullname as coursefullname,
122 course.startdate as coursestartdate, att.name as attname, att.grade as attgrade
123 FROM {attendance} att
124 JOIN {course} course
125 ON att.course = course.id
126 WHERE att.course $usql
127 ORDER BY coursefullname ASC, attname ASC";
128
129 $params = array_merge($uparams, array('uid' => $userid));
130
131 return $DB->get_records_sql($sql, $params);
132 }
133
134 /**
135 * Used to calculate a fraction based on the part and total values
136 *
137 * @param float $part - part of the total value
138 * @param float $total - total value.
139 * @return float the calculated fraction.
140 */
141 function attendance_calc_fraction($part, $total) {
142 if ($total == 0) {
143 return 0;
144 } else {
145 return $part / $total;
146 }
147 }
148
149 /**
150 * Check to see if statusid in use to help prevent deletion etc.
151 *
152 * @param integer $statusid
153 */
154 function attendance_has_logs_for_status($statusid) {
155 global $DB;
156 return $DB->record_exists('attendance_log', array('statusid' => $statusid));
157 }
158
159 /**
160 * Helper function to add sessiondate_selector to add/update forms.
161 *
162 * @param MoodleQuickForm $mform
163 */
164 function attendance_form_sessiondate_selector (MoodleQuickForm $mform) {
165
166 $mform->addElement('date_selector', 'sessiondate', get_string('sessiondate', 'attendance'));
167
168 for ($i = 0; $i <= 23; $i++) {
169 $hours[$i] = sprintf("%02d", $i);
170 }
171 for ($i = 0; $i < 60; $i += 5) {
172 $minutes[$i] = sprintf("%02d", $i);
173 }
174
175 $sesendtime = array();
176 $sesendtime[] =& $mform->createElement('static', 'from', '', get_string('from', 'attendance'));
177 $sesendtime[] =& $mform->createElement('select', 'starthour', get_string('hour', 'form'), $hours, false, true);
178 $sesendtime[] =& $mform->createElement('select', 'startminute', get_string('minute', 'form'), $minutes, false, true);
179 $sesendtime[] =& $mform->createElement('static', 'to', '', get_string('to', 'attendance'));
180 $sesendtime[] =& $mform->createElement('select', 'endhour', get_string('hour', 'form'), $hours, false, true);
181 $sesendtime[] =& $mform->createElement('select', 'endminute', get_string('minute', 'form'), $minutes, false, true);
182 $mform->addGroup($sesendtime, 'sestime', get_string('time', 'attendance'), array(' '), true);
183 }
184
185 /**
186 * Count the number of status sets that exist for this instance.
187 *
188 * @param int $attendanceid
189 * @return int
190 */
191 function attendance_get_max_statusset($attendanceid) {
192 global $DB;
193
194 $max = $DB->get_field_sql('SELECT MAX(setnumber) FROM {attendance_statuses} WHERE attendanceid = ? AND deleted = 0',
195 array($attendanceid));
196 if ($max) {
197 return $max;
198 }
199 return 0;
200 }
201
202 /**
203 * Returns the maxpoints for each statusset
204 *
205 * @param array $statuses
206 * @return array
207 */
208 function attendance_get_statusset_maxpoints($statuses) {
209 $statussetmaxpoints = array();
210 foreach ($statuses as $st) {
211 if (!isset($statussetmaxpoints[$st->setnumber])) {
212 $statussetmaxpoints[$st->setnumber] = $st->grade;
213 }
214 }
215 return $statussetmaxpoints;
216 }
217
218 /**
219 * Update user grades
220 *
221 * @param mod_attendance_structure|stdClass $attendance
222 * @param array $userids
223 */
224 function attendance_update_users_grade($attendance, $userids=array()) {
225 global $DB;
226
227 if (empty($attendance->grade)) {
228 return false;
229 }
230
231 list($course, $cm) = get_course_and_cm_from_instance($attendance->id, 'attendance');
232
233 $summary = new mod_attendance_summary($attendance->id, $userids);
234
235 if (empty($userids)) {
236 $context = context_module::instance($cm->id);
237 $userids = array_keys(get_enrolled_users($context, 'mod/attendance:canbelisted', 0, 'u.id'));
238 }
239
240 if ($attendance->grade < 0) {
241 $dbparams = array('id' => -($attendance->grade));
242 $scale = $DB->get_record('scale', $dbparams);
243 $scalearray = explode(',', $scale->scale);
244 $attendancegrade = count($scalearray);
245 } else {
246 $attendancegrade = $attendance->grade;
247 }
248
249 $grades = array();
250 foreach ($userids as $userid) {
251 $grades[$userid] = new stdClass();
252 $grades[$userid]->userid = $userid;
253
254 if ($summary->has_taken_sessions($userid)) {
255 $usersummary = $summary->get_taken_sessions_summary_for($userid);
256 $grades[$userid]->rawgrade = $usersummary->takensessionspercentage * $attendancegrade;
257 } else {
258 $grades[$userid]->rawgrade = null;
259 }
260 }
261
262 return grade_update('mod/attendance', $course->id, 'mod', 'attendance', $attendance->id, 0, $grades);
263 }
264
265 /**
266 * Add an attendance status variable
267 *
268 * @param stdClass $status
269 * @return bool
270 */
271 function attendance_add_status($status) {
272 global $DB;
273 if (empty($status->context)) {
274 $status->context = context_system::instance();
275 }
276
277 if (!empty($status->acronym) && !empty($status->description)) {
278 $status->deleted = 0;
279 $status->visible = 1;
280 $status->setunmarked = 0;
281
282 $id = $DB->insert_record('attendance_statuses', $status);
283 $status->id = $id;
284
285 $event = \mod_attendance\event\status_added::create(array(
286 'objectid' => $status->attendanceid,
287 'context' => $status->context,
288 'other' => array('acronym' => $status->acronym,
289 'description' => $status->description,
290 'grade' => $status->grade)));
291 if (!empty($status->cm)) {
292 $event->add_record_snapshot('course_modules', $status->cm);
293 }
294 $event->add_record_snapshot('attendance_statuses', $status);
295 $event->trigger();
296 return true;
297 } else {
298 return false;
299 }
300 }
301
302 /**
303 * Remove a status variable from an attendance instance
304 *
305 * @param stdClass $status
306 * @param stdClass $context
307 * @param stdClass $cm
308 */
309 function attendance_remove_status($status, $context = null, $cm = null) {
310 global $DB;
311 if (empty($context)) {
312 $context = context_system::instance();
313 }
314 $DB->set_field('attendance_statuses', 'deleted', 1, array('id' => $status->id));
315 $event = \mod_attendance\event\status_removed::create(array(
316 'objectid' => $status->id,
317 'context' => $context,
318 'other' => array(
319 'acronym' => $status->acronym,
320 'description' => $status->description
321 )));
322 if (!empty($cm)) {
323 $event->add_record_snapshot('course_modules', $cm);
324 }
325 $event->add_record_snapshot('attendance_statuses', $status);
326 $event->trigger();
327 }
328
329 /**
330 * Update status variable for a particular Attendance module instance
331 *
332 * @param stdClass $status
333 * @param string $acronym
334 * @param string $description
335 * @param int $grade
336 * @param bool $visible
337 * @param stdClass $context
338 * @param stdClass $cm
339 * @param int $studentavailability
340 * @param bool $setunmarked
341 * @return array
342 */
343 function attendance_update_status($status, $acronym, $description, $grade, $visible,
344 $context = null, $cm = null, $studentavailability = null, $setunmarked = false) {
345 global $DB;
346
347 if (empty($context)) {
348 $context = context_system::instance();
349 }
350
351 if (isset($visible)) {
352 $status->visible = $visible;
353 $updated[] = $visible ? get_string('show') : get_string('hide');
354 } else if (empty($acronym) || empty($description)) {
355 return array('acronym' => $acronym, 'description' => $description);
356 }
357
358 $updated = array();
359
360 if ($acronym) {
361 $status->acronym = $acronym;
362 $updated[] = $acronym;
363 }
364 if ($description) {
365 $status->description = $description;
366 $updated[] = $description;
367 }
368 if (isset($grade)) {
369 $status->grade = $grade;
370 $updated[] = $grade;
371 }
372 if (isset($studentavailability)) {
373 if (empty($studentavailability)) {
374 if ($studentavailability !== '0') {
375 $studentavailability = null;
376 }
377 }
378
379 $status->studentavailability = $studentavailability;
380 $updated[] = $studentavailability;
381 }
382 if ($setunmarked) {
383 $status->setunmarked = 1;
384 } else {
385 $status->setunmarked = 0;
386 }
387 $DB->update_record('attendance_statuses', $status);
388
389 $event = \mod_attendance\event\status_updated::create(array(
390 'objectid' => $status->attendanceid,
391 'context' => $context,
392 'other' => array('acronym' => $acronym, 'description' => $description, 'grade' => $grade,
393 'updated' => implode(' ', $updated))));
394 if (!empty($cm)) {
395 $event->add_record_snapshot('course_modules', $cm);
396 }
397 $event->add_record_snapshot('attendance_statuses', $status);
398 $event->trigger();
399 }
400
401 /**
402 * Similar to core random_string function but only lowercase letters.
403 * designed to make it relatively easy to provide a simple password in class.
404 *
405 * @param int $length The length of the string to be created.
406 * @return string
407 */
408 function attendance_random_string($length=6) {
409 $randombytes = random_bytes_emulate($length);
410 $pool = 'abcdefghijklmnopqrstuvwxyz';
411 $pool .= '0123456789';
412 $poollen = strlen($pool);
413 $string = '';
414 for ($i = 0; $i < $length; $i++) {
415 $rand = ord($randombytes[$i]);
416 $string .= substr($pool, ($rand % ($poollen)), 1);
417 }
418 return $string;
419 }
420
421 /**
422 * Check to see if this session is open for student marking.
423 *
424 * @param stdclass $sess the session record from attendance_sessions.
425 * @return boolean
426 */
427 function attendance_can_student_mark($sess) {
428 $canmark = false;
429 $attconfig = get_config('attendance');
430 if (!empty($attconfig->studentscanmark) && !empty($sess->studentscanmark)) {
431 if (empty($attconfig->studentscanmarksessiontime)) {
432 $canmark = true;
433 } else {
434 $duration = $sess->duration;
435 if (empty($duration)) {
436 $duration = $attconfig->studentscanmarksessiontimeend * 60;
437 }
438 if ($sess->sessdate < time() && time() < ($sess->sessdate + $duration)) {
439 $canmark = true;
440 }
441 }
442 }
443 return $canmark;
444 }
445
446 /**
447 * Generate worksheet for Attendance export
448 *
449 * @param stdclass $data The data for the report
450 * @param string $filename The name of the file
451 * @param string $format excel|ods
452 *
453 */
454 function attendance_exporttotableed($data, $filename, $format) {
455 global $CFG;
456
457 if ($format === 'excel') {
458 require_once("$CFG->libdir/excellib.class.php");
459 $filename .= ".xls";
460 $workbook = new MoodleExcelWorkbook("-");
461 } else {
462 require_once("$CFG->libdir/odslib.class.php");
463 $filename .= ".ods";
464 $workbook = new MoodleODSWorkbook("-");
465 }
466 // Sending HTTP headers.
467 $workbook->send($filename);
468 // Creating the first worksheet.
469 $myxls = $workbook->add_worksheet('Attendances');
470 // Format types.
471 $formatbc = $workbook->add_format();
472 $formatbc->set_bold(1);
473
474 $myxls->write(0, 0, get_string('course'), $formatbc);
475 $myxls->write(0, 1, $data->course);
476 $myxls->write(1, 0, get_string('group'), $formatbc);
477 $myxls->write(1, 1, $data->group);
478
479 $i = 3;
480 $j = 0;
481 foreach ($data->tabhead as $cell) {
482 // Merge cells if the heading would be empty (remarks column).
483 if (empty($cell)) {
484 $myxls->merge_cells($i, $j - 1, $i, $j);
485 } else {
486 $myxls->write($i, $j, $cell, $formatbc);
487 }
488 $j++;
489 }
490 $i++;
491 $j = 0;
492 foreach ($data->table as $row) {
493 foreach ($row as $cell) {
494 $myxls->write($i, $j++, $cell);
495 }
496 $i++;
497 $j = 0;
498 }
499 $workbook->close();
500 }
501
502 /**
503 * Generate csv for Attendance export
504 *
505 * @param stdclass $data The data for the report
506 * @param string $filename The name of the file
507 *
508 */
509 function attendance_exporttocsv($data, $filename) {
510 $filename .= ".txt";
511
512 header("Content-Type: application/download\n");
513 header("Content-Disposition: attachment; filename=\"$filename\"");
514 header("Expires: 0");
515 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
516 header("Pragma: public");
517
518 echo get_string('course')."\t".$data->course."\n";
519 echo get_string('group')."\t".$data->group."\n\n";
520
521 echo implode("\t", $data->tabhead)."\n";
522 foreach ($data->table as $row) {
523 echo implode("\t", $row)."\n";
524 }
525 }
526
527 /**
528 * Get session data for form.
529 * @param stdClass $formdata moodleform - attendance form.
530 * @param mod_attendance_structure $att - used to get attendance level subnet.
531 * @return array.
532 */
533 function attendance_construct_sessions_data_for_add($formdata, mod_attendance_structure $att) {
534 global $CFG;
535
536 $sesstarttime = $formdata->sestime['starthour'] * HOURSECS + $formdata->sestime['startminute'] * MINSECS;
537 $sesendtime = $formdata->sestime['endhour'] * HOURSECS + $formdata->sestime['endminute'] * MINSECS;
538 $sessiondate = $formdata->sessiondate + $sesstarttime;
539 $duration = $sesendtime - $sesstarttime;
540 if (empty(get_config('attendance', 'enablewarnings'))) {
541 $absenteereport = get_config('attendance', 'absenteereport_default');
542 } else {
543 $absenteereport = empty($formdata->absenteereport) ? 0 : 1;
544 }
545
546 $now = time();
547
548 if (empty(get_config('attendance', 'studentscanmark'))) {
549 $formdata->studentscanmark = 0;
550 }
551
552 $sessions = array();
553 if (isset($formdata->addmultiply)) {
554 $startdate = $sessiondate;
555 $enddate = $formdata->sessionenddate + DAYSECS; // Because enddate in 0:0am.
556
557 if ($enddate < $startdate) {
558 return null;
559 }
560
561 // Getting first day of week.
562 $sdate = $startdate;
563 $dinfo = usergetdate($sdate);
564 if ($CFG->calendar_startwday === '0') { // Week start from sunday.
565 $startweek = $startdate - $dinfo['wday'] * DAYSECS; // Call new variable.
566 } else {
567 $wday = $dinfo['wday'] === 0 ? 7 : $dinfo['wday'];
568 $startweek = $startdate - ($wday - 1) * DAYSECS;
569 }
570
571 $wdaydesc = array(0 => 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
572
573 while ($sdate < $enddate) {
574 if ($sdate < $startweek + WEEKSECS) {
575 $dinfo = usergetdate($sdate);
576 if (isset($formdata->sdays) && array_key_exists($wdaydesc[$dinfo['wday']], $formdata->sdays)) {
577 $sess = new stdClass();
578 $sess->sessdate = make_timestamp($dinfo['year'], $dinfo['mon'], $dinfo['mday'],
579 $formdata->sestime['starthour'], $formdata->sestime['startminute']);
580 $sess->duration = $duration;
581 $sess->descriptionitemid = $formdata->sdescription['itemid'];
582 $sess->description = $formdata->sdescription['text'];
583 $sess->descriptionformat = $formdata->sdescription['format'];
584 $sess->timemodified = $now;
585 $sess->absenteereport = $absenteereport;
586 $sess->studentpassword = '';
587 if (isset($formdata->studentscanmark)) { // Students will be able to mark their own attendance.
588 $sess->studentscanmark = 1;
589 if (!empty($formdata->usedefaultsubnet)) {
590 $sess->subnet = $att->subnet;
591 } else {
592 $sess->subnet = $formdata->subnet;
593 }
594 $sess->automark = $formdata->automark;
595 if (isset($formdata->autoassignstatus)) {
596 $sess->autoassignstatus = 1;
597 }
598 $sess->automarkcompleted = 0;
599 if (!empty($formdata->randompassword)) {
600 $sess->studentpassword = attendance_random_string();
601 } else if (!empty($formdata->studentpassword)) {
602 $sess->studentpassword = $formdata->studentpassword;
603 }
604 } else {
605 $sess->subnet = '';
606 $sess->automark = 0;
607 $sess->automarkcompleted = 0;
608 }
609 $sess->statusset = $formdata->statusset;
610
611 attendance_fill_groupid($formdata, $sessions, $sess);
612 }
613 $sdate += DAYSECS;
614 } else {
615 $startweek += WEEKSECS * $formdata->period;
616 $sdate = $startweek;
617 }
618 }
619 } else {
620 $sess = new stdClass();
621 $sess->sessdate = $sessiondate;
622 $sess->duration = $duration;
623 $sess->descriptionitemid = $formdata->sdescription['itemid'];
624 $sess->description = $formdata->sdescription['text'];
625 $sess->descriptionformat = $formdata->sdescription['format'];
626 $sess->timemodified = $now;
627 $sess->studentscanmark = 0;
628 $sess->autoassignstatus = 0;
629 $sess->subnet = '';
630 $sess->studentpassword = '';
631 $sess->automark = 0;
632 $sess->automarkcompleted = 0;
633 $sess->absenteereport = $absenteereport;
634
635 if (isset($formdata->studentscanmark) && !empty($formdata->studentscanmark)) {
636 // Students will be able to mark their own attendance.
637 $sess->studentscanmark = 1;
638 if (isset($formdata->autoassignstatus) && !empty($formdata->autoassignstatus)) {
639 $sess->autoassignstatus = 1;
640 }
641 if (!empty($formdata->randompassword)) {
642 $sess->studentpassword = attendance_random_string();
643 } else if (!empty($formdata->studentpassword)) {
644 $sess->studentpassword = $formdata->studentpassword;
645 }
646 if (!empty($formdata->usedefaultsubnet)) {
647 $sess->subnet = $att->subnet;
648 } else {
649 $sess->subnet = $formdata->subnet;
650 }
651
652 if (!empty($formdata->automark)) {
653 $sess->automark = $formdata->automark;
654 }
655 }
656 $sess->statusset = $formdata->statusset;
657
658 attendance_fill_groupid($formdata, $sessions, $sess);
659 }
660
661 return $sessions;
662 }
663
664 /**
665 * Helper function for attendance_construct_sessions_data_for_add().
666 *
667 * @param stdClass $formdata
668 * @param stdClass $sessions
669 * @param stdClass $sess
670 */
671 function attendance_fill_groupid($formdata, &$sessions, $sess) {
672 if ($formdata->sessiontype == mod_attendance_structure::SESSION_COMMON) {
673 $sess = clone $sess;
674 $sess->groupid = 0;
675 $sessions[] = $sess;
676 } else {
677 foreach ($formdata->groups as $groupid) {
678 $sess = clone $sess;
679 $sess->groupid = $groupid;
680 $sessions[] = $sess;
681 }
682 }
683 }
684
685 /**
686 * Generates a summary of points for the courses selected.
687 *
688 * @param array $courseids optional list of courses to return
689 * @param string $orderby - optional order by param
690 * @return stdClass
691 */
692 function attendance_course_users_points($courseids = array(), $orderby = '') {
693 global $DB;
694
695 $where = '';
696 $params = array();
697 $where .= ' AND ats.sessdate < :enddate ';
698 $params['enddate'] = time();
699
700 $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
701 $where .= ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
702
703 if (!empty($courseids)) {
704 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
705 $where .= ' AND c.id ' . $insql;
706 $params = array_merge($params, $inparams);
707 }
708
709 $sql = "SELECT courseid, coursename, sum(points) / sum(maxpoints) as percentage FROM (
710 SELECT a.id, a.course as courseid, c.fullname as coursename, atl.studentid AS userid, COUNT(DISTINCT ats.id) AS numtakensessions,
711 SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints
712 FROM {attendance_sessions} ats
713 JOIN {attendance} a ON a.id = ats.attendanceid
714 JOIN {course} c ON c.id = a.course
715 JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
716 JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
717 JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
718 FROM {attendance_statuses}
719 WHERE deleted = 0
720 AND visible = 1
721 GROUP BY attendanceid, setnumber) stm
722 ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
723 {$joingroup}
724 WHERE ats.sessdate >= c.startdate
725 AND ats.lasttaken != 0
726 {$where}
727 GROUP BY a.id, a.course, c.fullname, atl.studentid
728 ) p GROUP by courseid, coursename {$orderby}";
729
730 return $DB->get_records_sql($sql, $params);
731 }
732
733 /**
734 * Generates a list of users flagged absent.
735 *
736 * @param array $courseids optional list of courses to return
737 * @param string $orderby how to order results.
738 * @param bool $allfornotify get notification list for scheduled task.
739 * @return stdClass
740 */
741 function attendance_get_users_to_notify($courseids = array(), $orderby = '', $allfornotify = false) {
742 global $DB;
743
744 $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
745 $where = ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
746 $having = '';
747 $params = array();
748
749 if (!empty($courseids)) {
750 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
751 $where .= ' AND c.id ' . $insql;
752 $params = array_merge($params, $inparams);
753 }
754 if ($allfornotify) {
755 // Exclude warnings that have already sent the max num.
756 $having .= ' AND n.maxwarn > COUNT(DISTINCT ns.id) ';
757 }
758
759 $unames = get_all_user_name_fields(true);
760 $unames2 = get_all_user_name_fields(true, 'u');
761
762 $idfield = $DB->sql_concat('cm.id', 'atl.studentid', 'n.id');
763 $sql = "SELECT {$idfield} as uniqueid, a.id as aid, {$unames2}, a.name as aname, cm.id as cmid, c.id as courseid,
764 c.fullname as coursename, atl.studentid AS userid, n.id as notifyid, n.warningpercent, n.emailsubject,
765 n.emailcontent, n.emailcontentformat, n.emailuser, n.thirdpartyemails, n.warnafter, n.maxwarn,
766 COUNT(DISTINCT ats.id) AS numtakensessions, SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints,
767 COUNT(DISTINCT ns.id) as nscount, MAX(ns.timesent) as timesent,
768 SUM(stg.grade) / SUM(stm.maxgrade) AS percent
769 FROM {attendance_sessions} ats
770 JOIN {attendance} a ON a.id = ats.attendanceid
771 JOIN {course_modules} cm ON cm.instance = a.id
772 JOIN {course} c on c.id = cm.course
773 JOIN {modules} md ON md.id = cm.module AND md.name = 'attendance'
774 JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
775 JOIN {user} u ON (u.id = atl.studentid)
776 JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
777 JOIN {attendance_warning} n ON n.idnumber = a.id
778 LEFT JOIN {attendance_warning_done} ns ON ns.notifyid = n.id AND ns.userid = atl.studentid
779 JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
780 FROM {attendance_statuses}
781 WHERE deleted = 0
782 AND visible = 1
783 GROUP BY attendanceid, setnumber) stm
784 ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
785 {$joingroup}
786 WHERE ats.absenteereport = 1 {$where}
787 GROUP BY uniqueid, a.id, a.name, a.course, c.fullname, atl.studentid, n.id, n.warningpercent,
788 n.emailsubject, n.emailcontent, n.emailcontentformat, n.warnafter, n.maxwarn,
789 n.emailuser, n.thirdpartyemails, cm.id, c.id, {$unames2}, ns.userid
790 HAVING n.warnafter <= COUNT(DISTINCT ats.id) AND n.warningpercent > ((SUM(stg.grade) / SUM(stm.maxgrade)) * 100)
791 {$having}
792 {$orderby}";
793
794 if (!$allfornotify) {
795 $idfield = $DB->sql_concat('cmid', 'userid');
796 // Only show one record per attendance for teacher reports.
797 $sql = "SELECT DISTINCT {$idfield} as id, {$unames}, aid, cmid, courseid, aname, coursename, userid,
798 numtakensessions, percent, MAX(timesent) as timesent
799 FROM ({$sql}) as m
800 GROUP BY id, aid, cmid, courseid, aname, userid, numtakensessions,
801 percent, coursename, {$unames} {$orderby}";
802 }
803
804 return $DB->get_records_sql($sql, $params);
805
806 }
807
808 /**
809 * Template variables into place in supplied email content.
810 *
811 * @param object $record db record of details
812 * @return array - the content of the fields after templating.
813 */
814 function attendance_template_variables($record) {
815 $templatevars = array(
816 '/%coursename%/' => $record->coursename,
817 '/%courseid%/' => $record->courseid,
818 '/%userfirstname%/' => $record->firstname,
819 '/%userlastname%/' => $record->lastname,
820 '/%userid%/' => $record->userid,
821 '/%warningpercent%/' => $record->warningpercent,
822 '/%attendancename%/' => $record->aname,
823 '/%cmid%/' => $record->cmid,
824 '/%numtakensessions%/' => $record->numtakensessions,
825 '/%points%/' => $record->points,
826 '/%maxpoints%/' => $record->maxpoints,
827 '/%percent%/' => $record->percent,
828 );
829 $extrauserfields = get_all_user_name_fields();
830 foreach ($extrauserfields as $extra) {
831 $templatevars['/%'.$extra.'%/'] = $record->$extra;
832 }
833 $patterns = array_keys($templatevars); // The placeholders which are to be replaced.
834 $replacements = array_values($templatevars); // The values which are to be templated in for the placeholders.
835 // Array to describe which fields in reengagement object should have a template replacement.
836 $replacementfields = array('emailsubject', 'emailcontent');
837
838 // Replace %variable% with relevant value everywhere it occurs in reengagement->field.
839 foreach ($replacementfields as $field) {
840 $record->$field = preg_replace($patterns, $replacements, $record->$field);
841 }
842 return $record;
843 }
844
845 /**
846 * Find highest available status for a user.
847 *
848 * @param mod_attendance_structure $att attendance structure
849 * @param stdclass $attforsession attendance_session record.
850 * @return bool/int
851 */
852 function attendance_session_get_highest_status(mod_attendance_structure $att, $attforsession) {
853 // Find the status to set here.
854 $statuses = $att->get_statuses();
855 $highestavailablegrade = 0;
856 $highestavailablestatus = new stdClass();
857 foreach ($statuses as $status) {
858 if ($status->studentavailability === '0') {
859 // This status is never available to students.
860 continue;
861 }
862 if (!empty($status->studentavailability)) {
863 $toolateforstatus = (($attforsession->sessdate + ($status->studentavailability * 60)) < time());
864 if ($toolateforstatus) {
865 continue;
866 }
867 }
868 // This status is available to the student.
869 if ($status->grade > $highestavailablegrade) {
870 // This is the most favourable grade so far; save it.
871 $highestavailablegrade = $status->grade;
872 $highestavailablestatus = $status;
873 }
874 }
875 if (empty($highestavailablestatus)) {
876 return false;
877 }
878 return $highestavailablestatus->id;
879 }
880
881 /**
882 * Get available automark options.
883 *
884 * @return array
885 */
886 function attendance_get_automarkoptions() {
887 $options = array();
888 $options[ATTENDANCE_AUTOMARK_DISABLED] = get_string('noautomark', 'attendance');
889 if (strpos(get_config('tool_log', 'enabled_stores'), 'logstore_standard') !== false) {
890 $options[ATTENDANCE_AUTOMARK_ALL] = get_string('automarkall', 'attendance');
891 }
892 $options[ATTENDANCE_AUTOMARK_CLOSE] = get_string('automarkclose', 'attendance');
893 return $options;
894 }