diff --git a/add_form.php b/add_form.php index dabaeb1..c92893e 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'); + // 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 index 0000000..9f5699b --- /dev/null +++ b/attendance.php @@ -0,0 +1,84 @@ +. + +/** + * 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(); 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 0e9336d..2642874 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -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'); + } // UPGRADES from attforblock are only supported for sites that are running attforblock version 2012120700. return $result; diff --git a/lang/en/attendance.php b/lang/en/attendance.php index 5dd803c..8138358 100755 --- a/lang/en/attendance.php +++ b/lang/en/attendance.php @@ -208,3 +208,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 1975b0b..7b62f20 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); @@ -702,6 +703,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"; } @@ -834,6 +837,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. @@ -1016,31 +1070,65 @@ 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; - if (!array_key_exists($userid, $this->userstatusesstat)) { - $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 - GROUP BY al.statusid"; - $params = array( - 'aid' => $this->id, - 'cstartdate' => $this->course->startdate, - 'uid' => $userid); + // Need to start setting the parameters here for the filters to work. + $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 = ''; + } + + $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. @@ -1115,13 +1203,13 @@ class attendance { $where2 = "ats.attendanceid = :aid2 AND ats.sessdate >= :csdate2 AND ats.groupid $gsql"; } - $sql = "SELECT ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks + $sql = "SELECT 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 WHERE $where UNION - SELECT ats.id, ats.groupid, ats.sessdate, ats.duration, ats.description, al.statusid, al.remarks + SELECT 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 = :uid2 diff --git a/renderables.php b/renderables.php index 4234bcd..d62ede4 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); @@ -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 index 2b7dae2..6c36945 100644 --- a/renderer.php +++ b/renderer.php @@ -140,6 +140,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'); @@ -644,8 +647,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; @@ -661,6 +673,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'; @@ -701,6 +721,13 @@ class mod_attendance_renderer extends plugin_renderer_base { $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'; + } + foreach ($reportdata->users as $user) { $row = new html_table_row(); @@ -722,10 +749,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; } - return html_writer::table($table); + 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); + } } protected function render_attendance_preferences_data(attendance_preferences_data $prefdata) { diff --git a/report.php b/report.php index 032c4fd..c928ec9 100644 --- a/report.php +++ b/report.php @@ -56,7 +56,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); diff --git a/sessions.php b/sessions.php index 046a596..ea44026 100644 --- a/sessions.php +++ b/sessions.php @@ -216,6 +216,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); } @@ -233,6 +236,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..7089b39 --- /dev/null +++ b/student_attenance_form.php @@ -0,0 +1,63 @@ +. + +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 diff --git a/version.php b/version.php index e7ab8e3..d36b123 100644 --- a/version.php +++ b/version.php @@ -22,7 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$module->version = 2013082900; +$module->version = 2013082901; $module->requires = 2013040500; $module->release = '2.5.1'; $module->maturity = MATURITY_STABLE; diff --git a/view.php b/view.php index 5117605..19e288d 100644 --- a/view.php +++ b/view.php @@ -63,7 +63,7 @@ $PAGE->navbar->add(get_string('attendancereport', 'attendance')); $output = $PAGE->get_renderer('mod_attendance'); -$userid = isset($pageparams->studentid) ? $pageparams->studentid : $USER->id; +$userid = (isset($pageparams->studentid) && ($att->perm->can_manage() || $att->perm->can_take() || $att->perm->can_change())) ? $pageparams->studentid : $USER->id; $userdata = new attendance_user_data($att, $userid); echo $output->header();