diff --git a/add_form.php b/add_form.php
index 9fc813a..a9550a3 100644
--- a/add_form.php
+++ b/add_form.php
@@ -103,6 +103,10 @@ class mod_attendance_add_form extends moodleform {
$mform->addElement('checkbox', 'addmultiply', '', get_string('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'));
for ($i=0; $i<=23; $i++) {
diff --git a/attendance.php b/attendance.php
new file mode 100644
index 0000000..539354c
--- /dev/null
+++ b/attendance.php
@@ -0,0 +1,84 @@
+.
+
+/**
+ * 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();
diff --git a/backup/moodle2/backup_attendance_activity_task.class.php b/backup/moodle2/backup_attendance_activity_task.class.php
index 87b5c89..df3b4a0 100644
--- a/backup/moodle2/backup_attendance_activity_task.class.php
+++ b/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.
$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.
$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;
}
diff --git a/backup/moodle2/restore_attendance_activity_task.class.php b/backup/moodle2/restore_attendance_activity_task.class.php
index 5ca7794..629cecd 100644
--- a/backup/moodle2/restore_attendance_activity_task.class.php
+++ b/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() {
$rules = array();
- $rules[] = new restore_decode_rule('ATTFORBLOCKVIEWBYID',
+ $rules[] = new restore_decode_rule('ATTENDANCEVIEWBYID',
'/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'));
return $rules;
diff --git a/db/install.xml b/db/install.xml
index 1bc5a05..ca4e6b0 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -30,6 +30,7 @@
+
diff --git a/db/upgrade.php b/db/upgrade.php
index c00926f..579f80f 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -34,6 +34,18 @@ function xmldb_attendance_upgrade($oldversion=0) {
$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) {
// Replace values that reference old module "attforblock" to "attendance".
$sql = "UPDATE {grade_items}
diff --git a/export.php b/export.php
index a0a690a..9b6fb99 100644
--- a/export.php
+++ b/export.php
@@ -88,6 +88,14 @@ if ($mform->is_submitted()) {
if (isset($formdata->ident['uname'])) {
$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('firstname');
$groupmode = groups_get_activity_groupmode($cm, $course);
@@ -95,7 +103,6 @@ if ($mform->is_submitted()) {
$data->tabhead[] = get_string('groups');
}
-
if (count($reportdata->sessions) > 0) {
foreach ($reportdata->sessions as $sess) {
$text = userdate($sess->sessdate, get_string('strftimedmyhm', 'attendance'));
@@ -122,6 +129,14 @@ if ($mform->is_submitted()) {
if (isset($formdata->ident['uname'])) {
$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->firstname;
if (!empty($groupmode)) {
diff --git a/export_form.php b/export_form.php
index 994c5e1..766e863 100644
--- a/export_form.php
+++ b/export_form.php
@@ -62,8 +62,14 @@ class mod_attendance_export_form extends moodleform {
$ident = array();
$ident[] =& $mform->createElement('checkbox', 'id', '', get_string('studentid', 'attendance'));
-
$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('
'), true);
$mform->setDefaults(array('ident[id]' => true, 'ident[uname]' => true));
$mform->setType('id', PARAM_INT);
diff --git a/lang/en/attendance.php b/lang/en/attendance.php
index 4c8df1f..ee35575 100755
--- a/lang/en/attendance.php
+++ b/lang/en/attendance.php
@@ -216,3 +216,11 @@ $string['viewmode'] = 'View mode';
$string['week'] = 'week(s)';
$string['weeks'] = 'Weeks';
$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';
diff --git a/locallib.php b/locallib.php
index ace0f42..f30c292 100644
--- a/locallib.php
+++ b/locallib.php
@@ -33,6 +33,7 @@ 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_SORT_LASTNAME', 1);
define('ATT_SORT_FIRSTNAME', 2);
@@ -701,6 +702,8 @@ class attendance {
if ($this->pageparams->startdate && $this->pageparams->enddate) {
$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 {
$where = "attendanceid = :aid AND sessdate >= :csdate";
}
@@ -807,6 +810,7 @@ class attendance {
}
$i++;
}
+
add_to_log($this->course->id, 'attendance', 'sessions added', $this->url_manage(),
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);
}
+ /**
+ * 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) {
global $DB, $USER;
// 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));
}
+ // create url for link in log screen
$params = array(
'sessionid' => $this->pageparams->sessionid,
'grouptype' => $this->pageparams->grouptype);
+
$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;
if ($this->pageparams->grouptype != attendance::SESSION_COMMON) {
@@ -916,12 +984,12 @@ class attendance {
global $DB, $CFG;
// 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)) {
- $orderby = "u.firstname ASC, u.lastname ASC";
+ $orderby = "u.firstname ASC, u.lastname ASC, u.idnumber ASC, u.institution ASC, u.department ASC";
} 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) {
@@ -1082,13 +1150,36 @@ class attendance {
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;
$params = array(
'aid' => $this->id,
'cstartdate' => $this->course->startdate,
'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 = '';
if (!empty($this->pageparams->startdate) && !empty($this->pageparams->enddate)) {
$period = ' AND ats.sessdate >= :sdate AND ats.sessdate < :edate ';
@@ -1096,38 +1187,47 @@ class attendance {
$params['edate'] = $this->pageparams->enddate;
}
- if (!array_key_exists($userid, $this->userstatusesstat)) {
- if ($this->get_group_mode()) {
- $qry = "SELECT al.statusid, count(al.statusid) AS stcnt
- FROM {attendance_log} al
- JOIN {attendance_sessions} ats ON al.sessionid = ats.id
- LEFT JOIN {groups_members} gm ON gm.userid = al.studentid AND gm.groupid = ats.groupid
- WHERE ats.attendanceid = :aid AND
- ats.sessdate >= :cstartdate AND
- al.studentid = :uid AND
- (ats.groupid = 0 or gm.id is NOT NULL)".$period."
- GROUP BY al.statusid";
- } else {
- $qry = "SELECT al.statusid, count(al.statusid) AS stcnt
- FROM {attendance_log} al
- JOIN {attendance_sessions} ats
- ON al.sessionid = ats.id
- WHERE ats.attendanceid = :aid AND
- ats.sessdate >= :cstartdate AND
- al.studentid = :uid".$period."
- GROUP BY al.statusid";
-
- }
-
-
+ if ($this->get_group_mode()) {
+ $qry = "SELECT al.statusid, count(al.statusid) AS stcnt
+ FROM {attendance_log} al
+ JOIN {attendance_sessions} ats ON al.sessionid = ats.id
+ LEFT JOIN {groups_members} gm ON gm.userid = al.studentid AND gm.groupid = ats.groupid
+ WHERE ats.attendanceid = :aid AND
+ ats.sessdate >= :cstartdate AND
+ al.studentid = :uid AND
+ (ats.groupid = 0 or gm.id is NOT NULL)".$period.$processed_filters."
+ GROUP BY al.statusid";
+ } else {
+ $qry = "SELECT al.statusid, count(al.statusid) AS stcnt
+ FROM {attendance_log} al
+ JOIN {attendance_sessions} ats
+ ON al.sessionid = ats.id
+ WHERE ats.attendanceid = :aid AND
+ ats.sessdate >= :cstartdate AND
+ al.studentid = :uid".$period.$processed_filters."
+ 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);
}
+ // Return the cached stats.
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.
@@ -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.
$id = $DB->sql_concat(':value', 'ats.id');
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
RIGHT JOIN {attendance_log} al
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)
ORDER BY ats.sessdate ASC";
} 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
RIGHT JOIN {attendance_log} al
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";
}
- $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
LEFT JOIN {attendance_log} al
ON ats.id = al.sessionid AND al.studentid = :uid
@@ -1295,7 +1395,7 @@ class attendance {
$sess->timemodified = $now;
$DB->update_record('attendance_sessions', $sess);
}
- $sessions->close();
+ $sessions->close();
add_to_log($this->course->id, 'attendance', 'sessions duration updated', $this->url_manage(),
get_string('sessionsids', 'attendance').implode(', ', $sessionsids), $this->cm->id);
}
@@ -1353,9 +1453,9 @@ class attendance {
add_to_log($this->course->id, 'attendance', 'status updated', $this->url_preferences(),
implode(' ', $updated), $this->cm->id);
}
+
}
-
function att_get_statuses($attid, $onlyvisible=true) {
global $DB;
diff --git a/renderables.php b/renderables.php
index 373bdc0..75c5324 100644
--- a/renderables.php
+++ b/renderables.php
@@ -109,19 +109,23 @@ class attendance_filter_controls implements renderable {
public $prevcur;
public $nextcur;
public $curdatetxt;
+ public $reportcontrol;
private $urlpath;
private $urlparams;
private $att;
- public function __construct(attendance $att) {
+ public function __construct(attendance $att, $report = false) {
global $PAGE;
$this->pageparams = $att->pageparams;
$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;
$date = usergetdate($att->pageparams->curdate);
@@ -456,6 +460,12 @@ class attendance_report_data implements renderable {
global $CFG;
$this->perm = $att->perm;
+
+ $currenttime = time();
+ if ($att->pageparams->view == ATT_VIEW_NOTPRESENT) {
+ $att->pageparams->enddate = $currenttime;
+ }
+
$this->pageparams = $att->pageparams;
$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;
}
- foreach ($this->users as $user) {
- $this->usersgroups[$user->id] = groups_get_all_groups($att->course->id, $user->id);
+ $maxgrade = att_get_user_max_grade(count($this->sessions), $this->statuses);
- $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->grades[$user->id] = $att->get_user_grade($user->id);
- $this->maxgrades[$user->id] = $att->get_user_max_grade($user->id);
+ $this->sessionslog[$user->id] = $att->get_user_filtered_sessions_log($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]);
}
}
diff --git a/renderer.php b/renderer.php
old mode 100755
new mode 100644
index f8ce347..9f43f0d
--- a/renderer.php
+++ b/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);
- $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;
}
- $numberofpages = ceil($totalusers / $usersperpage);
+ $numberofpages = ceil($totalusers / $fcontrols->pageparams->perpage);
if ($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) {
$views[ATT_VIEW_ALL] = get_string('all', '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_WEEKS] = get_string('weeks', 'attendance');
$views[ATT_VIEW_DAYS] = get_string('days', 'attendance');
@@ -734,8 +737,17 @@ class mod_attendance_renderer extends plugin_renderer_base {
$cell->colspan = 2;
$row->cells[] = $cell;
} else {
- $row->cells[] = '?';
- $row->cells[] = '';
+ if (!empty($sess->studentscanmark)) { // Student can mark their own attendance.
+ // 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;
@@ -751,6 +763,14 @@ class mod_attendance_renderer extends plugin_renderer_base {
}
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->attributes['class'] = 'generaltable attwidth';
@@ -792,13 +812,20 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table->align[] = 'center';
$table->size[] = '1px';
}
-
+
if ($reportdata->sessionslog) {
$table->head[] = get_string('remarks', 'attendance');
$table->align[] = 'center';
$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) {
$row = new html_table_row();
@@ -827,6 +854,11 @@ class mod_attendance_renderer extends plugin_renderer_base {
$row->cells[] = '';
}
}
+
+ if ($bulkmessagecapability) { // Create the checkbox for bulk messaging.
+ $row->cells[] = html_writer::checkbox('user'.$user->id, 'on', false);
+ }
+
$table->data[] = $row;
}
@@ -837,7 +869,9 @@ class mod_attendance_renderer extends plugin_renderer_base {
foreach ($reportdata->sessions as $sess) {
foreach ($reportdata->users as $user) {
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]++;
+ }
}
}
@@ -849,8 +883,23 @@ class mod_attendance_renderer extends plugin_renderer_base {
}
$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) {
diff --git a/report.php b/report.php
index 8a4d4cf..b9d37eb 100644
--- a/report.php
+++ b/report.php
@@ -57,7 +57,7 @@ $PAGE->navbar->add(get_string('report', 'attendance'));
$output = $PAGE->get_renderer('mod_attendance');
$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);
add_to_log($course->id, 'attendance', 'report viewed', '/mod/attendance/report.php?id='.$id, '', $cm->id);
diff --git a/sessions.php b/sessions.php
index b03b3df..fda84fe 100644
--- a/sessions.php
+++ b/sessions.php
@@ -220,6 +220,9 @@ function construct_sessions_data_for_add($formdata) {
$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;
+ }
fill_groupid($formdata, $sessions, $sess);
}
@@ -237,6 +240,9 @@ function construct_sessions_data_for_add($formdata) {
$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;
+ }
fill_groupid($formdata, $sessions, $sess);
}
diff --git a/student_attenance_form.php b/student_attenance_form.php
new file mode 100644
index 0000000..5f7d7d7
--- /dev/null
+++ b/student_attenance_form.php
@@ -0,0 +1,63 @@
+.
+
+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();
+ }
+}
\ No newline at end of file
diff --git a/tests/behat/attendance_mod.feature b/tests/behat/attendance_mod.feature
new file mode 100644
index 0000000..34c04c9
--- /dev/null
+++ b/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
+
diff --git a/tests/behat/behat_mod_attendance.php b/tests/behat/behat_mod_attendance.php
new file mode 100644
index 0000000..cbcd3b0
--- /dev/null
+++ b/tests/behat/behat_mod_attendance.php
@@ -0,0 +1,116 @@
+.
+
+
+// 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();
+
+ }
+ }
+ }
+ }
+
+}