UoN Changes to allow students to mark their own attendance on sessions that have...
authorNeill Magill <neill.magill@nottingham.ac.uk>
Thu, 29 Aug 2013 13:05:07 +0000 (14:05 +0100)
committerJoseph Baxter <joseph.baxter@nottingham.ac.uk>
Tue, 3 Jun 2014 13:31:13 +0000 (14:31 +0100)
Adds a Report that lists only students who do not have full attendance.

Adds the Ability to send e-mails to students.

add_form.php
attendance.php [new file with mode: 0644]
db/install.xml
db/upgrade.php
lang/en/attendance.php
locallib.php
renderables.php
renderer.php [changed mode: 0755->0644]
report.php
sessions.php
student_attenance_form.php [new file with mode: 0644]

index 9fc813a..53dc051 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');
 
+        // Studetns can mark own attendance.
+        $mform->addElement('checkbox', 'studentscanmark', '', get_string('studentscanmark','attforblock'));
+        $mform->addHelpButton('studentscanmark', 'studentscanmark', 'attforblock');
+
         $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..9f5699b
--- /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 attforblock
+ * @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);
+$attforblock = $DB->get_record('attforblock', array('id' => $attforsession->attendanceid), '*', MUST_EXIST);
+$cm = get_coursemodule_from_instance('attforblock', $attforblock->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 attforblock($attforblock, $cm, $course, $PAGE->context, $pageparams);
+
+// Require that a session key is passed to this page.
+require_sesskey();
+
+// Create the form.
+$mform = new mod_attforblock_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/attforblock/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/attforblock/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_attforblock', $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_attforblock');
+echo $output->header();
+$mform->display();
+echo $output->footer();
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..b38d71f 100644 (file)
@@ -32,7 +32,17 @@ function xmldb_attendance_upgrade($oldversion=0) {
     global $CFG, $THEME, $DB;
     $dbman = $DB->get_manager(); // Loads ddl manager and xmldb classes.
 
-    $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(true, 2013082901, 'attendance');
+    }
 
     if ($oldversion < 2013082902) {
         // Replace values that reference old module "attforblock" to "attendance".
index 4c8df1f..ee35575 100755 (executable)
@@ -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';
index ace0f42..d5d2d79 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";
         }
@@ -833,6 +836,57 @@ 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_attforblock');
+        $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));
+
+        // Log the change.
+        $params = array(
+                'sessionid' => $mformdata->sessid);
+        $url = $this->url_take($params);
+        $this->log('attendance taked', $url, $USER->firstname.' '.$USER->lastname);
+
+        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.
@@ -1082,7 +1136,14 @@ 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,
@@ -1120,14 +1181,42 @@ class attendance {
             }
 
 
+        // Make the filter array into a SQL string.
+        if (!empty($processed_filters)) {
+            $processed_filters = 'AND '.implode(' AND ', $processed_filters);
+        } else {
+            $processed_filters = '';
+        }
+
+        $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
+                         $processed_filters
+                GROUP BY al.statusid";
+
+        if ($filters !== null) { // We do not want to cache, or use a cached version of the results when a filter is set.
+            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.
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..7ec3216
@@ -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', 'attforblock');
+        }
         $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/attforblock/attendance.php',
+                            array('sessid' => $sess->id, 'sesskey' => sesskey()));
+                    $cell = new html_table_cell(html_writer::link($url, get_string('submitattendance', 'attforblock')));
+                    $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_attforblock.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,6 +812,14 @@ class mod_attendance_renderer extends plugin_renderer_base {
             $table->align[] = 'center';
             $table->size[] = '1px';
         }
+
+        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';
+        }
+
         
         if ($reportdata->sessionslog) {
             $table->head[] = get_string('remarks', 'attendance');
@@ -820,6 +848,28 @@ class mod_attendance_renderer extends plugin_renderer_base {
                 $row->cells[] = $reportdata->grades[$user->id].' / '.$reportdata->maxgrades[$user->id];
             }
 
+            if ($bulkmessagecapability) { // Create the checkbox for bulk messaging.
+                $row->cells[] = html_writer::checkbox('user'.$user->id, 'on', false);
+            }
+
+            $table->data[] = $row;
+        }
+
+        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);
+            $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);
+        }
             if ($reportdata->sessionslog) {
                 if (isset($sess) && isset($reportdata->sessionslog[$user->id][$sess->id]->remarks)) {
                     $row->cells[] = $reportdata->sessionslog[$user->id][$sess->id]->remarks;
index 8a4d4cf..a0503f5 100644 (file)
@@ -58,7 +58,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);
-$reportdata = new attendance_report_data($att);
+$reportdata = new attendance_report_data($att, true);
 
 add_to_log($course->id, 'attendance', 'report viewed', '/mod/attendance/report.php?id='.$id, '', $cm->id);
 
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..7089b39
--- /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_attforblock_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_attforblock'));
+
+        $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', 'attforblock'), 'required', '', 'client', false, false);
+
+        $this->add_action_buttons();
+    }
+}
\ No newline at end of file