uon merge conflict fixes
authorJoseph Baxter <joseph.baxter@nottingham.ac.uk>
Mon, 21 Jul 2014 09:06:46 +0000 (10:06 +0100)
committerJoseph Baxter <joseph.baxter@nottingham.ac.uk>
Mon, 21 Jul 2014 09:06:46 +0000 (10:06 +0100)
18 files changed:
add_form.php
attendance.php [new file with mode: 0644]
backup/moodle2/backup_attendance_activity_task.class.php
backup/moodle2/restore_attendance_activity_task.class.php
db/install.xml
db/upgrade.php
export.php
export_form.php
lang/en/attendance.php [changed mode: 0755->0644]
locallib.php
renderables.php
renderer.php [changed mode: 0755->0644]
report.php
sessions.php
student_attenance_form.php [new file with mode: 0644]
tests/behat/attendance_mod.feature [new file with mode: 0644]
tests/behat/behat_mod_attendance.php [new file with mode: 0644]
version.php

index 9fc813a..a9550a3 100644 (file)
@@ -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 (file)
index 0000000..539354c
--- /dev/null
@@ -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();
index 87b5c89..df3b4a0 100644 (file)
@@ -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;
     }
index 5ca7794..629cecd 100644 (file)
@@ -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;
index 1bc5a05..ca4e6b0 100644 (file)
@@ -30,6 +30,7 @@
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" 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="studentscanmark" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for attendance_sessions"/>
index c00926f..579f80f 100644 (file)
@@ -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}
index a0a690a..9b6fb99 100644 (file)
@@ -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)) {
index 994c5e1..766e863 100644 (file)
@@ -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('<br />'), true);
         $mform->setDefaults(array('ident[id]' => true, 'ident[uname]' => true));
         $mform->setType('id', PARAM_INT);
old mode 100755 (executable)
new mode 100644 (file)
index f9cf66b..16fbca1
@@ -225,3 +225,11 @@ $string['eventsessiondeleted'] = 'Session deleted';
 $string['eventdurationupdated'] = 'Session duration updated';
 $string['eventstatusupdated'] = 'Status updated';
 $string['eventstatusadded'] = 'Status added';
+
+$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';
index 53988ef..6c787ff 100644 (file)
@@ -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";
         }
@@ -846,6 +849,68 @@ class attendance {
         $event->trigger();
     }
 
+    /**
+     * 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);
+               
+        // Log the change.
+        $event = \mod_attendance\event\attendance_taken::create(array(
+            'objectid' => $this->id,
+            'context' => $this->context,
+            'other' => $params));
+        $event->add_record_snapshot('course_modules', $this->cm);
+        $event->add_record_snapshot('attendance', $this);
+        $event->trigger();
+
+        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.
@@ -893,6 +958,7 @@ 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);
@@ -934,12 +1000,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) {
@@ -1100,13 +1166,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 ';
@@ -1114,38 +1203,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.
@@ -1233,7 +1331,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
@@ -1241,7 +1339,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
@@ -1271,7 +1369,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
@@ -1391,9 +1489,9 @@ class attendance {
         $event->add_record_snapshot('attendance', $this);
         $event->trigger();
     }
+    
 }
 
-
 function att_get_statuses($attid, $onlyvisible=true) {
     global $DB;
 
index 373bdc0..75c5324 100644 (file)
@@ -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]);
             }
         }
 
old mode 100755 (executable)
new mode 100644 (file)
index f8ce347..9f43f0d
@@ -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) {
index e556336..be09bd4 100644 (file)
@@ -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);
 
 // Trigger a report viewed event.
index b03b3df..fda84fe 100644 (file)
@@ -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 (file)
index 0000000..5f7d7d7
--- /dev/null
@@ -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();
+    }
+}
\ No newline at end of file
diff --git a/tests/behat/attendance_mod.feature b/tests/behat/attendance_mod.feature
new file mode 100644 (file)
index 0000000..34c04c9
--- /dev/null
@@ -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 (file)
index 0000000..cbcd3b0
--- /dev/null
@@ -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();
+
+                }
+            }
+        }
+    }
+    
+}
index 1ac71de..2865f09 100644 (file)
@@ -22,7 +22,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$plugin->version  = 2014050900;
+$plugin->version  = 2014072100;
 $plugin->requires = 2014042900;
 $plugin->release = '2.7.0';
 $plugin->maturity  = MATURITY_STABLE;