From 04a4a8f1e1c2af04a3b272e6a4b09b57e93a231d Mon Sep 17 00:00:00 2001 From: Nick Phillips Date: Wed, 24 Jul 2019 10:07:16 +1200 Subject: [PATCH] Make update from student's 'allsessions' attendance report work. --- locallib.php | 50 +++++++++++++++- renderables.php | 141 +++++++++++++++++++++++++++++++++++++++++-- renderer.php | 156 ++++++++++++++++++++++++++++++++++++++++++++---- view.php | 24 +++++--- 4 files changed, 347 insertions(+), 24 deletions(-) diff --git a/locallib.php b/locallib.php index cba6927..ff8bc1e 100644 --- a/locallib.php +++ b/locallib.php @@ -115,7 +115,7 @@ function attendance_get_setname($attid, $statusset, $includevalues = true) { * @param stdClass $pageparams * @return array */ -function get_user_sessions_log_full($userid, $pageparams) { +function attendance_get_user_sessions_log_full($userid, $pageparams) { global $DB; // All taken sessions (including previous groups). @@ -364,6 +364,54 @@ function attendance_update_users_grade($attendance, $userids=array()) { return grade_update('mod/attendance', $course->id, 'mod', 'attendance', $attendance->id, 0, $grades); } +/** + * Update grades for specified users for specified attendance + * + * @param integer $attendanceid - the id of the attendance to update + * @param integer $grade - the value of the 'grade' property of the specified attendance + * @param array $userids - the userids of the users to be updated + */ +function attendance_update_users_grades_by_id($attendanceid, $grade, $userids) { + global $DB; + + if (empty($grade)) { + return false; + } + + list($course, $cm) = get_course_and_cm_from_instance($attendanceid, 'attendance'); + + $summary = new mod_attendance_summary($attendanceid, $userids); + + if (empty($userids)) { + $context = context_module::instance($cm->id); + $userids = array_keys(get_enrolled_users($context, 'mod/attendance:canbelisted', 0, 'u.id')); + } + + if ($grade < 0) { + $dbparams = array('id' => -($grade)); + $scale = $DB->get_record('scale', $dbparams); + $scalearray = explode(',', $scale->scale); + $attendancegrade = count($scalearray); + } else { + $attendancegrade = $grade; + } + + $grades = array(); + foreach ($userids as $userid) { + $grades[$userid] = new stdClass(); + $grades[$userid]->userid = $userid; + + if ($summary->has_taken_sessions($userid)) { + $usersummary = $summary->get_taken_sessions_summary_for($userid); + $grades[$userid]->rawgrade = $usersummary->takensessionspercentage * $attendancegrade; + } else { + $grades[$userid]->rawgrade = null; + } + } + + return grade_update('mod/attendance', $course->id, 'mod', 'attendance', $attendanceid, 0, $grades); +} + /** * Add an attendance status variable * diff --git a/renderables.php b/renderables.php index 1271006..90ce3ce 100644 --- a/renderables.php +++ b/renderables.php @@ -516,7 +516,7 @@ class attendance_user_data implements renderable { $this->filtercontrols = new attendance_filter_controls($att); } - $this->sessionslog = get_user_sessions_log_full($userid, $this->pageparams); + $this->sessionslog = attendance_get_user_sessions_log_full($userid, $this->pageparams); foreach ($this->sessionslog as $sessid => $sess) { $this->sessionslog[$sessid]->cmid = $this->coursesatts[$sess->attendanceid]->cmid; @@ -546,12 +546,145 @@ class attendance_user_data implements renderable { } /** - * url helper. + * Url function + * @param array $params + * @param array $excludeparams * @return moodle_url */ - public function url() { - return new moodle_url($this->urlpath, $this->urlparams); + public function url($params=array(), $excludeparams=array()) { + $params = array_merge($this->urlparams, $params); + + foreach ($excludeparams as $paramkey) { + unset($params[$paramkey]); + } + + return new moodle_url($this->urlpath, $params); + } + + /** + * Take multiple sessions attendance from form data. + * + * @param stdClass $formdata + */ + public function take_sessions_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. + // This whole function could do with a nice clean up. + + // XXX - replace this, see below + //$statuses = implode(',', array_keys( (array)$this->get_statuses() )); + $now = time(); + $sesslog = array(); + $formdata = (array)$formdata; + $dbstudlogs = array(); + $updatedsessions = array(); + $sessionatt = array(); + + foreach ($formdata as $key => $value) { + // Look at Remarks field because the user options may not be passed if empty. + if (substr($key, 0, 7) == 'remarks') { + $formlog = array(); + $parts = explode('sess', substr($key, 7)); + $stid = $parts[0]; + if (!(is_numeric($stid))) { // Sanity check on $stid. + print_error('nonnumericid', 'attendance'); + } + $sessid = $parts[1]; + if (!(is_numeric($sessid))) { // Sanity check on $sessid. + print_error('nonnumericid', 'attendance'); + } + $dbsession = $this->sessionslog[$sessid]; + + $context = context_module::instance($dbsession->cmid); + if (!has_capability('mod/attendance:takeattendances', $context)) { + // How do we tell user about this? + error_log("User without capability tried to take attendance for context, cmid:", $dbsession->cmid); + continue; + } + + $formkey = 'user'.$stid.'sess'.$sessid; + $attid = $dbsession->attendanceid; + $statusset = array_filter($this->statuses[$attid], function($x) use($dbsession) { return $x->setnumber === $dbsession->statusset; }); + $sessionatt[$sessid] = $attid; + $formlog = new stdClass(); + if (array_key_exists($formkey, $formdata) && is_numeric($formdata[$formkey])) { + $formlog->statusid = $formdata[$formkey]; + } + $formlog->studentid = $stid; // We check is_numeric on this above. + $formlog->statusset = implode(',', array_keys($statusset)); + $formlog->remarks = $value; + $formlog->sessionid = $sessid; + $formlog->timetaken = $now; + $formlog->takenby = $USER->id; + + if (!array_key_exists($stid, $sesslog)) { + $sesslog[$stid] = array(); + } + $sesslog[$stid][$sessid] = $formlog; + } + } + + $updateatts = array(); + foreach ($sesslog as $stid => $userlog) { + $dbstudlog = $DB->get_records('attendance_log', array('studentid' => $stid), '', 'sessionid,statusid,remarks,id,statusset'); + foreach($userlog as $log) { + if (array_key_exists($log->sessionid, $dbstudlog)) { + $attid = $sessionatt[$log->sessionid]; + // Check if anything important has changed before updating record. + // Don't update timetaken/takenby records if nothing has changed. + if ($dbstudlog[$log->sessionid]->remarks != $log->remarks || + $dbstudlog[$log->sessionid]->statusid != $log->statusid || + $dbstudlog[$log->sessionid]->statusset != $log->statusset) { + + $log->id = $dbstudlog[$log->sessionid]->id; + $DB->update_record('attendance_log', $log); + + $updatedsessions[$log->sessionid] = $log->sessionid; + if (!array_key_exists($attid, $updateatts)) { + $updateatts[$attid] = array(); + } + array_push($updateatts[$attid], $log->studentid); + } + } else { + $DB->insert_record('attendance_log', $log, false); + $updatedsessions[$log->sessionid] = $log->sessionid; + if (!array_key_exists($attid, $updateatts)) { + $updateatts[$attid] = array(); + } + array_push($updateatts[$attid], $log->studentid); + } + } + } + + foreach ($updatedsessions as $sessionid) { + $session = $this->sessionslog[$sessionid]; + $session->lasttaken = $now; + $session->lasttakenby = $USER->id; + $DB->update_record('attendance_sessions', $session); + } + + if (!empty($updateatts)) { + $attendancegrade = $DB->get_records_list('attendance', 'id', array_keys($updateatts), '', 'id, grade'); + foreach($updateatts as $attid => $updateusers) { + if ($attendancegrade[$attid] != 0) { + attendance_update_users_grades_by_id($attid, $grade, $updateusers); + } + } + } + + // Create url for link in log screen. + $params = $this->pageparams->get_significant_params(); + // XXX - TODO + // Waiting for event for viewing user report(s) before creating derived event for editing. + /* $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_sessions', $session); */ + /* $event->trigger(); */ } + } /** diff --git a/renderer.php b/renderer.php index 4f3fda7..8f365dd 100644 --- a/renderer.php +++ b/renderer.php @@ -1000,6 +1000,71 @@ class mod_attendance_renderer extends plugin_renderer_base { return $celldata; } + /** + * Construct take session controls. + * + * @param attendance_take_data $takedata + * @param stdClass $user + * @return array + */ + private function construct_take_session_controls(attendance_take_data $takedata, $user) { + $celldata = array(); + if ($user->enrolmentend and $user->enrolmentend < $takedata->sessioninfo->sessdate) { + $celldata['text'] = get_string('enrolmentend', 'attendance', userdate($user->enrolmentend, '%d.%m.%Y')); + $celldata['colspan'] = count($takedata->statuses) + 1; + $celldata['class'] = 'userwithoutenrol'; + } else if (!$user->enrolmentend and $user->enrolmentstatus == ENROL_USER_SUSPENDED) { + // No enrolmentend and ENROL_USER_SUSPENDED. + $celldata['text'] = get_string('enrolmentsuspended', 'attendance'); + $celldata['colspan'] = count($takedata->statuses) + 1; + $celldata['class'] = 'userwithoutenrol'; + } else { + if ($takedata->updatemode and !array_key_exists($user->id, $takedata->sessionlog)) { + $celldata['class'] = 'userwithoutdata'; + } + + $celldata['text'] = array(); + foreach ($takedata->statuses as $st) { + $params = array( + 'type' => 'radio', + 'name' => 'user'.$user->id.'sess'.$takedata->sessioninfo->id, + 'class' => 'st'.$st->id, + 'value' => $st->id); + if (array_key_exists($user->id, $takedata->sessionlog) and $st->id == $takedata->sessionlog[$user->id]->statusid) { + $params['checked'] = ''; + } + + $input = html_writer::empty_tag('input', $params); + + if ($takedata->pageparams->viewmode == mod_attendance_take_page_params::SORTED_GRID) { + $input = html_writer::tag('nobr', $input . $st->acronym); + } + + $celldata['text'][] = $input; + } + $params = array( + 'type' => 'text', + 'name' => 'remarks'.$user->id.'sess'.$takedata->sessioninfo->id, + 'maxlength' => 255); + if (array_key_exists($user->id, $takedata->sessionlog)) { + $params['value'] = $takedata->sessionlog[$user->id]->remarks; + } + $input = html_writer::empty_tag('input', $params); + if ($takedata->pageparams->viewmode == mod_attendance_take_page_params::SORTED_GRID) { + $input = html_writer::empty_tag('br').$input; + } + $celldata['text'][] = $input; + + if ($user->enrolmentstart > $takedata->sessioninfo->sessdate + $takedata->sessioninfo->duration) { + $celldata['warning'] = get_string('enrolmentstart', 'attendance', + userdate($user->enrolmentstart, '%H:%M %d.%m.%Y')); + $celldata['class'] = 'userwithoutenrol'; + } + } + + return $celldata; + } + /** * Render header. * @@ -1453,15 +1518,6 @@ class mod_attendance_renderer extends plugin_renderer_base { $summarywidth++; } - /* if (has_capability('mod/attendance:takeattendances', $context)) { */ - /* $table->head[] = get_string('action'); */ - /* $table->align[] = ''; */ - /* $table->colclasses[] = 'actioncol'; */ - /* $table->size[] = ''; */ - /* $colcount++; */ - /* $summarywidth++; */ - /* } */ - $statusmaxpoints = array(); foreach ($userdata->statuses as $attid => $attstatuses) { $statusmaxpoints[$attid] = attendance_get_statusset_maxpoints($attstatuses); @@ -1863,7 +1919,61 @@ class mod_attendance_renderer extends plugin_renderer_base { } if (!empty($USER->editing)) { - // TODO: add ability to edit attendance here + $context = context_module::instance($sess->cmid); + if (has_capability('mod/attendance:takeattendances', $context)) { + // TODO: add ability to edit attendance here + $celltext = ''; + + // takedata needs: + // sessioninfo->sessdate + // sessioninfo->duration + // statuses + // updatemode + // sessionlog[userid]->statusid + // sessionlog[userid]->remarks + // pageparams->viewmode == mod_attendance_take_page_params::SORTED_GRID + // and urlparams to be able to use url method later. + // + // user needs: + // enrolmentstart + // enrolmentend + // enrolmentstatus + // id + + $nastyhack = new ReflectionClass('attendance_take_data'); + $takedata = $nastyhack->newInstanceWithoutConstructor(); + $takedata->sessioninfo = $sess; + $takedata->statuses = array_filter($userdata->statuses[$sess->attendanceid], function($x) use ($sess) { + return ($x->setnumber == $sess->statusset); + }); + $takedata->updatemode = true; + $takedata->sessionlog = array($userdata->user->id => $sess); + $takedata->pageparams = new stdClass(); + $takedata->pageparams->viewmode = mod_attendance_take_page_params::SORTED_GRID; + $ucdata = $this->construct_take_session_controls($takedata, $userdata->user); + + $celltext = join($ucdata['text']); + + if (array_key_exists('warning', $ucdata)) { + $celltext .= html_writer::empty_tag('br'); + $celltext .= $ucdata['warning']; + } + if (array_key_exists('class', $ucdata)) { + $row->attributes['class'] = $ucdata['class']; + } + + $cell = new html_table_cell($celltext); + $cell->colspan = 2; + $row->cells[] = $cell; + } + else { + if (!empty($sess->statusid)) { + $status = $userdata->statuses[$sess->attendanceid][$sess->statusid]; + $row->cells[] = $status->description; + $row->cells[] = $sess->remarks; + } + } + } else { if (!empty($sess->statusid)) { $status = $userdata->statuses[$sess->attendanceid][$sess->statusid]; @@ -1904,7 +2014,31 @@ class mod_attendance_renderer extends plugin_renderer_base { } } - $allsessions->detail = html_writer::table($table); + if (!empty($USER->editing)) { + $row = new html_table_row(); + $params = array( + 'type' => 'submit', + 'class' => 'btn btn-primary', + 'value' => get_string('save', 'attendance')); + $cell = new html_table_cell(html_writer::tag('center', html_writer::empty_tag('input', $params))); + $cell->colspan = $colcount + (($groupby == 'activity')? 2 : 1); + $row->cells[] = $cell; + $table->data[] = $row; + } + + $logtext = html_writer::table($table); + + if (!empty($USER->editing)) { + $formtext = html_writer::start_div('no-overflow'); + $formtext .= $logtext; + $formtext .= html_writer::input_hidden_params($userdata->url(array('sesskey' => sesskey()))); + $formtext .= html_writer::end_div(); + // could use userdata->urlpath if not private or userdata->url_path() if existed, but '' turns + // out to DTRT. + $logtext = html_writer::tag('form', $formtext, array('method' => 'post', 'action' => '', + 'id' => 'attendancetakeform')); + } + $allsessions->detail = $logtext; return $allsessions; } diff --git a/view.php b/view.php index 748c3ed..40aafbf 100644 --- a/view.php +++ b/view.php @@ -62,14 +62,6 @@ if (!$pageparams->studentid) { } } -$PAGE->set_url($att->url_view($pageparams->get_significant_params())); -$PAGE->set_title($course->shortname. ": ".$att->name); -$PAGE->set_heading($course->fullname); -$PAGE->set_cacheable(true); -$PAGE->navbar->add(get_string('attendancereport', 'attendance')); - -$output = $PAGE->get_renderer('mod_attendance'); - if (isset($pageparams->studentid) && $USER->id != $pageparams->studentid) { // Only users with proper permissions should be able to see any user's individual report. require_capability('mod/attendance:viewreports', $context); @@ -79,6 +71,9 @@ if (isset($pageparams->studentid) && $USER->id != $pageparams->studentid) { $userid = $USER->id; } +$url = $att->url_view($pageparams->get_significant_params()); +$PAGE->set_url($url); + $userdata = new attendance_user_data($att, $userid); // Create url for link in log screen. @@ -105,6 +100,19 @@ $event->trigger(); $header = new mod_attendance_header($att); +if (($formdata = data_submitted()) && confirm_sesskey()) { + $userdata->take_sessions_from_form_data($formdata); + + redirect($url, get_string('attendancesuccess', 'attendance')); +} + +$PAGE->set_title($course->shortname. ": ".$att->name); +$PAGE->set_heading($course->fullname); +$PAGE->set_cacheable(true); +$PAGE->navbar->add(get_string('attendancereport', 'attendance')); + +$output = $PAGE->get_renderer('mod_attendance'); + echo $output->header(); echo $output->render($header);