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 * local functions and constants for module attendance
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
25 defined('MOODLE_INTERNAL') ||
die();
27 require_once($CFG->libdir
. '/gradelib.php');
28 require_once(dirname(__FILE__
).'/renderhelpers.php');
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);
38 define('ATT_SORT_DEFAULT', 0);
39 define('ATT_SORT_LASTNAME', 1);
40 define('ATT_SORT_FIRSTNAME', 2);
42 define('ATTENDANCE_AUTOMARK_DISABLED', 0);
43 define('ATTENDANCE_AUTOMARK_ALL', 1);
44 define('ATTENDANCE_AUTOMARK_CLOSE', 2);
46 // Max number of sessions available in the warnings set form to trigger warnings.
47 define('ATTENDANCE_MAXWARNAFTER', 100);
53 * @param bool $onlyvisible
54 * @param int $statusset
57 function attendance_get_statuses($attid, $onlyvisible=true
, $statusset = -1) {
61 $params = array('aid' => $attid);
63 if ($statusset >= 0) {
64 $params['statusset'] = $statusset;
65 $setsql = ' AND setnumber = :statusset ';
69 $statuses = $DB->get_records_select('attendance_statuses', "attendanceid = :aid AND visible = 1 AND deleted = 0 $setsql",
70 $params, 'setnumber ASC, grade DESC');
72 $statuses = $DB->get_records_select('attendance_statuses', "attendanceid = :aid AND deleted = 0 $setsql",
73 $params, 'setnumber ASC, grade DESC');
80 * Get the name of the status set.
83 * @param int $statusset
84 * @param bool $includevalues
87 function attendance_get_setname($attid, $statusset, $includevalues = true
) {
88 $statusname = get_string('statusset', 'mod_attendance', $statusset +
1);
90 $statuses = attendance_get_statuses($attid, true
, $statusset);
91 $statusesout = array();
92 foreach ($statuses as $status) {
93 $statusesout[] = $status->acronym
;
96 if (count($statusesout) > 6) {
97 $statusesout = array_slice($statusesout, 0, 6);
98 $statusesout[] = '&helip;';
100 $statusesout = implode(' ', $statusesout);
101 $statusname .= ' ('.$statusesout.')';
109 * Get users courses and the relevant attendances.
114 function attendance_get_user_courses_attendances($userid) {
117 $usercourses = enrol_get_users_courses($userid);
119 list($usql, $uparams) = $DB->get_in_or_equal(array_keys($usercourses), SQL_PARAMS_NAMED
, 'cid0');
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
125 ON att.course = course.id
126 WHERE att.course $usql
127 ORDER BY coursefullname ASC, attname ASC";
129 $params = array_merge($uparams, array('uid' => $userid));
131 return $DB->get_records_sql($sql, $params);
135 * Used to calculate a fraction based on the part and total values
137 * @param float $part - part of the total value
138 * @param float $total - total value.
139 * @return float the calculated fraction.
141 function attendance_calc_fraction($part, $total) {
145 return $part / $total;
150 * Check to see if statusid in use to help prevent deletion etc.
152 * @param integer $statusid
154 function attendance_has_logs_for_status($statusid) {
156 return $DB->record_exists('attendance_log', array('statusid' => $statusid));
160 * Helper function to add sessiondate_selector to add/update forms.
162 * @param MoodleQuickForm $mform
164 function attendance_form_sessiondate_selector (MoodleQuickForm
$mform) {
166 $mform->addElement('date_selector', 'sessiondate', get_string('sessiondate', 'attendance'));
168 for ($i = 0; $i <= 23; $i++
) {
169 $hours[$i] = sprintf("%02d", $i);
171 for ($i = 0; $i < 60; $i +
= 5) {
172 $minutes[$i] = sprintf("%02d", $i);
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
);
186 * Count the number of status sets that exist for this instance.
188 * @param int $attendanceid
191 function attendance_get_max_statusset($attendanceid) {
194 $max = $DB->get_field_sql('SELECT MAX(setnumber) FROM {attendance_statuses} WHERE attendanceid = ? AND deleted = 0',
195 array($attendanceid));
203 * Returns the maxpoints for each statusset
205 * @param array $statuses
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
;
215 return $statussetmaxpoints;
221 * @param mod_attendance_structure|stdClass $attendance
222 * @param array $userids
224 function attendance_update_users_grade($attendance, $userids=array()) {
227 if (empty($attendance->grade
)) {
231 list($course, $cm) = get_course_and_cm_from_instance($attendance->id
, 'attendance');
233 $summary = new mod_attendance_summary($attendance->id
, $userids);
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'));
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);
246 $attendancegrade = $attendance->grade
;
250 foreach ($userids as $userid) {
251 $grades[$userid] = new stdClass();
252 $grades[$userid]->userid
= $userid;
254 if ($summary->has_taken_sessions($userid)) {
255 $usersummary = $summary->get_taken_sessions_summary_for($userid);
256 $grades[$userid]->rawgrade
= $usersummary->takensessionspercentage
* $attendancegrade;
258 $grades[$userid]->rawgrade
= null
;
262 return grade_update('mod/attendance', $course->id
, 'mod', 'attendance', $attendance->id
, 0, $grades);
266 * Add an attendance status variable
268 * @param stdClass $status
271 function attendance_add_status($status) {
273 if (empty($status->context
)) {
274 $status->context
= context_system
::instance();
277 if (!empty($status->acronym
) && !empty($status->description
)) {
278 $status->deleted
= 0;
279 $status->visible
= 1;
280 $status->setunmarked
= 0;
282 $id = $DB->insert_record('attendance_statuses', $status);
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
);
294 $event->add_record_snapshot('attendance_statuses', $status);
303 * Remove a status variable from an attendance instance
305 * @param stdClass $status
306 * @param stdClass $context
307 * @param stdClass $cm
309 function attendance_remove_status($status, $context = null
, $cm = null
) {
311 if (empty($context)) {
312 $context = context_system
::instance();
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,
319 'acronym' => $status->acronym
,
320 'description' => $status->description
323 $event->add_record_snapshot('course_modules', $cm);
325 $event->add_record_snapshot('attendance_statuses', $status);
330 * Update status variable for a particular Attendance module instance
332 * @param stdClass $status
333 * @param string $acronym
334 * @param string $description
336 * @param bool $visible
337 * @param stdClass $context
338 * @param stdClass $cm
339 * @param int $studentavailability
340 * @param bool $setunmarked
343 function attendance_update_status($status, $acronym, $description, $grade, $visible,
344 $context = null
, $cm = null
, $studentavailability = null
, $setunmarked = false
) {
347 if (empty($context)) {
348 $context = context_system
::instance();
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);
361 $status->acronym
= $acronym;
362 $updated[] = $acronym;
365 $status->description
= $description;
366 $updated[] = $description;
369 $status->grade
= $grade;
372 if (isset($studentavailability)) {
373 if (empty($studentavailability)) {
374 if ($studentavailability !== '0') {
375 $studentavailability = null
;
379 $status->studentavailability
= $studentavailability;
380 $updated[] = $studentavailability;
383 $status->setunmarked
= 1;
385 $status->setunmarked
= 0;
387 $DB->update_record('attendance_statuses', $status);
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))));
395 $event->add_record_snapshot('course_modules', $cm);
397 $event->add_record_snapshot('attendance_statuses', $status);
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.
405 * @param int $length The length of the string to be created.
408 function attendance_random_string($length=6) {
409 $randombytes = random_bytes_emulate($length);
410 $pool = 'abcdefghijklmnopqrstuvwxyz';
411 $pool .= '0123456789';
412 $poollen = strlen($pool);
414 for ($i = 0; $i < $length; $i++
) {
415 $rand = ord($randombytes[$i]);
416 $string .= substr($pool, ($rand %
($poollen)), 1);
422 * Check to see if this session is open for student marking.
424 * @param stdclass $sess the session record from attendance_sessions.
427 function attendance_can_student_mark($sess) {
428 global $DB, $USER, $OUTPUT;
430 $attconfig = get_config('attendance');
431 if (!empty($attconfig->studentscanmark
) && !empty($sess->studentscanmark
)) {
432 if (empty($attconfig->studentscanmarksessiontime
)) {
435 $duration = $sess->duration
;
436 if (empty($duration)) {
437 $duration = $attconfig->studentscanmarksessiontimeend
* 60;
439 if ($sess->sessdate
< time() && time() < ($sess->sessdate +
$duration)) {
444 // Check if another student has marked attendance from this IP address recently.
445 if ($canmark && !empty($sess->preventsharedip
)) {
446 $time = time() - ($sess->preventsharediptime
* 60);
447 $sql = 'sessionid = ? AND studentid <> ? AND timetaken > ? AND ipaddress = ?';
448 $params = array($sess->id
, $USER->id
, $time, getremoteaddr());
449 $record = $DB->get_record_select('attendance_log', $sql, $params);
450 if (!empty($record)) {
451 // Trigger an ip_shared event.
452 $attendanceid = $DB->get_field('attendance_sessions', 'attendanceid', array('id' => $record->sessionid
));
453 $cm = get_coursemodule_from_instance('attendance', $attendanceid);
454 $event = \mod_attendance\event\session_ip_shared
::create(array(
456 'context' => \context_module
::instance($cm->id
),
458 'sessionid' => $record->sessionid
,
459 'otheruser' => $record->studentid
465 echo $OUTPUT->notification(get_string('preventsharederror', 'attendance'));
473 * Generate worksheet for Attendance export
475 * @param stdclass $data The data for the report
476 * @param string $filename The name of the file
477 * @param string $format excel|ods
480 function attendance_exporttotableed($data, $filename, $format) {
483 if ($format === 'excel') {
484 require_once("$CFG->libdir/excellib.class.php");
486 $workbook = new MoodleExcelWorkbook("-");
488 require_once("$CFG->libdir/odslib.class.php");
490 $workbook = new MoodleODSWorkbook("-");
492 // Sending HTTP headers.
493 $workbook->send($filename);
494 // Creating the first worksheet.
495 $myxls = $workbook->add_worksheet('Attendances');
497 $formatbc = $workbook->add_format();
498 $formatbc->set_bold(1);
500 $myxls->write(0, 0, get_string('course'), $formatbc);
501 $myxls->write(0, 1, $data->course
);
502 $myxls->write(1, 0, get_string('group'), $formatbc);
503 $myxls->write(1, 1, $data->group
);
507 foreach ($data->tabhead
as $cell) {
508 // Merge cells if the heading would be empty (remarks column).
510 $myxls->merge_cells($i, $j - 1, $i, $j);
512 $myxls->write($i, $j, $cell, $formatbc);
518 foreach ($data->table
as $row) {
519 foreach ($row as $cell) {
520 $myxls->write($i, $j++
, $cell);
529 * Generate csv for Attendance export
531 * @param stdclass $data The data for the report
532 * @param string $filename The name of the file
535 function attendance_exporttocsv($data, $filename) {
538 header("Content-Type: application/download\n");
539 header("Content-Disposition: attachment; filename=\"$filename\"");
540 header("Expires: 0");
541 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
542 header("Pragma: public");
544 echo get_string('course')."\t".$data->course
."\n";
545 echo get_string('group')."\t".$data->group
."\n\n";
547 echo implode("\t", $data->tabhead
)."\n";
548 foreach ($data->table
as $row) {
549 echo implode("\t", $row)."\n";
554 * Get session data for form.
555 * @param stdClass $formdata moodleform - attendance form.
556 * @param mod_attendance_structure $att - used to get attendance level subnet.
559 function attendance_construct_sessions_data_for_add($formdata, mod_attendance_structure
$att) {
562 $sesstarttime = $formdata->sestime
['starthour'] * HOURSECS +
$formdata->sestime
['startminute'] * MINSECS
;
563 $sesendtime = $formdata->sestime
['endhour'] * HOURSECS +
$formdata->sestime
['endminute'] * MINSECS
;
564 $sessiondate = $formdata->sessiondate +
$sesstarttime;
565 $duration = $sesendtime - $sesstarttime;
566 if (empty(get_config('attendance', 'enablewarnings'))) {
567 $absenteereport = get_config('attendance', 'absenteereport_default');
569 $absenteereport = empty($formdata->absenteereport
) ?
0 : 1;
574 if (empty(get_config('attendance', 'studentscanmark'))) {
575 $formdata->studentscanmark
= 0;
579 if (isset($formdata->addmultiply
)) {
580 $startdate = $sessiondate;
581 $enddate = $formdata->sessionenddate + DAYSECS
; // Because enddate in 0:0am.
583 if ($enddate < $startdate) {
587 // Getting first day of week.
589 $dinfo = usergetdate($sdate);
590 if ($CFG->calendar_startwday
=== '0') { // Week start from sunday.
591 $startweek = $startdate - $dinfo['wday'] * DAYSECS
; // Call new variable.
593 $wday = $dinfo['wday'] === 0 ?
7 : $dinfo['wday'];
594 $startweek = $startdate - ($wday - 1) * DAYSECS
;
597 $wdaydesc = array(0 => 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
599 while ($sdate < $enddate) {
600 if ($sdate < $startweek + WEEKSECS
) {
601 $dinfo = usergetdate($sdate);
602 if (isset($formdata->sdays
) && array_key_exists($wdaydesc[$dinfo['wday']], $formdata->sdays
)) {
603 $sess = new stdClass();
604 $sess->sessdate
= make_timestamp($dinfo['year'], $dinfo['mon'], $dinfo['mday'],
605 $formdata->sestime
['starthour'], $formdata->sestime
['startminute']);
606 $sess->duration
= $duration;
607 $sess->descriptionitemid
= $formdata->sdescription
['itemid'];
608 $sess->description
= $formdata->sdescription
['text'];
609 $sess->descriptionformat
= $formdata->sdescription
['format'];
610 $sess->timemodified
= $now;
611 $sess->absenteereport
= $absenteereport;
612 $sess->studentpassword
= '';
613 if (isset($formdata->studentscanmark
)) { // Students will be able to mark their own attendance.
614 $sess->studentscanmark
= 1;
615 if (!empty($formdata->usedefaultsubnet
)) {
616 $sess->subnet
= $att->subnet
;
618 $sess->subnet
= $formdata->subnet
;
620 $sess->automark
= $formdata->automark
;
621 if (isset($formdata->autoassignstatus
)) {
622 $sess->autoassignstatus
= 1;
624 $sess->automarkcompleted
= 0;
625 if (!empty($formdata->randompassword
)) {
626 $sess->studentpassword
= attendance_random_string();
627 } else if (!empty($formdata->studentpassword
)) {
628 $sess->studentpassword
= $formdata->studentpassword
;
630 if (!empty($formdata->preventsharedip
)) {
631 $sess->preventsharedip
= $formdata->preventsharedip
;
633 if (!empty($formdata->preventsharediptime
)) {
634 $sess->preventsharediptime
= $formdata->preventsharediptime
;
639 $sess->automarkcompleted
= 0;
640 $sess->preventsharedip
= 0;
641 $sess->preventsharediptime
= '';
643 $sess->statusset
= $formdata->statusset
;
645 attendance_fill_groupid($formdata, $sessions, $sess);
649 $startweek +
= WEEKSECS
* $formdata->period
;
654 $sess = new stdClass();
655 $sess->sessdate
= $sessiondate;
656 $sess->duration
= $duration;
657 $sess->descriptionitemid
= $formdata->sdescription
['itemid'];
658 $sess->description
= $formdata->sdescription
['text'];
659 $sess->descriptionformat
= $formdata->sdescription
['format'];
660 $sess->timemodified
= $now;
661 $sess->studentscanmark
= 0;
662 $sess->autoassignstatus
= 0;
664 $sess->studentpassword
= '';
666 $sess->automarkcompleted
= 0;
667 $sess->absenteereport
= $absenteereport;
669 if (isset($formdata->studentscanmark
) && !empty($formdata->studentscanmark
)) {
670 // Students will be able to mark their own attendance.
671 $sess->studentscanmark
= 1;
672 if (isset($formdata->autoassignstatus
) && !empty($formdata->autoassignstatus
)) {
673 $sess->autoassignstatus
= 1;
675 if (!empty($formdata->randompassword
)) {
676 $sess->studentpassword
= attendance_random_string();
677 } else if (!empty($formdata->studentpassword
)) {
678 $sess->studentpassword
= $formdata->studentpassword
;
680 if (!empty($formdata->usedefaultsubnet
)) {
681 $sess->subnet
= $att->subnet
;
683 $sess->subnet
= $formdata->subnet
;
686 if (!empty($formdata->automark
)) {
687 $sess->automark
= $formdata->automark
;
689 if (!empty($formdata->preventsharedip
)) {
690 $sess->preventsharedip
= $formdata->preventsharedip
;
692 if (!empty($formdata->preventsharediptime
)) {
693 $sess->preventsharediptime
= $formdata->preventsharediptime
;
696 $sess->statusset
= $formdata->statusset
;
698 attendance_fill_groupid($formdata, $sessions, $sess);
705 * Helper function for attendance_construct_sessions_data_for_add().
707 * @param stdClass $formdata
708 * @param stdClass $sessions
709 * @param stdClass $sess
711 function attendance_fill_groupid($formdata, &$sessions, $sess) {
712 if ($formdata->sessiontype
== mod_attendance_structure
::SESSION_COMMON
) {
717 foreach ($formdata->groups
as $groupid) {
719 $sess->groupid
= $groupid;
726 * Generates a summary of points for the courses selected.
728 * @param array $courseids optional list of courses to return
729 * @param string $orderby - optional order by param
732 function attendance_course_users_points($courseids = array(), $orderby = '') {
737 $where .= ' AND ats.sessdate < :enddate ';
738 $params['enddate'] = time();
740 $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
741 $where .= ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
743 if (!empty($courseids)) {
744 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED
);
745 $where .= ' AND c.id ' . $insql;
746 $params = array_merge($params, $inparams);
749 $sql = "SELECT courseid, coursename, sum(points) / sum(maxpoints) as percentage FROM (
750 SELECT a.id, a.course as courseid, c.fullname as coursename, atl.studentid AS userid, COUNT(DISTINCT ats.id) AS numtakensessions,
751 SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints
752 FROM {attendance_sessions} ats
753 JOIN {attendance} a ON a.id = ats.attendanceid
754 JOIN {course} c ON c.id = a.course
755 JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
756 JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
757 JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
758 FROM {attendance_statuses}
761 GROUP BY attendanceid, setnumber) stm
762 ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
764 WHERE ats.sessdate >= c.startdate
765 AND ats.lasttaken != 0
767 GROUP BY a.id, a.course, c.fullname, atl.studentid
768 ) p GROUP by courseid, coursename {$orderby}";
770 return $DB->get_records_sql($sql, $params);
774 * Generates a list of users flagged absent.
776 * @param array $courseids optional list of courses to return
777 * @param string $orderby how to order results.
778 * @param bool $allfornotify get notification list for scheduled task.
781 function attendance_get_users_to_notify($courseids = array(), $orderby = '', $allfornotify = false
) {
784 $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
785 $where = ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
789 if (!empty($courseids)) {
790 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED
);
791 $where .= ' AND c.id ' . $insql;
792 $params = array_merge($params, $inparams);
795 // Exclude warnings that have already sent the max num.
796 $having .= ' AND n.maxwarn > COUNT(DISTINCT ns.id) ';
799 $unames = get_all_user_name_fields(true
);
800 $unames2 = get_all_user_name_fields(true
, 'u');
802 $idfield = $DB->sql_concat('cm.id', 'atl.studentid', 'n.id');
803 $sql = "SELECT {$idfield} as uniqueid, a.id as aid, {$unames2}, a.name as aname, cm.id as cmid, c.id as courseid,
804 c.fullname as coursename, atl.studentid AS userid, n.id as notifyid, n.warningpercent, n.emailsubject,
805 n.emailcontent, n.emailcontentformat, n.emailuser, n.thirdpartyemails, n.warnafter, n.maxwarn,
806 COUNT(DISTINCT ats.id) AS numtakensessions, SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints,
807 COUNT(DISTINCT ns.id) as nscount, MAX(ns.timesent) as timesent,
808 SUM(stg.grade) / SUM(stm.maxgrade) AS percent
809 FROM {attendance_sessions} ats
810 JOIN {attendance} a ON a.id = ats.attendanceid
811 JOIN {course_modules} cm ON cm.instance = a.id
812 JOIN {course} c on c.id = cm.course
813 JOIN {modules} md ON md.id = cm.module AND md.name = 'attendance'
814 JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
815 JOIN {user} u ON (u.id = atl.studentid)
816 JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
817 JOIN {attendance_warning} n ON n.idnumber = a.id
818 LEFT JOIN {attendance_warning_done} ns ON ns.notifyid = n.id AND ns.userid = atl.studentid
819 JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
820 FROM {attendance_statuses}
823 GROUP BY attendanceid, setnumber) stm
824 ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
826 WHERE ats.absenteereport = 1 {$where}
827 GROUP BY uniqueid, a.id, a.name, a.course, c.fullname, atl.studentid, n.id, n.warningpercent,
828 n.emailsubject, n.emailcontent, n.emailcontentformat, n.warnafter, n.maxwarn,
829 n.emailuser, n.thirdpartyemails, cm.id, c.id, {$unames2}, ns.userid
830 HAVING n.warnafter <= COUNT(DISTINCT ats.id) AND n.warningpercent > ((SUM(stg.grade) / SUM(stm.maxgrade)) * 100)
834 if (!$allfornotify) {
835 $idfield = $DB->sql_concat('cmid', 'userid');
836 // Only show one record per attendance for teacher reports.
837 $sql = "SELECT DISTINCT {$idfield} as id, {$unames}, aid, cmid, courseid, aname, coursename, userid,
838 numtakensessions, percent, MAX(timesent) as timesent
840 GROUP BY id, aid, cmid, courseid, aname, userid, numtakensessions,
841 percent, coursename, {$unames} {$orderby}";
844 return $DB->get_records_sql($sql, $params);
849 * Template variables into place in supplied email content.
851 * @param object $record db record of details
852 * @return array - the content of the fields after templating.
854 function attendance_template_variables($record) {
855 $templatevars = array(
856 '/%coursename%/' => $record->coursename
,
857 '/%courseid%/' => $record->courseid
,
858 '/%userfirstname%/' => $record->firstname
,
859 '/%userlastname%/' => $record->lastname
,
860 '/%userid%/' => $record->userid
,
861 '/%warningpercent%/' => $record->warningpercent
,
862 '/%attendancename%/' => $record->aname
,
863 '/%cmid%/' => $record->cmid
,
864 '/%numtakensessions%/' => $record->numtakensessions
,
865 '/%points%/' => $record->points
,
866 '/%maxpoints%/' => $record->maxpoints
,
867 '/%percent%/' => $record->percent
,
869 $extrauserfields = get_all_user_name_fields();
870 foreach ($extrauserfields as $extra) {
871 $templatevars['/%'.$extra.'%/'] = $record->$extra;
873 $patterns = array_keys($templatevars); // The placeholders which are to be replaced.
874 $replacements = array_values($templatevars); // The values which are to be templated in for the placeholders.
875 // Array to describe which fields in reengagement object should have a template replacement.
876 $replacementfields = array('emailsubject', 'emailcontent');
878 // Replace %variable% with relevant value everywhere it occurs in reengagement->field.
879 foreach ($replacementfields as $field) {
880 $record->$field = preg_replace($patterns, $replacements, $record->$field);
886 * Find highest available status for a user.
888 * @param mod_attendance_structure $att attendance structure
889 * @param stdclass $attforsession attendance_session record.
892 function attendance_session_get_highest_status(mod_attendance_structure
$att, $attforsession) {
893 // Find the status to set here.
894 $statuses = $att->get_statuses();
895 $highestavailablegrade = 0;
896 $highestavailablestatus = new stdClass();
897 foreach ($statuses as $status) {
898 if ($status->studentavailability
=== '0') {
899 // This status is never available to students.
902 if (!empty($status->studentavailability
)) {
903 $toolateforstatus = (($attforsession->sessdate +
($status->studentavailability
* 60)) < time());
904 if ($toolateforstatus) {
908 // This status is available to the student.
909 if ($status->grade
> $highestavailablegrade) {
910 // This is the most favourable grade so far; save it.
911 $highestavailablegrade = $status->grade
;
912 $highestavailablestatus = $status;
915 if (empty($highestavailablestatus)) {
918 return $highestavailablestatus->id
;
922 * Get available automark options.
926 function attendance_get_automarkoptions() {
928 $options[ATTENDANCE_AUTOMARK_DISABLED
] = get_string('noautomark', 'attendance');
929 if (strpos(get_config('tool_log', 'enabled_stores'), 'logstore_standard') !== false
) {
930 $options[ATTENDANCE_AUTOMARK_ALL
] = get_string('automarkall', 'attendance');
932 $options[ATTENDANCE_AUTOMARK_CLOSE
] = get_string('automarkclose', 'attendance');