. /** * local functions and constants for module attendance * * @package mod_attendance * @copyright 2011 Artem Andreev * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/gradelib.php'); require_once(dirname(__FILE__).'/renderhelpers.php'); define('ATT_VIEW_DAYS', 1); define('ATT_VIEW_WEEKS', 2); define('ATT_VIEW_MONTHS', 3); define('ATT_VIEW_ALLPAST', 4); define('ATT_VIEW_ALL', 5); define('ATT_VIEW_NOTPRESENT', 6); define('ATT_VIEW_SUMMARY', 7); define('ATT_SORT_DEFAULT', 0); define('ATT_SORT_LASTNAME', 1); define('ATT_SORT_FIRSTNAME', 2); define('ATTENDANCE_AUTOMARK_DISABLED', 0); define('ATTENDANCE_AUTOMARK_ALL', 1); define('ATTENDANCE_AUTOMARK_CLOSE', 2); /** * Get statuses, * * @param int $attid * @param bool $onlyvisible * @param int $statusset * @return array */ function attendance_get_statuses($attid, $onlyvisible=true, $statusset = -1) { global $DB; // Set selector. $params = array('aid' => $attid); $setsql = ''; if ($statusset >= 0) { $params['statusset'] = $statusset; $setsql = ' AND setnumber = :statusset '; } if ($onlyvisible) { $statuses = $DB->get_records_select('attendance_statuses', "attendanceid = :aid AND visible = 1 AND deleted = 0 $setsql", $params, 'setnumber ASC, grade DESC'); } else { $statuses = $DB->get_records_select('attendance_statuses', "attendanceid = :aid AND deleted = 0 $setsql", $params, 'setnumber ASC, grade DESC'); } return $statuses; } /** * Get the name of the status set. * * @param int $attid * @param int $statusset * @param bool $includevalues * @return string */ function attendance_get_setname($attid, $statusset, $includevalues = true) { $statusname = get_string('statusset', 'mod_attendance', $statusset + 1); if ($includevalues) { $statuses = attendance_get_statuses($attid, true, $statusset); $statusesout = array(); foreach ($statuses as $status) { $statusesout[] = $status->acronym; } if ($statusesout) { if (count($statusesout) > 6) { $statusesout = array_slice($statusesout, 0, 6); $statusesout[] = '&helip;'; } $statusesout = implode(' ', $statusesout); $statusname .= ' ('.$statusesout.')'; } } return $statusname; } /** * Get users courses and the relevant attendances. * * @param int $userid * @return array */ function attendance_get_user_courses_attendances($userid) { global $DB; $usercourses = enrol_get_users_courses($userid); list($usql, $uparams) = $DB->get_in_or_equal(array_keys($usercourses), SQL_PARAMS_NAMED, 'cid0'); $sql = "SELECT att.id as attid, att.course as courseid, course.fullname as coursefullname, course.startdate as coursestartdate, att.name as attname, att.grade as attgrade FROM {attendance} att JOIN {course} course ON att.course = course.id WHERE att.course $usql ORDER BY coursefullname ASC, attname ASC"; $params = array_merge($uparams, array('uid' => $userid)); return $DB->get_records_sql($sql, $params); } /** * Used to calculate a fraction based on the part and total values * * @param float $part - part of the total value * @param float $total - total value. * @return float the calculated fraction. */ function attendance_calc_fraction($part, $total) { if ($total == 0) { return 0; } else { return $part / $total; } } /** * Check to see if statusid in use to help prevent deletion etc. * * @param integer $statusid */ function attendance_has_logs_for_status($statusid) { global $DB; return $DB->record_exists('attendance_log', array('statusid' => $statusid)); } /** * Helper function to add sessiondate_selector to add/update forms. * * @param MoodleQuickForm $mform */ function attendance_form_sessiondate_selector (MoodleQuickForm $mform) { $mform->addElement('date_selector', 'sessiondate', get_string('sessiondate', 'attendance')); for ($i = 0; $i <= 23; $i++) { $hours[$i] = sprintf("%02d", $i); } for ($i = 0; $i < 60; $i += 5) { $minutes[$i] = sprintf("%02d", $i); } $sesendtime = array(); $sesendtime[] =& $mform->createElement('static', 'from', '', get_string('from', 'attendance')); $sesendtime[] =& $mform->createElement('select', 'starthour', get_string('hour', 'form'), $hours, false, true); $sesendtime[] =& $mform->createElement('select', 'startminute', get_string('minute', 'form'), $minutes, false, true); $sesendtime[] =& $mform->createElement('static', 'to', '', get_string('to', 'attendance')); $sesendtime[] =& $mform->createElement('select', 'endhour', get_string('hour', 'form'), $hours, false, true); $sesendtime[] =& $mform->createElement('select', 'endminute', get_string('minute', 'form'), $minutes, false, true); $mform->addGroup($sesendtime, 'sestime', get_string('time', 'attendance'), array(' '), true); } /** * Count the number of status sets that exist for this instance. * * @param int $attendanceid * @return int */ function attendance_get_max_statusset($attendanceid) { global $DB; $max = $DB->get_field_sql('SELECT MAX(setnumber) FROM {attendance_statuses} WHERE attendanceid = ? AND deleted = 0', array($attendanceid)); if ($max) { return $max; } return 0; } /** * Returns the maxpoints for each statusset * * @param array $statuses * @return array */ function attendance_get_statusset_maxpoints($statuses) { $statussetmaxpoints = array(); foreach ($statuses as $st) { if (!isset($statussetmaxpoints[$st->setnumber])) { $statussetmaxpoints[$st->setnumber] = $st->grade; } } return $statussetmaxpoints; } /** * Update user grades * * @param mod_attendance_structure|stdClass $attendance * @param array $userids */ function attendance_update_users_grade($attendance, $userids=array()) { global $DB; if (empty($attendance->grade)) { return false; } list($course, $cm) = get_course_and_cm_from_instance($attendance->id, 'attendance'); $summary = new mod_attendance_summary($attendance->id, $userids); if (empty($userids)) { $context = context_module::instance($cm->id); $userids = array_keys(get_enrolled_users($context, 'mod/attendance:canbelisted', 0, 'u.id')); } if ($attendance->grade < 0) { $dbparams = array('id' => -($attendance->grade)); $scale = $DB->get_record('scale', $dbparams); $scalearray = explode(',', $scale->scale); $attendancegrade = count($scalearray); } else { $attendancegrade = $attendance->grade; } $grades = array(); foreach ($userids as $userid) { $grades[$userid] = new stdClass(); $grades[$userid]->userid = $userid; if ($summary->has_taken_sessions($userid)) { $usersummary = $summary->get_taken_sessions_summary_for($userid); $grades[$userid]->rawgrade = $usersummary->takensessionspercentage * $attendancegrade; } else { $grades[$userid]->rawgrade = null; } } return grade_update('mod/attendance', $course->id, 'mod', 'attendance', $attendance->id, 0, $grades); } /** * Add an attendance status variable * * @param stdClass $status * @return bool */ function attendance_add_status($status) { global $DB; if (empty($status->context)) { $status->context = context_system::instance(); } if (!empty($status->acronym) && !empty($status->description)) { $status->deleted = 0; $status->visible = 1; $status->setunmarked = 0; $id = $DB->insert_record('attendance_statuses', $status); $status->id = $id; $event = \mod_attendance\event\status_added::create(array( 'objectid' => $status->attendanceid, 'context' => $status->context, 'other' => array('acronym' => $status->acronym, 'description' => $status->description, 'grade' => $status->grade))); if (!empty($status->cm)) { $event->add_record_snapshot('course_modules', $status->cm); } $event->add_record_snapshot('attendance_statuses', $status); $event->trigger(); return true; } else { return false; } } /** * Remove a status variable from an attendance instance * * @param stdClass $status * @param stdClass $context * @param stdClass $cm */ function attendance_remove_status($status, $context = null, $cm = null) { global $DB; if (empty($context)) { $context = context_system::instance(); } $DB->set_field('attendance_statuses', 'deleted', 1, array('id' => $status->id)); $event = \mod_attendance\event\status_removed::create(array( 'objectid' => $status->id, 'context' => $context, 'other' => array( 'acronym' => $status->acronym, 'description' => $status->description ))); if (!empty($cm)) { $event->add_record_snapshot('course_modules', $cm); } $event->add_record_snapshot('attendance_statuses', $status); $event->trigger(); } /** * Update status variable for a particular Attendance module instance * * @param stdClass $status * @param string $acronym * @param string $description * @param int $grade * @param bool $visible * @param stdClass $context * @param stdClass $cm * @param int $studentavailability * @param bool $setunmarked * @return array */ function attendance_update_status($status, $acronym, $description, $grade, $visible, $context = null, $cm = null, $studentavailability = null, $setunmarked = false) { global $DB; if (empty($context)) { $context = context_system::instance(); } if (isset($visible)) { $status->visible = $visible; $updated[] = $visible ? get_string('show') : get_string('hide'); } else if (empty($acronym) || empty($description)) { return array('acronym' => $acronym, 'description' => $description); } $updated = array(); if ($acronym) { $status->acronym = $acronym; $updated[] = $acronym; } if ($description) { $status->description = $description; $updated[] = $description; } if (isset($grade)) { $status->grade = $grade; $updated[] = $grade; } if (isset($studentavailability)) { if (empty($studentavailability)) { if ($studentavailability !== '0') { $studentavailability = null; } } $status->studentavailability = $studentavailability; $updated[] = $studentavailability; } if ($setunmarked) { $status->setunmarked = 1; } else { $status->setunmarked = 0; } $DB->update_record('attendance_statuses', $status); $event = \mod_attendance\event\status_updated::create(array( 'objectid' => $status->attendanceid, 'context' => $context, 'other' => array('acronym' => $acronym, 'description' => $description, 'grade' => $grade, 'updated' => implode(' ', $updated)))); if (!empty($cm)) { $event->add_record_snapshot('course_modules', $cm); } $event->add_record_snapshot('attendance_statuses', $status); $event->trigger(); } /** * Similar to core random_string function but only lowercase letters. * designed to make it relatively easy to provide a simple password in class. * * @param int $length The length of the string to be created. * @return string */ function attendance_random_string($length=6) { $randombytes = random_bytes_emulate($length); $pool = 'abcdefghijklmnopqrstuvwxyz'; $pool .= '0123456789'; $poollen = strlen($pool); $string = ''; for ($i = 0; $i < $length; $i++) { $rand = ord($randombytes[$i]); $string .= substr($pool, ($rand % ($poollen)), 1); } return $string; } /** * Check to see if this session is open for student marking. * * @param stdclass $sess the session record from attendance_sessions. * @return boolean */ function attendance_can_student_mark($sess) { $canmark = false; $attconfig = get_config('attendance'); if (!empty($attconfig->studentscanmark) && !empty($sess->studentscanmark)) { if (empty($attconfig->studentscanmarksessiontime)) { $canmark = true; } else { $duration = $sess->duration; if (empty($duration)) { $duration = $attconfig->studentscanmarksessiontimeend * 60; } if ($sess->sessdate < time() && time() < ($sess->sessdate + $duration)) { $canmark = true; } } } return $canmark; } /** * Generate worksheet for Attendance export * * @param stdclass $data The data for the report * @param string $filename The name of the file * @param string $format excel|ods * */ function attendance_exporttotableed($data, $filename, $format) { global $CFG; if ($format === 'excel') { require_once("$CFG->libdir/excellib.class.php"); $filename .= ".xls"; $workbook = new MoodleExcelWorkbook("-"); } else { require_once("$CFG->libdir/odslib.class.php"); $filename .= ".ods"; $workbook = new MoodleODSWorkbook("-"); } // Sending HTTP headers. $workbook->send($filename); // Creating the first worksheet. $myxls = $workbook->add_worksheet('Attendances'); // Format types. $formatbc = $workbook->add_format(); $formatbc->set_bold(1); $myxls->write(0, 0, get_string('course'), $formatbc); $myxls->write(0, 1, $data->course); $myxls->write(1, 0, get_string('group'), $formatbc); $myxls->write(1, 1, $data->group); $i = 3; $j = 0; foreach ($data->tabhead as $cell) { // Merge cells if the heading would be empty (remarks column). if (empty($cell)) { $myxls->merge_cells($i, $j - 1, $i, $j); } else { $myxls->write($i, $j, $cell, $formatbc); } $j++; } $i++; $j = 0; foreach ($data->table as $row) { foreach ($row as $cell) { $myxls->write($i, $j++, $cell); } $i++; $j = 0; } $workbook->close(); } /** * Generate csv for Attendance export * * @param stdclass $data The data for the report * @param string $filename The name of the file * */ function attendance_exporttocsv($data, $filename) { $filename .= ".txt"; header("Content-Type: application/download\n"); header("Content-Disposition: attachment; filename=\"$filename\""); header("Expires: 0"); header("Cache-Control: must-revalidate,post-check=0,pre-check=0"); header("Pragma: public"); echo get_string('course')."\t".$data->course."\n"; echo get_string('group')."\t".$data->group."\n\n"; echo implode("\t", $data->tabhead)."\n"; foreach ($data->table as $row) { echo implode("\t", $row)."\n"; } } /** * Get session data for form. * @param stdClass $formdata moodleform - attendance form. * $param mod_attendance_structure $att - used to get attendance level subnet. * @return array. */ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_structure $att) { global $CFG; $sesstarttime = $formdata->sestime['starthour'] * HOURSECS + $formdata->sestime['startminute'] * MINSECS; $sesendtime = $formdata->sestime['endhour'] * HOURSECS + $formdata->sestime['endminute'] * MINSECS; $sessiondate = $formdata->sessiondate + $sesstarttime; $duration = $sesendtime - $sesstarttime; $now = time(); if (empty(get_config('attendance', 'studentscanmark'))) { $formdata->studentscanmark = 0; } $sessions = array(); if (isset($formdata->addmultiply)) { $startdate = $sessiondate; $enddate = $formdata->sessionenddate + DAYSECS; // Because enddate in 0:0am. if ($enddate < $startdate) { return null; } // Getting first day of week. $sdate = $startdate; $dinfo = usergetdate($sdate); if ($CFG->calendar_startwday === '0') { // Week start from sunday. $startweek = $startdate - $dinfo['wday'] * DAYSECS; // Call new variable. } else { $wday = $dinfo['wday'] === 0 ? 7 : $dinfo['wday']; $startweek = $startdate - ($wday - 1) * DAYSECS; } $wdaydesc = array(0 => 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'); while ($sdate < $enddate) { if ($sdate < $startweek + WEEKSECS) { $dinfo = usergetdate($sdate); if (isset($formdata->sdays) && array_key_exists($wdaydesc[$dinfo['wday']], $formdata->sdays)) { $sess = new stdClass(); $sess->sessdate = make_timestamp($dinfo['year'], $dinfo['mon'], $dinfo['mday'], $formdata->sestime['starthour'], $formdata->sestime['startminute']); $sess->duration = $duration; $sess->descriptionitemid = $formdata->sdescription['itemid']; $sess->description = $formdata->sdescription['text']; $sess->descriptionformat = $formdata->sdescription['format']; $sess->timemodified = $now; if (isset($formdata->studentscanmark)) { // Students will be able to mark their own attendance. $sess->studentscanmark = 1; if (!empty($formdata->usedefaultsubnet)) { $sess->subnet = $att->subnet; } else { $sess->subnet = $formdata->subnet; } $sess->automark = $formdata->automark; $sess->automarkcompleted = 0; if (!empty($formdata->randompassword)) { $sess->studentpassword = attendance_random_string(); } else { $sess->studentpassword = $formdata->studentpassword; } } else { $sess->studentpassword = ''; $sess->subnet = ''; $sess->automark = 0; $sess->automarkcompleted = 0; } $sess->statusset = $formdata->statusset; attendance_fill_groupid($formdata, $sessions, $sess); } $sdate += DAYSECS; } else { $startweek += WEEKSECS * $formdata->period; $sdate = $startweek; } } } else { $sess = new stdClass(); $sess->sessdate = $sessiondate; $sess->duration = $duration; $sess->descriptionitemid = $formdata->sdescription['itemid']; $sess->description = $formdata->sdescription['text']; $sess->descriptionformat = $formdata->sdescription['format']; $sess->timemodified = $now; $sess->studentscanmark = 0; $sess->subnet = ''; $sess->studentpassword = ''; $sess->automark = 0; $sess->automarkcompleted = 0; if (isset($formdata->studentscanmark) && !empty($formdata->studentscanmark)) { // Students will be able to mark their own attendance. $sess->studentscanmark = 1; if (!empty($formdata->randompassword)) { $sess->studentpassword = attendance_random_string(); } else if (!empty($formdata->studentpassword)) { $sess->studentpassword = $formdata->studentpassword; } if (!empty($formdata->usedefaultsubnet)) { $sess->subnet = $att->subnet; } else { $sess->subnet = $formdata->subnet; } if (!empty($formdata->automark)) { $sess->automark = $formdata->automark; } } $sess->statusset = $formdata->statusset; attendance_fill_groupid($formdata, $sessions, $sess); } return $sessions; } /** * Helper function for attendance_construct_sessions_data_for_add(). * * @param stdClass $formdata * @param stdClass $sessions * @param stdClass $sess */ function attendance_fill_groupid($formdata, &$sessions, $sess) { if ($formdata->sessiontype == mod_attendance_structure::SESSION_COMMON) { $sess = clone $sess; $sess->groupid = 0; $sessions[] = $sess; } else { foreach ($formdata->groups as $groupid) { $sess = clone $sess; $sess->groupid = $groupid; $sessions[] = $sess; } } } /** * Generates a summary of points for the courses selected. * * @param array $courseids optional list of courses to return * @param string $orderby - optional order by param * @return stdClass */ function attendance_course_users_points($courseids = array(), $orderby = '') { global $DB; $where = ''; $params = array(); $where .= ' AND ats.sessdate < :enddate '; $params['enddate'] = time(); $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)'; $where .= ' AND (ats.groupid = 0 or gm.id is NOT NULL)'; if (!empty($courseids)) { list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $where .= ' AND c.id ' . $insql; $params = array_merge($params, $inparams); } $sql = "SELECT courseid, coursename, sum(points) / sum(maxpoints) as percentage FROM ( SELECT a.id, a.course as courseid, c.fullname as coursename, atl.studentid AS userid, COUNT(DISTINCT ats.id) AS numtakensessions, SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints FROM mdl_attendance_sessions ats JOIN mdl_attendance a ON a.id = ats.attendanceid JOIN mdl_course c ON c.id = a.course JOIN mdl_attendance_log atl ON (atl.sessionid = ats.id) JOIN mdl_attendance_statuses stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1) JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade FROM mdl_attendance_statuses WHERE deleted = 0 AND visible = 1 GROUP BY attendanceid, setnumber) stm ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid) {$joingroup} WHERE ats.sessdate >= c.startdate AND ats.lasttaken != 0 {$where} GROUP BY a.id, a.course, c.fullname, atl.studentid ) p GROUP by courseid, coursename {$orderby}"; return $DB->get_records_sql($sql, $params); }