Browse Source

Merge pull request #44 from drjosephbaxter/MOODLE_24-26_STABLE

UoN Changes for Moodle 24 26 stable
MOODLE_26_STABLE
Dan Marsden 11 years ago
parent
commit
ea15c2f505
  1. 4
      add_form.php
  2. 84
      attendance.php
  3. 4
      backup/moodle2/backup_attendance_activity_task.class.php
  4. 4
      backup/moodle2/restore_attendance_activity_task.class.php
  5. 1
      db/install.xml
  6. 12
      db/upgrade.php
  7. 17
      export.php
  8. 8
      export_form.php
  9. 8
      lang/en/attendance.php
  10. 170
      locallib.php
  11. 38
      renderables.php
  12. 63
      renderer.php
  13. 2
      report.php
  14. 6
      sessions.php
  15. 63
      student_attenance_form.php
  16. 105
      tests/behat/attendance_mod.feature
  17. 116
      tests/behat/behat_mod_attendance.php

4
add_form.php

@ -103,6 +103,10 @@ class mod_attendance_add_form extends moodleform {
$mform->addElement('checkbox', 'addmultiply', '', get_string('createmultiplesessions', 'attendance')); $mform->addElement('checkbox', 'addmultiply', '', get_string('createmultiplesessions', 'attendance'));
$mform->addHelpButton('addmultiply', 'createmultiplesessions', 'attendance'); $mform->addHelpButton('addmultiply', 'createmultiplesessions', 'attendance');
// Students can mark own attendance.
$mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark','attendance'));
$mform->addHelpButton('studentscanmark', 'studentscanmark', 'attendance');
$mform->addElement('date_time_selector', 'sessiondate', get_string('sessiondate', 'attendance')); $mform->addElement('date_time_selector', 'sessiondate', get_string('sessiondate', 'attendance'));
for ($i=0; $i<=23; $i++) { for ($i=0; $i<=23; $i++) {

84
attendance.php

@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Prints attendance info for particular user
*
* @package mod
* @subpackage attendance
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__).'/../../config.php');
require_once(dirname(__FILE__).'/locallib.php');
require_once(dirname(__FILE__).'/student_attenance_form.php');
$pageparams = new att_sessions_page_params();
// Check that the required parameters are present.
$id = required_param('sessid', PARAM_INT);
$attendance_session_id = required_param('sessid', PARAM_INT);
$attforsession = $DB->get_record('attendance_sessions', array('id' => $id), '*', MUST_EXIST);
$attendance = $DB->get_record('attendance', array('id' => $attforsession->attendanceid), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('attendance', $attendance->id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
// Require the user is logged in.
require_login($course, true, $cm);
$pageparams->sessionid = $id;
$att = new attendance($attendance, $cm, $course, $PAGE->context, $pageparams);
// Require that a session key is passed to this page.
require_sesskey();
// Create the form.
$mform = new mod_attendance_student_attendance_form(null,
array('course' => $course, 'cm' => $cm, 'modcontext' => $PAGE->context, 'session' => $attforsession, 'attendance' => $att));
if ($mform->is_cancelled()) {
// The user cancelled the form, so redirect them to the view page.
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
redirect($url);
} else if ($fromform = $mform->get_data()) {
if (!empty($fromform->status)) {
$success = $att->take_from_student($fromform);
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
if ($success) {
// Redirect back to the view page for the block.
redirect($url);
} else {
print_error ('attendance_already_submitted', 'mod_attendance', $url);
}
}
// The form did not validate correctly so we will set it to display the data they submitted.
$mform->set_data($fromform);
}
$PAGE->set_url($att->url_sessions());
$PAGE->set_title($course->shortname. ": ".$att->name);
$PAGE->set_heading($course->fullname);
$PAGE->set_cacheable(true);
$PAGE->navbar->add($att->name);
$output = $PAGE->get_renderer('mod_attendance');
echo $output->header();
$mform->display();
echo $output->footer();

4
backup/moodle2/backup_attendance_activity_task.class.php

@ -60,11 +60,11 @@ class backup_attendance_activity_task extends backup_activity_task {
// Link to attendance view by moduleid. // Link to attendance view by moduleid.
$search = "/(" . $base . "\/mod\/attendance\/view.php\?id\=)([0-9]+)/"; $search = "/(" . $base . "\/mod\/attendance\/view.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@ATTFORBLOCKVIEWBYID*$2@$', $content); $content= preg_replace($search, '$@ATTENDANCEVIEWBYID*$2@$', $content);
// Link to attendance view by moduleid and studentid. // Link to attendance view by moduleid and studentid.
$search = "/(" . $base . "\/mod\/attendance\/view.php\?id\=)([0-9]+)\&studentid\=([0-9]+)/"; $search = "/(" . $base . "\/mod\/attendance\/view.php\?id\=)([0-9]+)\&studentid\=([0-9]+)/";
$content= preg_replace($search, '$@ATTFORBLOCKVIEWBYIDSTUD*$2*$3@$', $content); $content= preg_replace($search, '$@ATTENDANCEVIEWBYIDSTUD*$2*$3@$', $content);
return $content; return $content;
} }

4
backup/moodle2/restore_attendance_activity_task.class.php

@ -67,9 +67,9 @@ class restore_attendance_activity_task extends restore_activity_task {
static public function define_decode_rules() { static public function define_decode_rules() {
$rules = array(); $rules = array();
$rules[] = new restore_decode_rule('ATTFORBLOCKVIEWBYID', $rules[] = new restore_decode_rule('ATTENDANCEVIEWBYID',
'/mod/attendance/view.php?id=$1', 'course_module'); '/mod/attendance/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('ATTFORBLOCKVIEWBYIDSTUD', $rules[] = new restore_decode_rule('ATTENDANCEVIEWBYIDSTUD',
'/mod/attendance/view.php?id=$1&studentid=$2', array('course_module', 'user')); '/mod/attendance/view.php?id=$1&studentid=$2', array('course_module', 'user'));
return $rules; return $rules;

1
db/install.xml

@ -30,6 +30,7 @@
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="description" TYPE="text" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="description" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/> <FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="studentscanmark" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS> </FIELDS>
<KEYS> <KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for attendance_sessions"/> <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for attendance_sessions"/>

12
db/upgrade.php

@ -34,6 +34,18 @@ function xmldb_attendance_upgrade($oldversion=0) {
$result = true; $result = true;
if ($oldversion < 2013082901) {
$table = new xmldb_table('attendance_sessions');
$field = new xmldb_field('studentscanmark');
$field->set_attributes(XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint($result, 2013082901, 'attendance');
}
if ($oldversion < 2013082902) { if ($oldversion < 2013082902) {
// Replace values that reference old module "attforblock" to "attendance". // Replace values that reference old module "attforblock" to "attendance".
$sql = "UPDATE {grade_items} $sql = "UPDATE {grade_items}

17
export.php

@ -88,6 +88,14 @@ if ($mform->is_submitted()) {
if (isset($formdata->ident['uname'])) { if (isset($formdata->ident['uname'])) {
$data->tabhead[] = get_string('username'); $data->tabhead[] = get_string('username');
} }
$optional = array('idnumber', 'institution', 'department');
foreach ($optional as $opt) {
if (isset($formdata->ident[$opt])) {
$data->tabhead[] = get_string($opt);
}
}
$data->tabhead[] = get_string('lastname'); $data->tabhead[] = get_string('lastname');
$data->tabhead[] = get_string('firstname'); $data->tabhead[] = get_string('firstname');
$groupmode = groups_get_activity_groupmode($cm, $course); $groupmode = groups_get_activity_groupmode($cm, $course);
@ -95,7 +103,6 @@ if ($mform->is_submitted()) {
$data->tabhead[] = get_string('groups'); $data->tabhead[] = get_string('groups');
} }
if (count($reportdata->sessions) > 0) { if (count($reportdata->sessions) > 0) {
foreach ($reportdata->sessions as $sess) { foreach ($reportdata->sessions as $sess) {
$text = userdate($sess->sessdate, get_string('strftimedmyhm', 'attendance')); $text = userdate($sess->sessdate, get_string('strftimedmyhm', 'attendance'));
@ -122,6 +129,14 @@ if ($mform->is_submitted()) {
if (isset($formdata->ident['uname'])) { if (isset($formdata->ident['uname'])) {
$data->table[$i][] = $user->username; $data->table[$i][] = $user->username;
} }
$optional_row = array('idnumber', 'institution', 'department');
foreach ($optional_row as $opt) {
if (isset($formdata->ident[$opt])) {
$data->table[$i][] = $user->$opt;
}
}
$data->table[$i][] = $user->lastname; $data->table[$i][] = $user->lastname;
$data->table[$i][] = $user->firstname; $data->table[$i][] = $user->firstname;
if (!empty($groupmode)) { if (!empty($groupmode)) {

8
export_form.php

@ -62,8 +62,14 @@ class mod_attendance_export_form extends moodleform {
$ident = array(); $ident = array();
$ident[] =& $mform->createElement('checkbox', 'id', '', get_string('studentid', 'attendance')); $ident[] =& $mform->createElement('checkbox', 'id', '', get_string('studentid', 'attendance'));
$ident[] =& $mform->createElement('checkbox', 'uname', '', get_string('username')); $ident[] =& $mform->createElement('checkbox', 'uname', '', get_string('username'));
$optional = array('idnumber', 'institution', 'department');
foreach ($optional as $opt) {
$ident[] =& $mform->createElement('checkbox', $opt, '', get_string($opt));
$mform->setType($opt, PARAM_NOTAGS);
}
$mform->addGroup($ident, 'ident', get_string('identifyby', 'attendance'), array('<br />'), true); $mform->addGroup($ident, 'ident', get_string('identifyby', 'attendance'), array('<br />'), true);
$mform->setDefaults(array('ident[id]' => true, 'ident[uname]' => true)); $mform->setDefaults(array('ident[id]' => true, 'ident[uname]' => true));
$mform->setType('id', PARAM_INT); $mform->setType('id', PARAM_INT);

8
lang/en/attendance.php

@ -216,3 +216,11 @@ $string['viewmode'] = 'View mode';
$string['week'] = 'week(s)'; $string['week'] = 'week(s)';
$string['weeks'] = 'Weeks'; $string['weeks'] = 'Weeks';
$string['youcantdo'] = 'You can\'t do anything'; $string['youcantdo'] = 'You can\'t do anything';
// New strings.
$string['studentscanmark'] = 'Allow students to record own attendance';
$string['studentscanmark_help'] = 'If checked students will be able to change their own attendance status for the session.';
$string['set_by_student'] = 'Self-recorded';
$string['attendance_already_submitted'] = 'You may not self register attendance that has already been set.';
$string['lowgrade'] = 'Low grade';
$string['submitattendance'] = 'Submit attendance';
$string['attendancenotset'] = 'You must set your attendance';

170
locallib.php

@ -33,6 +33,7 @@ define('ATT_VIEW_WEEKS', 2);
define('ATT_VIEW_MONTHS', 3); define('ATT_VIEW_MONTHS', 3);
define('ATT_VIEW_ALLPAST', 4); define('ATT_VIEW_ALLPAST', 4);
define('ATT_VIEW_ALL', 5); define('ATT_VIEW_ALL', 5);
define('ATT_VIEW_NOTPRESENT', 6);
define('ATT_SORT_LASTNAME', 1); define('ATT_SORT_LASTNAME', 1);
define('ATT_SORT_FIRSTNAME', 2); define('ATT_SORT_FIRSTNAME', 2);
@ -701,6 +702,8 @@ class attendance {
if ($this->pageparams->startdate && $this->pageparams->enddate) { if ($this->pageparams->startdate && $this->pageparams->enddate) {
$where = "attendanceid = :aid AND sessdate >= :csdate AND sessdate >= :sdate AND sessdate < :edate"; $where = "attendanceid = :aid AND sessdate >= :csdate AND sessdate >= :sdate AND sessdate < :edate";
} else if ($this->pageparams->enddate) {
$where = "attendanceid = :aid AND sessdate >= :csdate AND sessdate < :edate";
} else { } else {
$where = "attendanceid = :aid AND sessdate >= :csdate"; $where = "attendanceid = :aid AND sessdate >= :csdate";
} }
@ -807,6 +810,7 @@ class attendance {
} }
$i++; $i++;
} }
add_to_log($this->course->id, 'attendance', 'sessions added', $this->url_manage(), add_to_log($this->course->id, 'attendance', 'sessions added', $this->url_manage(),
implode(',', $info_array), $this->cm->id); implode(',', $info_array), $this->cm->id);
} }
@ -833,6 +837,65 @@ class attendance {
add_to_log($this->course->id, 'attendance', 'session updated', $url, $info, $this->cm->id); add_to_log($this->course->id, 'attendance', 'session updated', $url, $info, $this->cm->id);
} }
/**
* Used to record attendance submitted by the student.
*
* @global type $DB
* @global type $USER
* @param type $mformdata
* @return boolean
*/
public function take_from_student($mformdata) {
global $DB, $USER;
$statuses = implode(',', array_keys( (array)$this->get_statuses() ));
$now = time();
$record = new stdClass();
$record->studentid = $USER->id;
$record->statusid = $mformdata->status;
$record->statusset = $statuses;
$record->remarks = get_string('set_by_student', 'mod_attendance');
$record->sessionid = $mformdata->sessid;
$record->timetaken = $now;
$record->takenby = $USER->id;
$dbsesslog = $this->get_session_log($mformdata->sessid);
if (array_key_exists($record->studentid, $dbsesslog)) {
// Already recorded do not save.
return false;
}
else {
$DB->insert_record('attendance_log', $record, false);
}
// Update the session to show that a register has been taken, or staff may overwrite records.
$rec = new object();
$rec->id = $mformdata->sessid;
$rec->lasttaken = $now;
$rec->lasttakenby = $USER->id;
$DB->update_record('attendance_sessions', $rec);
// Update the users grade.
$this->update_users_grade(array($USER->id));
/* create url for link in log screen
* need to set grouptype to 0 to allow take attendance page to be called
* from report/log page */
$params = array(
'sessionid' => $this->pageparams->sessionid,
'grouptype' => 0);
$url = $this->url_take($params);
$logurl = att_log_convert_url($url);
// Log the change.
add_to_log($this->course->id, 'attendance', 'taken by student', $logurl, '', $this->cm->id);
return true;
}
public function take_from_form_data($formdata) { public function take_from_form_data($formdata) {
global $DB, $USER; global $DB, $USER;
// TODO: WARNING - $formdata is unclean - comes from direct $_POST - ideally needs a rewrite but we do some cleaning below. // TODO: WARNING - $formdata is unclean - comes from direct $_POST - ideally needs a rewrite but we do some cleaning below.
@ -880,11 +943,16 @@ class attendance {
$this->update_users_grade(array_keys($sesslog)); $this->update_users_grade(array_keys($sesslog));
} }
// create url for link in log screen
$params = array( $params = array(
'sessionid' => $this->pageparams->sessionid, 'sessionid' => $this->pageparams->sessionid,
'grouptype' => $this->pageparams->grouptype); 'grouptype' => $this->pageparams->grouptype);
$url = $this->url_take($params); $url = $this->url_take($params);
add_to_log($this->course->id, 'attendance', 'taken', $url, '', $this->cm->id); $logurl = att_log_convert_url($url);
// Log the change.
add_to_log($this->course->id, 'attendance', 'taken', $logurl, '', $this->cm->id);
$group = 0; $group = 0;
if ($this->pageparams->grouptype != attendance::SESSION_COMMON) { if ($this->pageparams->grouptype != attendance::SESSION_COMMON) {
@ -916,12 +984,12 @@ class attendance {
global $DB, $CFG; global $DB, $CFG;
// Fields we need from the user table. // Fields we need from the user table.
$userfields = user_picture::fields('u', array('username')); $userfields = user_picture::fields('u', array('username' , 'idnumber' , 'institution' , 'department'));
if (isset($this->pageparams->sort) and ($this->pageparams->sort == ATT_SORT_FIRSTNAME)) { if (isset($this->pageparams->sort) and ($this->pageparams->sort == ATT_SORT_FIRSTNAME)) {
$orderby = "u.firstname ASC, u.lastname ASC"; $orderby = "u.firstname ASC, u.lastname ASC, u.idnumber ASC, u.institution ASC, u.department ASC";
} else { } else {
$orderby = "u.lastname ASC, u.firstname ASC"; $orderby = "u.lastname ASC, u.firstname ASC, u.idnumber ASC, u.institution ASC, u.department ASC";
} }
if ($page) { if ($page) {
@ -1082,13 +1150,36 @@ class attendance {
return $this->usertakensesscount[$userid]; return $this->usertakensesscount[$userid];
} }
public function get_user_statuses_stat($userid) { /**
*
* @global type $DB
* @param type $userid
* @param type $filters - An array things to filter by. For now only enddate is valid.
* @return type
*/
public function get_user_statuses_stat($userid, array $filters = null) {
global $DB; global $DB;
$params = array( $params = array(
'aid' => $this->id, 'aid' => $this->id,
'cstartdate' => $this->course->startdate, 'cstartdate' => $this->course->startdate,
'uid' => $userid); 'uid' => $userid);
$processed_filters = array();
// We test for any valid filters sent.
if (isset($filters['enddate'])) {
$processed_filters[] = 'ats.sessdate <= :enddate';
$params['enddate'] = $filters['enddate'];
}
// Make the filter array into a SQL string.
if (!empty($processed_filters)) {
$processed_filters = ' AND '.implode(' AND ', $processed_filters);
} else {
$processed_filters = '';
}
$period = ''; $period = '';
if (!empty($this->pageparams->startdate) && !empty($this->pageparams->enddate)) { if (!empty($this->pageparams->startdate) && !empty($this->pageparams->enddate)) {
$period = ' AND ats.sessdate >= :sdate AND ats.sessdate < :edate '; $period = ' AND ats.sessdate >= :sdate AND ats.sessdate < :edate ';
@ -1096,38 +1187,47 @@ class attendance {
$params['edate'] = $this->pageparams->enddate; $params['edate'] = $this->pageparams->enddate;
} }
if (!array_key_exists($userid, $this->userstatusesstat)) { if ($this->get_group_mode()) {
if ($this->get_group_mode()) { $qry = "SELECT al.statusid, count(al.statusid) AS stcnt
$qry = "SELECT al.statusid, count(al.statusid) AS stcnt FROM {attendance_log} al
FROM {attendance_log} al JOIN {attendance_sessions} ats ON al.sessionid = ats.id
JOIN {attendance_sessions} ats ON al.sessionid = ats.id LEFT JOIN {groups_members} gm ON gm.userid = al.studentid AND gm.groupid = ats.groupid
LEFT JOIN {groups_members} gm ON gm.userid = al.studentid AND gm.groupid = ats.groupid WHERE ats.attendanceid = :aid AND
WHERE ats.attendanceid = :aid AND ats.sessdate >= :cstartdate AND
ats.sessdate >= :cstartdate AND al.studentid = :uid AND
al.studentid = :uid AND (ats.groupid = 0 or gm.id is NOT NULL)".$period.$processed_filters."
(ats.groupid = 0 or gm.id is NOT NULL)".$period." GROUP BY al.statusid";
GROUP BY al.statusid"; } else {
} else { $qry = "SELECT al.statusid, count(al.statusid) AS stcnt
$qry = "SELECT al.statusid, count(al.statusid) AS stcnt FROM {attendance_log} al
FROM {attendance_log} al JOIN {attendance_sessions} ats
JOIN {attendance_sessions} ats ON al.sessionid = ats.id
ON al.sessionid = ats.id WHERE ats.attendanceid = :aid AND
WHERE ats.attendanceid = :aid AND ats.sessdate >= :cstartdate AND
ats.sessdate >= :cstartdate AND al.studentid = :uid".$period.$processed_filters."
al.studentid = :uid".$period." GROUP BY al.statusid";
GROUP BY al.statusid"; }
} // We do not want to cache, or use a cached version of the results when a filter is set.
if ($filters !== null) {
return $DB->get_records_sql($qry, $params);
} else if (!array_key_exists($userid, $this->userstatusesstat)) {
// Not filtered so if we do not already have them do the query.
$this->userstatusesstat[$userid] = $DB->get_records_sql($qry, $params); $this->userstatusesstat[$userid] = $DB->get_records_sql($qry, $params);
} }
// Return the cached stats.
return $this->userstatusesstat[$userid]; return $this->userstatusesstat[$userid];
} }
public function get_user_grade($userid) { /**
return att_get_user_grade($this->get_user_statuses_stat($userid), $this->get_statuses()); *
* @param type $userid
* @param type $filters - An array things to filter by. For now only enddate is valid.
* @return type
*/
public function get_user_grade($userid, array $filters = null) {
return att_get_user_grade($this->get_user_statuses_stat($userid, $filters), $this->get_statuses());
} }
// For getting sessions count implemented simplest method - taken sessions. // For getting sessions count implemented simplest method - taken sessions.
@ -1215,7 +1315,7 @@ class attendance {
// It would be better as a UNION query butunfortunatly MS SQL does not seem to support doing a DISTINCT on a the description field. // It would be better as a UNION query butunfortunatly MS SQL does not seem to support doing a DISTINCT on a the description field.
$id = $DB->sql_concat(':value', 'ats.id'); $id = $DB->sql_concat(':value', 'ats.id');
if ($this->get_group_mode()) { if ($this->get_group_mode()) {
$sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks $sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks, ats.studentscanmark
FROM {attendance_sessions} ats FROM {attendance_sessions} ats
RIGHT JOIN {attendance_log} al RIGHT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid ON ats.id = al.sessionid AND al.studentid = :uid
@ -1223,7 +1323,7 @@ class attendance {
WHERE $where AND (ats.groupid = 0 or gm.id is NOT NULL) WHERE $where AND (ats.groupid = 0 or gm.id is NOT NULL)
ORDER BY ats.sessdate ASC"; ORDER BY ats.sessdate ASC";
} else { } else {
$sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks $sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks, ats.studentscanmark
FROM {attendance_sessions} ats FROM {attendance_sessions} ats
RIGHT JOIN {attendance_log} al RIGHT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid ON ats.id = al.sessionid AND al.studentid = :uid
@ -1253,7 +1353,7 @@ class attendance {
$where = "ats.attendanceid = :aid AND ats.sessdate >= :csdate AND ats.groupid $gsql"; $where = "ats.attendanceid = :aid AND ats.sessdate >= :csdate AND ats.groupid $gsql";
} }
$sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks $sql = "SELECT $id, ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks, ats.studentscanmark
FROM {attendance_sessions} ats FROM {attendance_sessions} ats
LEFT JOIN {attendance_log} al LEFT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid ON ats.id = al.sessionid AND al.studentid = :uid
@ -1353,8 +1453,8 @@ class attendance {
add_to_log($this->course->id, 'attendance', 'status updated', $this->url_preferences(), add_to_log($this->course->id, 'attendance', 'status updated', $this->url_preferences(),
implode(' ', $updated), $this->cm->id); implode(' ', $updated), $this->cm->id);
} }
}
}
function att_get_statuses($attid, $onlyvisible=true) { function att_get_statuses($attid, $onlyvisible=true) {
global $DB; global $DB;

38
renderables.php

@ -109,19 +109,23 @@ class attendance_filter_controls implements renderable {
public $prevcur; public $prevcur;
public $nextcur; public $nextcur;
public $curdatetxt; public $curdatetxt;
public $reportcontrol;
private $urlpath; private $urlpath;
private $urlparams; private $urlparams;
private $att; private $att;
public function __construct(attendance $att) { public function __construct(attendance $att, $report = false) {
global $PAGE; global $PAGE;
$this->pageparams = $att->pageparams; $this->pageparams = $att->pageparams;
$this->cm = $att->cm; $this->cm = $att->cm;
// This is a report control only if $reports is true and the attendance block can be graded.
$this->reportcontrol = $report && ($att->grade > 0);
$this->curdate = $att->pageparams->curdate; $this->curdate = $att->pageparams->curdate;
$date = usergetdate($att->pageparams->curdate); $date = usergetdate($att->pageparams->curdate);
@ -456,6 +460,12 @@ class attendance_report_data implements renderable {
global $CFG; global $CFG;
$this->perm = $att->perm; $this->perm = $att->perm;
$currenttime = time();
if ($att->pageparams->view == ATT_VIEW_NOTPRESENT) {
$att->pageparams->enddate = $currenttime;
}
$this->pageparams = $att->pageparams; $this->pageparams = $att->pageparams;
$this->users = $att->get_users($att->pageparams->group, $att->pageparams->page); $this->users = $att->get_users($att->pageparams->group, $att->pageparams->page);
@ -473,16 +483,28 @@ class attendance_report_data implements renderable {
$this->decimalpoints = $CFG->grade_decimalpoints; $this->decimalpoints = $CFG->grade_decimalpoints;
} }
foreach ($this->users as $user) { $maxgrade = att_get_user_max_grade(count($this->sessions), $this->statuses);
$this->usersgroups[$user->id] = groups_get_all_groups($att->course->id, $user->id);
$this->sessionslog[$user->id] = $att->get_user_filtered_sessions_log($user->id); foreach ($this->users as $key => $user) {
$grade = 0;
if ($this->gradable) {
$grade = $att->get_user_grade($user->id, array('enddate' => $currenttime));
$totalgrade = $att->get_user_grade($user->id);
}
$this->usersstats[$user->id] = $att->get_user_statuses_stat($user->id); if ($att->pageparams->view != ATT_VIEW_NOTPRESENT || $grade < $maxgrade) {
$this->usersgroups[$user->id] = groups_get_all_groups($att->course->id, $user->id);
if ($this->gradable) { $this->sessionslog[$user->id] = $att->get_user_filtered_sessions_log($user->id);
$this->grades[$user->id] = $att->get_user_grade($user->id);
$this->maxgrades[$user->id] = $att->get_user_max_grade($user->id); $this->usersstats[$user->id] = $att->get_user_statuses_stat($user->id);
if ($this->gradable) {
$this->grades[$user->id] = $totalgrade;
$this->maxgrades[$user->id] = $att->get_user_max_grade($user->id);;
}
} else {
unset($this->users[$key]);
} }
} }

63
renderer.php

@ -104,12 +104,12 @@ class mod_attendance_renderer extends plugin_renderer_base {
} }
$totalusers = count_enrolled_users(context_module::instance($fcontrols->cm->id), 'mod/attendance:canbelisted', $group); $totalusers = count_enrolled_users(context_module::instance($fcontrols->cm->id), 'mod/attendance:canbelisted', $group);
$usersperpage = $fcontrols->pageparams->perpage;
if (empty($fcontrols->pageparams->page) || !$fcontrols->pageparams->page || !$totalusers || !$usersperpage) { if (empty($fcontrols->pageparams->page) || !$fcontrols->pageparams->page || !$totalusers || empty($fcontrols->pageparams->perpage)) {
return $paging_controls; return $paging_controls;
} }
$numberofpages = ceil($totalusers / $usersperpage); $numberofpages = ceil($totalusers / $fcontrols->pageparams->perpage);
if ($fcontrols->pageparams->page > 1) { if ($fcontrols->pageparams->page > 1) {
$paging_controls .= html_writer::link($fcontrols->url(array('curdate' => $fcontrols->nextcur, 'page' => $fcontrols->pageparams->page - 1)), $paging_controls .= html_writer::link($fcontrols->url(array('curdate' => $fcontrols->nextcur, 'page' => $fcontrols->pageparams->page - 1)),
@ -173,6 +173,9 @@ class mod_attendance_renderer extends plugin_renderer_base {
protected function render_view_controls(attendance_filter_controls $fcontrols) { protected function render_view_controls(attendance_filter_controls $fcontrols) {
$views[ATT_VIEW_ALL] = get_string('all', 'attendance'); $views[ATT_VIEW_ALL] = get_string('all', 'attendance');
$views[ATT_VIEW_ALLPAST] = get_string('allpast', 'attendance'); $views[ATT_VIEW_ALLPAST] = get_string('allpast', 'attendance');
if ($fcontrols->reportcontrol) {
$views[ATT_VIEW_NOTPRESENT] = get_string('lowgrade', 'attendance');
}
$views[ATT_VIEW_MONTHS] = get_string('months', 'attendance'); $views[ATT_VIEW_MONTHS] = get_string('months', 'attendance');
$views[ATT_VIEW_WEEKS] = get_string('weeks', 'attendance'); $views[ATT_VIEW_WEEKS] = get_string('weeks', 'attendance');
$views[ATT_VIEW_DAYS] = get_string('days', 'attendance'); $views[ATT_VIEW_DAYS] = get_string('days', 'attendance');
@ -734,8 +737,17 @@ class mod_attendance_renderer extends plugin_renderer_base {
$cell->colspan = 2; $cell->colspan = 2;
$row->cells[] = $cell; $row->cells[] = $cell;
} else { } else {
$row->cells[] = '?'; if (!empty($sess->studentscanmark)) { // Student can mark their own attendance.
$row->cells[] = ''; // URL to the page that lets the student modify their attendance.
$url = new moodle_url('/mod/attendance/attendance.php',
array('sessid' => $sess->id, 'sesskey' => sesskey()));
$cell = new html_table_cell(html_writer::link($url, get_string('submitattendance', 'attendance')));
$cell->colspan = 2;
$row->cells[] = $cell;
} else { // Student cannot mark their own attendace.
$row->cells[] = '?';
$row->cells[] = '';
}
} }
$table->data[] = $row; $table->data[] = $row;
@ -751,6 +763,14 @@ class mod_attendance_renderer extends plugin_renderer_base {
} }
protected function render_attendance_report_data(attendance_report_data $reportdata) { protected function render_attendance_report_data(attendance_report_data $reportdata) {
global $PAGE;
// Initilise Javascript used to (un)check all checkboxes.
$this->page->requires->js_init_call('M.mod_attendance.init_manage');
// Check if the user should be able to bulk send messages to other users on the course.
$bulkmessagecapability = has_capability('moodle/course:bulkmessaging', $PAGE->context);
$table = new html_table(); $table = new html_table();
$table->attributes['class'] = 'generaltable attwidth'; $table->attributes['class'] = 'generaltable attwidth';
@ -799,6 +819,13 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table->size[] = '200px'; $table->size[] = '200px';
} }
if ($bulkmessagecapability) { // Display the table header for bulk messaging.
// The checkbox must have an id of cb_selector so that the JavaScript will pick it up.
$table->head[] = html_writer::checkbox('cb_selector', 0, false, '', array('id' => 'cb_selector'));
$table->align[] = 'center';
$table->size[] = '1px';
}
foreach ($reportdata->users as $user) { foreach ($reportdata->users as $user) {
$row = new html_table_row(); $row = new html_table_row();
@ -827,6 +854,11 @@ class mod_attendance_renderer extends plugin_renderer_base {
$row->cells[] = ''; $row->cells[] = '';
} }
} }
if ($bulkmessagecapability) { // Create the checkbox for bulk messaging.
$row->cells[] = html_writer::checkbox('user'.$user->id, 'on', false);
}
$table->data[] = $row; $table->data[] = $row;
} }
@ -837,7 +869,9 @@ class mod_attendance_renderer extends plugin_renderer_base {
foreach ($reportdata->sessions as $sess) { foreach ($reportdata->sessions as $sess) {
foreach ($reportdata->users as $user) { foreach ($reportdata->users as $user) {
foreach($reportdata->statuses as $status) { foreach($reportdata->statuses as $status) {
if ($reportdata->sessionslog[$user->id][$sess->id]->statusid == $status->id) $sessionstats[$status->id]++; if (!empty($reportdata->sessionslog[$user->id][$sess->id])) {
if ($reportdata->sessionslog[$user->id][$sess->id]->statusid == $status->id) $sessionstats[$status->id]++;
}
} }
} }
@ -850,7 +884,22 @@ class mod_attendance_renderer extends plugin_renderer_base {
} }
$table->data[] = $statrow; $table->data[] = $statrow;
return html_writer::table($table).html_writer::tag('div', get_string('users').': '.count($reportdata->users)); if ($bulkmessagecapability) { // Require that the user can bulk message users.
// Display check boxes that will allow the user to send a message to the students that have been checked.
$output = html_writer::empty_tag('input', array('name' => 'sesskey', 'type' => 'hidden', 'value' => sesskey()));
$output .= html_writer::empty_tag('input', array('name' => 'formaction', 'type' => 'hidden', 'value' => 'messageselect.php'));
$output .= html_writer::empty_tag('input', array('name' => 'id', 'type' => 'hidden', 'value' => $GLOBALS['COURSE']->id));
$output .= html_writer::empty_tag('input', array('name' => 'returnto', 'type' => 'hidden', 'value' => s(me())));
$output .= html_writer::table($table).html_writer::tag('div', get_string('users').': '.count($reportdata->users));;
$output .= html_writer::tag('div',
html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('messageselectadd'))),
array('class' => 'buttons'));
$url = new moodle_url('/user/action_redir.php');
return html_writer::tag('form', $output, array('action' => $url->out(), 'method' => 'post'));
} else {
return html_writer::table($table).html_writer::tag('div', get_string('users').': '.count($reportdata->users));
}
} }
protected function render_attendance_preferences_data(attendance_preferences_data $prefdata) { protected function render_attendance_preferences_data(attendance_preferences_data $prefdata) {

2
report.php

@ -57,7 +57,7 @@ $PAGE->navbar->add(get_string('report', 'attendance'));
$output = $PAGE->get_renderer('mod_attendance'); $output = $PAGE->get_renderer('mod_attendance');
$tabs = new attendance_tabs($att, attendance_tabs::TAB_REPORT); $tabs = new attendance_tabs($att, attendance_tabs::TAB_REPORT);
$filtercontrols = new attendance_filter_controls($att); $filtercontrols = new attendance_filter_controls($att, true);
$reportdata = new attendance_report_data($att); $reportdata = new attendance_report_data($att);
add_to_log($course->id, 'attendance', 'report viewed', '/mod/attendance/report.php?id='.$id, '', $cm->id); add_to_log($course->id, 'attendance', 'report viewed', '/mod/attendance/report.php?id='.$id, '', $cm->id);

6
sessions.php

@ -220,6 +220,9 @@ function construct_sessions_data_for_add($formdata) {
$sess->description = $formdata->sdescription['text']; $sess->description = $formdata->sdescription['text'];
$sess->descriptionformat = $formdata->sdescription['format']; $sess->descriptionformat = $formdata->sdescription['format'];
$sess->timemodified = $now; $sess->timemodified = $now;
if (isset($formdata->studentscanmark)) { // Students will be able to mark their own attendance.
$sess->studentscanmark = 1;
}
fill_groupid($formdata, $sessions, $sess); fill_groupid($formdata, $sessions, $sess);
} }
@ -237,6 +240,9 @@ function construct_sessions_data_for_add($formdata) {
$sess->description = $formdata->sdescription['text']; $sess->description = $formdata->sdescription['text'];
$sess->descriptionformat = $formdata->sdescription['format']; $sess->descriptionformat = $formdata->sdescription['format'];
$sess->timemodified = $now; $sess->timemodified = $now;
if (isset($formdata->studentscanmark)) { // Students will be able to mark their own attendance.
$sess->studentscanmark = 1;
}
fill_groupid($formdata, $sessions, $sess); fill_groupid($formdata, $sessions, $sess);
} }

63
student_attenance_form.php

@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
require_once($CFG->libdir.'/formslib.php');
class mod_attendance_student_attendance_form extends moodleform {
public function definition() {
global $CFG, $USER;
$mform =& $this->_form;
$course = $this->_customdata['course'];
$cm = $this->_customdata['cm'];
$modcontext = $this->_customdata['modcontext'];
$attforsession = $this->_customdata['session'];
$attblock = $this->_customdata['attendance'];
$statuses = $attblock->get_statuses();
$mform->addElement('hidden', 'sessid', null);
$mform->setType('sessid', PARAM_INT);
$mform->setConstant('sessid', $attforsession->id);
$mform->addElement('hidden', 'sesskey', null);
$mform->setType('sesskey', PARAM_INT);
$mform->setConstant('sesskey', sesskey());
// Set a title as the date and time of the session.
$sesstiontitle = userdate($attforsession->sessdate, get_string('strftimedate')).' '
.userdate($attforsession->sessdate, get_string('strftimehm', 'mod_attendance'));
$mform->addElement('header', 'session', $sesstiontitle);
// If a session description is set display it.
if (!empty($attforsession->description)) {
$mform->addElement('html', $attforsession->description);
}
// Create radio buttons for setting the attendance status.
$radioarray = array();
foreach ($statuses as $status) {
$radioarray[] =& $mform->createElement('radio', 'status', '', $status->description, $status->id, array());
}
// Add the radio buttons as a control with the user's name in front.
$mform->addGroup($radioarray, 'statusarray', $USER->firstname.' '.$USER->lastname.':', array(''), false);
$mform->addRule('statusarray', get_string('attendancenotset', 'attendance'), 'required', '', 'client', false, false);
$this->add_action_buttons();
}
}

105
tests/behat/attendance_mod.feature

@ -0,0 +1,105 @@
@mod @uon @mod_attendance
Feature: Teachers and Students can record session attendance
In order to record session attendance
As a student
I need to be able to mark my own attendance to a session
And as a teacher
I need to be able to mark any students attendance to a session
In order to report on session attendance
As a teacher
I need to be able to export session attendance and run reports
In order to contact students with poor attendance
As a teacher
I need the ability to message a group of students with low attendance
Background:
Given the following "courses" exist:
| fullname | shortname | summary | category |
| Course 1 | C101 | Prove the attendance activity works | 0 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber | department | institution |
| student1 | Sam | Student | student1@asd.com | 1234 | computer science | University of Nottingham |
| teacher1 | Teacher | One | teacher1@asd.com | 5678 | computer science | University of Nottingham |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C101 | student |
| teacher1 | C101 | editingteacher |
And I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I add a "Attendance" to section "1"
And I press "Save and display"
And I log out
Scenario: Students can mark their own attendance
When I log in as "teacher1"
And I follow "Course 1"
And I follow "Attendance"
And I follow "Add"
And I check "Allow students to record own attendance"
And I set the following fields to these values:
| id_sessiondate_hour | 23 |
And I click on "id_submitbutton" "button"
And I follow "Continue"
And I log out
When I log in as "student1"
And I follow "Course 1"
And I follow "Attendance"
And I follow "Submit attendance"
And I check "Present"
And I press "Save changes"
Then I should see "Self-recorded"
And I log out
When I log in as "teacher1"
And I follow "Course 1"
And I expand "Reports" node
And I follow "Logs"
And I click on "Get these logs" "button"
Then "attendance taken by student" "link" should exist
Scenario: Teachers can view low grade report and send a message
When I log in as "teacher1"
And I follow "Course 1"
And I follow "Attendance"
And I follow "Add"
And I set the following fields to these values:
| id_sessiondate_hour | 01 |
And I click on "id_submitbutton" "button"
And I follow "Continue"
And I follow "Report"
And I follow "Low grade"
And I check "user3"
And I click on "Send a message" "button"
Then I should see "Message body"
And I should see "student1@asd.com"
And I expand "Reports" node
And I follow "Logs"
And I click on "Get these logs" "button"
Then "attendance report viewed" "link" should exist
# Dependency - selenium running with firefox profile with auto saving of txt files to $CFG->behat_download.
@javascript @_file_download
Scenario: Export report includes id number, department and institution
When I log in as "teacher1"
And I follow "Course 1"
And I follow "Attendance"
And I follow "Add"
And I set the following fields to these values:
| id_sessiondate_hour | 01 |
And I click on "id_submitbutton" "button"
And I follow "Continue"
And I follow "Export"
Then the "id_ident_idnumber" checkbox should not be checked
And the "id_ident_institution" checkbox should not be checked
And the "id_ident_department" checkbox should not be checked
And I check "id_ident_idnumber"
And I check "id_ident_institution"
And I check "id_ident_department"
And I set the following fields to these values:
| format | Download in text format |
And I click on "OK" "button"
Then attendance export file is ok
And I should see "ID number" as "1234" in the file
And I should see "Department" as "computer science" in the file
And I should see "Institution" as "University of Nottingham" in the file

116
tests/behat/behat_mod_attendance.php

@ -0,0 +1,116 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Behat\Exception\PendingException as PendingException;
/**
* Attendance steps definitions.
*
* @package mod
* @subpackage attendance
* @category test
* @copyright 2014 University of Nottingham
* @author Joseph Baxter (joseph.baxter@nottingham.ac.uk)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_attendance extends behat_base {
protected $file_contents;
/**
* @Then /^attendance export file is ok$/
*/
public function attendance_export_file_is_ok() {
global $CFG;
$check = true;
// Location selenium will download to.
$dir = $CFG->behat_download;
$files = scandir($dir, 1);
$filename = $files[0];
$file = fopen($dir . $filename, "r");
$count = 0;
$header = null;
// The file is tab seperated but not exactly a tsv.
while (($row = fgetcsv($file, 0, "\t")) !== FALSE) {
// Ignore unwanted information at the start of the file.
if ($count < 3) {
$count++;
continue;
}
if (!$header) {
$header = $row;
} else {
$this->file_contents = array_combine($header, $row);
}
$count++;
}
fclose($file);
unlink($dir . $filename);
// Check if data rows exist.
if ($count < 2) {
$check = false;
}
if ($check) {
return true;
} else {
throw new ExpectationException('Attendance export file not ok', $this->getSession());
}
}
/**
* @Given /^I should see "([^"]*)" as "([^"]*)" in the file$/
*/
public function i_should_see_as_in_the_file($field, $value) {
foreach ($this->file_contents as $array_field => $array_value) {
if ($field == $array_field) {
if ($value == $array_value) {
return true;
} else {
throw new PendingException();
}
}
}
}
}
Loading…
Cancel
Save