Compare commits

...

37 Commits

Author SHA1 Message Date
Dan Marsden 9d52b9816b
Merge pull request #562 from wiktorwandachowicz/MOODLE_37_536-dst 3 years ago
Wiktor Wandachowicz e3afc85fb3 Correct timestamps in database for DST days 3 years ago
Dan Marsden cc5cae8186 Fix #519 - wrap text in first column. 4 years ago
Dan Marsden 3820023bbb Use api function for limit. 4 years ago
Dan Marsden 85362c9a40 Fix #460 - add default handling for qrcode rotation 4 years ago
Dan Marsden 9a524a6143 If QRpass used and auto-marking just do it. 4 years ago
Dan Marsden c730f9f701 require login before pass check. 4 years ago
Alex Morris 0ec73f6c78 Add group to externallib test (#492) 4 years ago
Dan Marsden e027c1f6cd Prevent langauge translation issues with acronym. 4 years ago
Dan Marsden bad54336ce Update travis config 4 years ago
Dan Marsden 9504e86394 Bump version for plugins db release. 4 years ago
Dan Marsden 10e5506155 Fix location of qrcode to cover full lib. 4 years ago
Dan Marsden 0e1008e762 Fix some phpdoc references. 4 years ago
Dan Marsden 5bb6773e07 Fix #453 - add Moodle community of enquiry indicators for analytics. 4 years ago
kristian-94 26a71efe90
Add caleventid index to attendance_sessions table (#485) 4 years ago
Dan Marsden 14773cb85a Fix #467 use of core deprecated strings. 5 years ago
Dan Marsden f51d21119a Fix #466 - sanity check sort var before using. 5 years ago
Dan Marsden 4c75a2c699 Fix #455 - Fix some hard-coded language strings. 5 years ago
Dan Marsden 9d4a0ba7a6 Fix #445 - use attendance specific strings. 5 years ago
Dan Marsden e8c56d77dc
bump version for plugins db release. 5 years ago
Nadav Kavalerchik 555ca3cda9 Swap hour:min input fields in a new session form, when in RTL (#434) 5 years ago
Ruslan Kabalin 622fd3bfcb Fix externallib (#431) 5 years ago
Dan Marsden 3f68f35f0f Don't use param_raw. 5 years ago
Dan Marsden 295d9f3292 Change to using single_select API. 5 years ago
Dan Marsden e167d10716 Fix some moodle coding guideline warnings. 5 years ago
Dan Marsden 5369a5ed86 Issue #405 Make status selector span multiple columns. 5 years ago
M 452f08ecd2 Issue #405 Convert shift click functionality to drop down menu. 5 years ago
Nick Phillips 6e8629e3dc Fix #405 New feature to allow better control of "set all" status values. 6 years ago
Dan Marsden f4fc5cd1a6 Fix #422 - Only ask for fields once. 5 years ago
Dan Marsden 9289c899a6 Fix #427 - correct some bad sql. 5 years ago
Dan Marsden a03e7c764a Force travis to use jdk 8 5 years ago
Dan Marsden d525b45176 Fix #421 - include filelib.php in structure class. 5 years ago
Dan Marsden fa55c41710 Add mysql service for travis. 5 years ago
Dan Marsden 6d2af13983 Set relateduserid correctly in event. 5 years ago
Nick Phillips 6cb524c463 Fix #411 - Add event for viewing of per-student reports, and use in view.php. 5 years ago
Dan Marsden fe86264471 Use correct branch in travis. 5 years ago
Dan Marsden b9d7d159cd Set require Moodle 3.7 and bump version higher than 3.6 branch. 5 years ago
  1. 25
      .travis.yml
  2. 6
      absentee.php
  3. 5
      add_form.php
  4. 30
      attendance.php
  5. 2
      backup/moodle2/backup_attendance_activity_task.class.php
  6. 2
      backup/moodle2/backup_attendance_stepslib.php
  7. 8
      backup/moodle2/restore_attendance_activity_task.class.php
  8. 48
      classes/analytics/indicator/activity_base.php
  9. 69
      classes/analytics/indicator/cognitive_depth.php
  10. 69
      classes/analytics/indicator/social_breadth.php
  11. 136
      classes/event/session_report_viewed.php
  12. 12
      classes/import/sessions.php
  13. 8
      classes/structure.php
  14. 8
      coursesummary.php
  15. 4
      db/install.php
  16. 12
      db/upgrade.php
  17. 4
      export.php
  18. 40
      externallib.php
  19. 19
      lang/en/attendance.php
  20. 35
      locallib.php
  21. 4
      messageselect.php
  22. 2
      password.php
  23. 2
      password_ajax.php
  24. 114
      renderer.php
  25. 3
      settings.php
  26. 16
      templates/mobile_view_page.mustache
  27. 18
      tests/behat/extra_features.feature
  28. 18
      tests/externallib_test.php
  29. 2
      tests/generator/lib.php
  30. 2
      thirdpartylibs.xml
  31. 6
      version.php
  32. 23
      view.php

25
.travis.yml

@ -1,13 +1,13 @@
language: php
sudo: true
addons:
firefox: "47.0.1"
postgresql: "9.4"
apt:
packages:
- openjdk-8-jre-headless
postgresql: "9.5"
services:
- mysql
- postgresql
- docker
cache:
directories:
@ -19,7 +19,7 @@ php:
env:
global:
- MOODLE_BRANCH=master
- MOODLE_BRANCH=MOODLE_37_STABLE
- MUSTACHE_IGNORE_NAMES=mobile_teacher_form.mustache
matrix:
- DB=pgsql
@ -27,14 +27,15 @@ env:
before_install:
- phpenv config-rm xdebug.ini
- nvm install 8.9
- nvm use 8.9
- nvm install 14.0.0
- nvm use 14.0.0
- cd ../..
- composer create-project -n --no-dev --prefer-dist blackboard-open-source/moodle-plugin-ci ci ^2
- composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
- export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH"
install:
- moodle-plugin-ci install
- docker run -d -p 127.0.0.1:4444:4444 --net=host --shm-size=2g -v $HOME/build/moodle:$HOME/build/moodle selenium/standalone-chrome:3
script:
- moodle-plugin-ci phplint
@ -47,4 +48,4 @@ script:
- moodle-plugin-ci grunt
- moodle-plugin-ci phpdoc
- moodle-plugin-ci phpunit
- moodle-plugin-ci behat
- moodle-plugin-ci behat --profile chrome

6
absentee.php

@ -101,6 +101,12 @@ $table->setup();
$sortcolumns = $table->get_sort_columns();
// Now do sorting if specified.
// Sanity check $sort var before including in sql. Make sure it matches a known column.
$allowedsort = array_diff(array_keys($table->columns), $table->column_nosort);
if (!in_array($sort, $allowedsort)) {
$sort = '';
}
$orderby = ' ORDER BY percent ASC';
if (!empty($sort)) {
$direction = ' DESC';

5
add_form.php

@ -247,9 +247,8 @@ class mod_attendance_add_form extends moodleform {
if (isset($pluginconfig->includeqrcode_default)) {
$mform->setDefault('includeqrcode', $pluginconfig->includeqrcode_default);
}
// TODO - Change in DB and provide value
if (isset($pluginconfig->includeqrcode_default)) {
$mform->setDefault('rotateqrcode', $pluginconfig->includeqrcode_default);
if (isset($pluginconfig->rotateqrcode_default)) {
$mform->setDefault('rotateqrcode', $pluginconfig->rotateqrcode_default);
}
if (isset($pluginconfig->automark_default)) {
$mform->setDefault('automark', $pluginconfig->automark_default);

30
attendance.php

@ -38,26 +38,29 @@ $attendance = $DB->get_record('attendance', array('id' => $attforsession->attend
$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);
$qrpassflag = false;
// If the randomised code is on grab it.
if ($attforsession->rotateqrcode == 1) {
$cookiename = 'attendance_'.$attforsession->id;
$secrethash = md5($USER->id.$attforsession->rotateqrcodesecret);
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
// Check if cookie is set and verify
// Check if cookie is set and verify.
if (isset($_COOKIE[$cookiename])) {
// Check the token
// Check the token.
if ($secrethash !== $_COOKIE[$cookiename]) {
// Flag error
// Flag error.
print_error('qr_cookie_error', 'mod_attendance', $url);
}
} else {
// Check password
// Check password.
$sql = 'SELECT * FROM {attendance_rotate_passwords}'.
' WHERE attendanceid = ? AND expirytime > ? ORDER BY expirytime ASC LIMIT 2';
$qrpassdatabase = $DB->get_records_sql($sql, ['attendanceid' => $id, time() - $attconfig->rotateqrcodeexpirymargin]);
$qrpassflag = false;
' WHERE attendanceid = ? AND expirytime > ? ORDER BY expirytime ASC';
$qrpassdatabase = $DB->get_records_sql($sql, ['attendanceid' => $id, time() - $attconfig->rotateqrcodeexpirymargin], 0, 2);
foreach ($qrpassdatabase as $qrpasselement) {
if ($qrpass == $qrpasselement->password) {
@ -66,18 +69,15 @@ if ($attforsession->rotateqrcode == 1) {
}
if ($qrpassflag) {
// Create and store the token
// Create and store the token.
setcookie($cookiename, $secrethash, time() + (60 * 5), "/");
} else {
// Flag error
// Flag error.
print_error('qr_pass_wrong', 'mod_attendance', $url);
}
}
}
// Require the user is logged in.
require_login($course, true, $cm);
list($canmark, $reason) = attendance_can_student_mark($attforsession);
if (!$canmark) {
redirect(new moodle_url('/mod/attendance/view.php', array('id' => $cm->id)), get_string($reason, 'attendance'));
@ -98,8 +98,8 @@ if (empty($attforsession->includeqrcode)) {
$qrpass = ''; // Override qrpass if set, as it is not allowed.
}
// Check to see if autoassignstatus is in use and no password required.
if ($attforsession->autoassignstatus && empty($attforsession->studentpassword)) {
// Check to see if autoassignstatus is in use and no password required or Qrpass given and passed.
if ($attforsession->autoassignstatus && (empty($attforsession->studentpassword)) || $qrpassflag) {
$statusid = attendance_session_get_highest_status($att, $attforsession);
$url = new moodle_url('/mod/attendance/view.php', array('id' => $cm->id));
if (empty($statusid)) {

2
backup/moodle2/backup_attendance_activity_task.class.php

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class {@link backup_attendance_activity_task} definition
* Class {@see backup_attendance_activity_task} definition
*
* @package mod_attendance
* @copyright 2011 Artem Andreev <andreev.artem@gmail.com>

2
backup/moodle2/backup_attendance_stepslib.php

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines all the backup steps that will be used by {@link backup_attendance_activity_task}
* Defines all the backup steps that will be used by {@see backup_attendance_activity_task}
*
* @package mod_attendance
* @copyright 2011 Artem Andreev <andreev.artem@gmail.com>

8
backup/moodle2/restore_attendance_activity_task.class.php

@ -84,9 +84,9 @@ class restore_attendance_activity_task extends restore_activity_task {
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* by the {@see restore_logs_processor} when restoring
* attendance logs. It must return one array
* of {@link restore_log_rule} objects
* of {@see restore_log_rule} objects
*/
static public function define_restore_log_rules() {
$rules = array();
@ -97,9 +97,9 @@ class restore_attendance_activity_task extends restore_activity_task {
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* by the {@see restore_logs_processor} when restoring
* course logs. It must return one array
* of {@link restore_log_rule} objects
* of {@see restore_log_rule} objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at

48
classes/analytics/indicator/activity_base.php

@ -0,0 +1,48 @@
<?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/>.
/**
* Activity base class.
*
* @package mod_attendance
* @copyright 2020 Catalyst IT
* @author Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_attendance
* @copyright 2020 Catalyst IT
* @author Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* feedback_viewed_events
*
* @return string[]
*/
protected function feedback_viewed_events() {
return array('\mod_attendance\event\session_report_viewed');
}
}

69
classes/analytics/indicator/cognitive_depth.php

@ -0,0 +1,69 @@
<?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/>.
/**
* Cognitive depth indicator - attendance.
*
* @package mod_attendance
* @copyright 2020 Catalyst IT
* @author Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - attendance.
*
* @package mod_attendance
* @copyright 2020 Catalyst IT
* @author Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name() : \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_attendance');
}
/**
* Defines indicator type.
*
* @return string
*/
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
/**
* Returns the potential level of cognitive depth.
*
* @param \cm_info $cm
* @return int
*/
public function get_cognitive_depth_level(\cm_info $cm) {
return self::COGNITIVE_LEVEL_3;
}
}

69
classes/analytics/indicator/social_breadth.php

@ -0,0 +1,69 @@
<?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/>.
/**
* Social breadth indicator - attendance.
*
* @package mod_attendance
* @copyright 2020 Catalyst IT
* @author Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - attendance.
*
* @package mod_attendance
* @copyright 2020 Catalyst IT
* @author Dan Marsden
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name() : \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_attendance');
}
/**
* Defines indicator type.
*
* @return string
*/
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
/**
* Returns the potential level of social breadth.
*
* @param \cm_info $cm
* @return int
*/
public function get_social_breadth_level(\cm_info $cm) {
return self::SOCIAL_LEVEL_2;
}
}

136
classes/event/session_report_viewed.php

@ -0,0 +1,136 @@
<?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/>.
/**
* This file contains an event for when a student's attendance report is viewed.
*
* @package mod_attendance
* @copyright 2019 Nick Phillips
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_attendance\event;
defined('MOODLE_INTERNAL') || die();
/**
* Event for when a student's attendance report is viewed.
*
* @property-read array $other {
* Extra information about event properties.
*
* string studentid Id of student whose attendances were viewed.
* string mode Mode of the report viewed.
* }
* @package mod_attendance
* @copyright 2019 Nick Phillips
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class session_report_viewed extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
// Objecttable and objectid can't be meaningfully specified.
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return 'User with id ' . $this->userid . ' viewed attendance sessions for student with id ' .
$this->relateduserid;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventstudentattendancesessionsviewed', 'mod_attendance');
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
// Mode is optional.
$mode = empty($this->other['mode']) ? "" : $this->other['mode'];
return new \moodle_url('/mod/attendance/view.php', array('id' => $this->contextinstanceid,
'studentid' => $this->relateduserid,
'mode' => $mode,
'view' => $this->other['view'],
'curdate' => $this->other['curdate']));
}
/**
* Replace add_to_log() statement.
*
* @return array of parameters to be passed to legacy add_to_log() function.
*/
protected function get_legacy_logdata() {
return array($this->courseid, 'attendance', 'student sessions viewed', $this->get_url(),
'student id ' . $this->relateduserid, $this->contextinstanceid);
}
/**
* Get objectid mapping
*
* @return array of parameters for object mapping.
*/
public static function get_objectid_mapping() {
return array();
}
/**
* Get other mapping
*
* @return array of parameters for object mapping for objects referenced in 'other' property.
*/
public static function get_other_mapping() {
return array();
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
if (!isset($this->relateduserid)) {
throw new \coding_exception('The event mod_attendance\\event\\session_report_viewed must specify relateduserid.');
}
// View params can be left out as defaults will be the same when log event is viewed as when
// it was stored.
// filter params are important, but stored in session so default effectively unknown,
// hence required here.
if (!isset($this->other['view'])) {
throw new \coding_exception('The event mod_attendance\\event\\session_report_viewed must specify view.');
}
if (!isset($this->other['curdate'])) {
throw new \coding_exception('The event mod_attendance\\event\\session_report_viewed must specify curdate.');
}
parent::validate_data();
}
}

12
classes/import/sessions.php

@ -112,6 +112,7 @@ class sessions {
get_string('preventsharediptime', 'attendance'),
get_string('calendarevent', 'attendance'),
get_string('includeqrcode', 'attendance'),
get_string('rotateqrcode', 'attendance'),
);
}
@ -151,7 +152,8 @@ class sessions {
'preventsharedip' => $data->header16,
'preventsharediptime' => $data->header17,
'calendarevent' => $data->header18,
'includeqrcode' => $data->header19
'includeqrcode' => $data->header19,
'rotateqrcode' => $data->header20,
);
} else {
return array(
@ -174,7 +176,8 @@ class sessions {
'preventsharedip' => 16,
'preventsharediptime' => 17,
'calendarevent' => 18,
'includeqrcode' => 19
'includeqrcode' => 19,
'rotateqrcode' => 20
);
}
}
@ -357,6 +360,11 @@ class sessions {
}
}
if ($mapping['rotateqrcode'] == -1) {
$session->rotateqrcode = $pluginconfig->rotateqrcode_default;
} else {
$session->rotateqrcode = $this->get_column_data($row, $mapping['rotateqrcode']);
}
$session->statusset = 0;

8
classes/structure.php

@ -23,7 +23,9 @@
*/
defined('MOODLE_INTERNAL') || die();
global $CFG; // This class is included inside existing functions.
require_once(dirname(__FILE__) . '/calendar_helpers.php');
require_once($CFG->libdir .'/filelib.php');
/**
* Main class with all Attendance related info.
@ -107,7 +109,7 @@ class mod_attendance_structure {
* with a full database record (course should not be stored in instances table anyway).
*
* @param stdClass $dbrecord Attandance instance data from {attendance} table
* @param stdClass $cm Course module record as returned by {@link get_coursemodule_from_id()}
* @param stdClass $cm Course module record as returned by {@see get_coursemodule_from_id()}
* @param stdClass $course Course record from {course} table
* @param stdClass $context The context of the workshop instance
* @param stdClass $pageparams
@ -806,7 +808,7 @@ class mod_attendance_structure {
$groups = $groupid;
}
$users = get_users_by_capability($this->context, 'mod/attendance:canbelisted',
$userfields.',u.id, u.firstname, u.lastname, u.email',
$userfields,
$orderby, $startusers, $usersperpage, $groups,
'', false, true);
} else {
@ -822,7 +824,7 @@ class mod_attendance_structure {
$groups = $groupid;
}
$users = get_users_by_capability($this->context, 'mod/attendance:canbelisted',
$userfields.',u.id, u.firstname, u.lastname, u.email',
$userfields,
$orderby, '', '', $groups,
'', false, true);
} else {

8
coursesummary.php

@ -94,8 +94,14 @@ $table->setup();
// Work out direction of sort required.
$sortcolumns = $table->get_sort_columns();
// Now do sorting if specified.
// Sanity check $sort var before including in sql. Make sure it matches a known column.
$allowedsort = array_diff(array_keys($table->columns), $table->column_nosort);
if (!in_array($sort, $allowedsort)) {
$sort = '';
}
// Now do sorting if specified.
$orderby = ' ORDER BY percentage ASC';
if (!empty($sort)) {
$direction = ' DESC';

4
db/install.php

@ -36,6 +36,10 @@ function xmldb_attendance_install() {
$rec = new stdClass;
$rec->attendanceid = 0;
$rec->acronym = get_string($k.'acronym', 'attendance');
// Sanity check - if language translation uses more than the allowed 2 chars.
if (mb_strlen($rec->acronym) > 2) {
$rec->acronym = $k;
}
$rec->description = get_string($k.'full', 'attendance');
$rec->grade = $v;
$rec->visible = 1;

12
db/upgrade.php

@ -628,5 +628,17 @@ function xmldb_attendance_upgrade($oldversion=0) {
}
if ($oldversion < 2019072403) {
$table = new xmldb_table('attendance_sessions');
// Conditionally launch add index caleventid.
$index = new xmldb_index('caleventid', XMLDB_INDEX_NOTUNIQUE, array('caleventid'));
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}
// Attendance savepoint reached.
upgrade_mod_savepoint(true, 2019072403, 'attendance');
}
return $result;
}

4
export.php

@ -79,7 +79,9 @@ if ($formdata = $mform->get_data()) {
$reportdata = new attendance_report_data($att);
if ($reportdata->users) {
$filename = clean_filename($course->shortname.'_Attendances_'.userdate(time(), '%Y%m%d-%H%M'));
$filename = clean_filename($course->shortname.'_'.
get_string('modulenameplural', 'attendance').
'_'.userdate(time(), '%Y%m%d-%H%M'));
$group = $formdata->group ? $reportdata->groups[$formdata->group] : 0;
$data = new stdClass;

40
externallib.php

@ -45,7 +45,8 @@ class mod_wsattendance_external extends external_api {
'courseid' => new external_value(PARAM_INT, 'course id'),
'name' => new external_value(PARAM_TEXT, 'attendance name'),
'intro' => new external_value(PARAM_RAW, 'attendance description', VALUE_DEFAULT, ''),
'groupmode' => new external_value(PARAM_INT, 'group mode (0 - no groups, 1 - separate groups, 2 - visible groups)', VALUE_DEFAULT, 0),
'groupmode' => new external_value(PARAM_INT,
'group mode (0 - no groups, 1 - separate groups, 2 - visible groups)', VALUE_DEFAULT, 0),
)
);
}
@ -147,16 +148,18 @@ class mod_wsattendance_external extends external_api {
require_capability('mod/attendance:manageattendances', $context);
// Delete attendance instance.
attendance_delete_instance($params['attendanceid']);
$result = attendance_delete_instance($params['attendanceid']);
rebuild_course_cache($cm->course, true);
return $result;
}
/**
* Describes remove_attendance return values.
*
* @return void
* @return external_value
*/
public static function remove_attendance_returns() {
return new external_value(PARAM_BOOL, 'attendance deletion result');
}
/**
@ -188,7 +191,8 @@ class mod_wsattendance_external extends external_api {
* @param bool $addcalendarevent
* @return array
*/
public static function add_session(int $attendanceid, $description, int $sessiontime, int $duration, int $groupid, bool $addcalendarevent) {
public static function add_session(int $attendanceid, $description, int $sessiontime, int $duration, int $groupid,
bool $addcalendarevent) {
global $USER, $DB;
$params = self::validate_parameters(self::add_session_parameters(), array(
@ -218,7 +222,7 @@ class mod_wsattendance_external extends external_api {
throw new invalid_parameter_exception('Group id is not specified (or 0) in separate groups mode.');
}
if ($groupmode === SEPARATEGROUPS || ($groupmode === VISIBLEGROUPS && $groupid > 0)) {
// Determine valid groups
// Determine valid groups.
$userid = has_capability('moodle/site:accessallgroups', $context) ? 0 : $USER->id;
$validgroupids = array_map(function($group) {
return $group->id;
@ -284,7 +288,7 @@ class mod_wsattendance_external extends external_api {
* Delete session from attendance instance.
*
* @param int $sessionid
* @return int $sessionid
* @return bool
*/
public static function remove_session(int $sessionid) {
global $DB;
@ -308,14 +312,17 @@ class mod_wsattendance_external extends external_api {
// Delete session.
$attendance->delete_sessions(array($sessionid));
attendance_update_users_grade($attendance);
return true;
}
/**
* Describes remove_session return values.
*
* @return void
* @return external_value
*/
public static function remove_session_returns() {
return new external_value(PARAM_BOOL, 'attendance session deletion result');
}
/**
@ -492,6 +499,8 @@ class mod_wsattendance_external extends external_api {
* @param int $statusset
*/
public static function update_user_status($sessionid, $studentid, $takenbyid, $statusid, $statusset) {
global $DB;
$params = self::validate_parameters(self::update_user_status_parameters(), array(
'sessionid' => $sessionid,
'studentid' => $studentid,
@ -500,11 +509,20 @@ class mod_wsattendance_external extends external_api {
'statusset' => $statusset,
));
// Make sure session is open for marking.
$session = $DB->get_record('attendance_sessions', array('id' => $params['sessionid']), '*', MUST_EXIST);
list($canmark, $reason) = attendance_can_student_mark($attforsession);
if (!$canmark) {
throw new invalid_parameter_exception($reason);
$cm = get_coursemodule_from_instance('attendance', $session->attendanceid, 0, false, MUST_EXIST);
// Check permissions.
$context = context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/attendance:view', $context);
// If not a teacher, make sure session is open for self-marking.
if (!has_capability('mod/attendance:takeattendances', $context)) {
list($canmark, $reason) = attendance_can_student_mark($session);
if (!$canmark) {
throw new invalid_parameter_exception($reason);
}
}
// Check user id is valid.

19
lang/en/attendance.php

@ -216,6 +216,7 @@ $string['eventsessionipshared'] = 'Attendance self-marking IP conflict';
$string['eventsessionupdated'] = 'Session updated';
$string['eventstatusadded'] = 'Status added';
$string['eventstatusupdated'] = 'Status updated';
$string['eventstudentattendancesessionsviewed'] = 'Session report viewed';
$string['eventtaken'] = 'Attendance taken';
$string['eventtakenbystudent'] = 'Attendance taken by student';
$string['export'] = 'Export';
@ -254,6 +255,16 @@ $string['includeremarks'] = 'Include remarks';
$string['incorrectpassword'] = 'You have entered an incorrect password and your attendance has not been recorded, please enter the correct password.';
$string['incorrectpasswordshort'] = 'Incorrect password, attendance not recorded.';
$string['indetail'] = 'In detail...';
$string['indicator:cognitivedepth'] = 'Attendance cognitive';
$string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in an Attendance activity.';
$string['indicator:cognitivedepthdef'] = 'Attendance cognitive';
$string['indicator:cognitivedepthdef_help'] = 'The participant has reached this percentage of the cognitive engagement offered by the Attendance during this analysis interval (Levels = No view, View)';
$string['indicator:cognitivedepthdef_link'] = 'Learning_analytics_indicators#Cognitive_depth';
$string['indicator:socialbreadth'] = 'Attendance social';
$string['indicator:socialbreadth_help'] = 'This indicator is based on the social breadth reached by the student in an Attendance activity.';
$string['indicator:socialbreadthdef'] = 'Attendance social';
$string['indicator:socialbreadthdef_help'] = 'The participant has reached this percentage of the social engagement offered by the Attendance during this analysis interval (Levels = No participation, Participant alone)';
$string['indicator:socialbreadthdef_link'] = 'Learning_analytics_indicators#Social_breadth';
$string['invalidaction'] = 'You must select an action';
$string['invalidemails'] = 'You must specify addresses of existing user accounts, could not find: {$a}';
$string['invalidimportfile'] = 'File format is invalid.';
@ -332,6 +343,7 @@ $string['oversessionstaken_help'] = 'Shows statistics for sessions where attenda
<li><strong>Points</strong>: points awarded based on the taken sessions.</li>
<li><strong>Percentage</strong>: percentage of points awarded over the maxium possible points of the taken sessions.</li>
</ul>';
$string['pageof'] = 'Page {$a->page} of {$a->numpages}';
$string['participant'] = 'Participant';
$string['password'] = 'Password';
$string['enterpassword'] = 'Enter password';
@ -439,8 +451,8 @@ $string['sessiontype'] = 'Type';
$string['sessiontypeshort'] = 'Type';
$string['sessionupdated'] = 'Session successfully updated';
$string['set_by_student'] = 'Self-recorded';
$string['setallstatuses'] = 'Set status for all users';
$string['setallstatusesto'] = 'Set status for all users to «{$a}»';
$string['setallstatuses'] = 'Set status for';
$string['setallstatusesto'] = 'Set status to «{$a}»';
$string['setperiod'] = 'Specified time in minutes to release IP';
$string['settings'] = 'Settings';
$string['setunmarked'] = 'Automatically set when not marked';
@ -462,6 +474,8 @@ $string['statusdeleted'] = 'Status deleted';
$string['statuses'] = 'Statuses';
$string['statusset'] = 'Status set {$a}';
$string['statussetsettings'] = 'Status set';
$string['statusall'] = 'all';
$string['statusunselected'] = 'unselected';
$string['strftimedm'] = '%b %d';
$string['strftimedmy'] = '%d %b %Y';
$string['strftimedmyhm'] = '%d %b %Y %I.%M%p'; // Line added to allow multiple sessions in the same day.
@ -571,3 +585,4 @@ $string['formattexttype'] = 'Formatting';
$string['currentlyselectedusers'] = 'Currently selected users';
$string['usemessageform'] = 'or use the form below to send a message to the selected students';
$string['backtoparticipants'] = 'Back to participants list';
$string['previewhtml'] = 'HTML format preview';

35
locallib.php

@ -177,12 +177,21 @@ function attendance_form_sessiondate_selector (MoodleQuickForm $mform) {
}
$sesendtime = array();
$sesendtime[] =& $mform->createElement('static', 'from', '', get_string('from', 'attendance'));
$sesendtime[] =& $mform->createElement('select', 'starthour', get_string('hour', 'form'), $hours, false, true);
$sesendtime[] =& $mform->createElement('select', 'startminute', get_string('minute', 'form'), $minutes, false, true);
$sesendtime[] =& $mform->createElement('static', 'to', '', get_string('to', 'attendance'));
$sesendtime[] =& $mform->createElement('select', 'endhour', get_string('hour', 'form'), $hours, false, true);
$sesendtime[] =& $mform->createElement('select', 'endminute', get_string('minute', 'form'), $minutes, false, true);
if (!right_to_left()) {
$sesendtime[] =& $mform->createElement('static', 'from', '', get_string('from', 'attendance'));
$sesendtime[] =& $mform->createElement('select', 'starthour', get_string('hour', 'form'), $hours, false, true);
$sesendtime[] =& $mform->createElement('select', 'startminute', get_string('minute', 'form'), $minutes, false, true);
$sesendtime[] =& $mform->createElement('static', 'to', '', get_string('to', 'attendance'));
$sesendtime[] =& $mform->createElement('select', 'endhour', get_string('hour', 'form'), $hours, false, true);
$sesendtime[] =& $mform->createElement('select', 'endminute', get_string('minute', 'form'), $minutes, false, true);
} else {
$sesendtime[] =& $mform->createElement('static', 'from', '', get_string('from', 'attendance'));
$sesendtime[] =& $mform->createElement('select', 'startminute', get_string('minute', 'form'), $minutes, false, true);
$sesendtime[] =& $mform->createElement('select', 'starthour', get_string('hour', 'form'), $hours, false, true);
$sesendtime[] =& $mform->createElement('static', 'to', '', get_string('to', 'attendance'));
$sesendtime[] =& $mform->createElement('select', 'endminute', get_string('minute', 'form'), $minutes, false, true);
$sesendtime[] =& $mform->createElement('select', 'endhour', get_string('hour', 'form'), $hours, false, true);
}
$mform->addGroup($sesendtime, 'sestime', get_string('time', 'attendance'), array(' '), true);
}
@ -458,7 +467,7 @@ function attendance_can_student_mark($sess, $log = true) {
$record = $DB->get_record_select('attendance_log', $sql, $params);
} else {
// Assume ATTENDANCE_SHAREDIP_FORCED.
$sql = 'sessionid = ? AND studentid <> ? ipaddress = ?';
$sql = 'sessionid = ? AND studentid <> ? AND ipaddress = ?';
$params = array($sess->id, $USER->id, getremoteaddr());
$record = $DB->get_record_select('attendance_log', $sql, $params);
}
@ -509,7 +518,7 @@ function attendance_exporttotableed($data, $filename, $format) {
// Sending HTTP headers.
$workbook->send($filename);
// Creating the first worksheet.
$myxls = $workbook->add_worksheet('Attendances');
$myxls = $workbook->add_worksheet(get_string('modulenameplural', 'attendance'));
// Format types.
$formatbc = $workbook->add_format();
$formatbc->set_bold(1);
@ -695,7 +704,13 @@ function attendance_construct_sessions_data_for_add($formdata, mod_attendance_st
}
} else {
$sess = new stdClass();
$sess->sessdate = $sessiondate;
$sess->sessdate = make_timestamp(
date("Y", $formdata->sessiondate),
date("m", $formdata->sessiondate),
date("d", $formdata->sessiondate),
$formdata->sestime['starthour'],
$formdata->sestime['startminute']
);
$sess->duration = $duration;
$sess->descriptionitemid = $formdata->sdescription['itemid'];
$sess->description = $formdata->sdescription['text'];
@ -1172,4 +1187,4 @@ function attendance_return_passwords($session) {
$sql = 'SELECT * FROM {attendance_rotate_passwords} WHERE attendanceid = ? AND expirytime > ? ORDER BY expirytime ASC';
return json_encode($DB->get_records_sql($sql, ['attendanceid' => $session->id, time()], $strictness = IGNORE_MISSING));
}
}

4
messageselect.php

@ -131,7 +131,7 @@ if (!empty($messagebody) && !$edit && !$deluser && ($preview || $send)) {
<input type="hidden" name="format" value="'.$format.'" />
<input type="hidden" name="sesskey" value="' . sesskey() . '" />
';
echo "<h3>".get_string('previewhtml')."</h3>";
echo "<h3>".get_string('previewhtml', 'mod_attendance')."</h3>";
echo "<div class=\"messagepreview\">\n".format_text($messagebody, $format)."\n</div>\n";
echo '<p align="center"><input type="submit" name="send" value="'.get_string('sendmessage', 'message').'" />'."\n";
echo '<input type="submit" name="edit" value="'.get_string('update').'" /></p>';
@ -156,7 +156,7 @@ if (!empty($messagebody) && !$edit && !$deluser && ($preview || $send)) {
}
echo '</ul>';
}
echo '<p align="center"><a href="index.php?id='.$id.'">'.get_string('backtoparticipants').'</a></p>';
echo '<p align="center"><a href="index.php?id='.$id.'">'.get_string('backtoparticipants', 'mod_attendance').'</a></p>';
}
echo $OUTPUT->footer();
exit;

2
password.php

@ -17,7 +17,7 @@
/**
* Displays help via AJAX call or in a new page
*
* Use {@link core_renderer::help_icon()} or {@link addHelpButton()} to display
* Use {@see core_renderer::help_icon()} or {@see addHelpButton()} to display
* the help icon.
*
* @copyright 2017 Dan Marsden

2
password_ajax.php

@ -17,7 +17,7 @@
/**
* Displays help via AJAX call or in a new page
*
* Use {@link core_renderer::help_icon()} or {@link addHelpButton()} to display
* Use {@see core_renderer::help_icon()} or {@see addHelpButton()} to display
* the help icon.
*
* @copyright 2017 Dan Marsden

114
renderer.php

@ -28,6 +28,7 @@ require_once(dirname(__FILE__).'/locallib.php');
require_once(dirname(__FILE__).'/renderables.php');
require_once(dirname(__FILE__).'/renderhelpers.php');
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->libdir.'/moodlelib.php');
/**
* Attendance module renderer class
@ -129,7 +130,11 @@ class mod_attendance_renderer extends plugin_renderer_base {
'page' => $fcontrols->pageparams->page - 1)),
$this->output->larrow());
}
$pagingcontrols .= html_writer::tag('span', "Page {$fcontrols->pageparams->page} of $numberofpages",
$a = new stdClass();
$a->page = $fcontrols->pageparams->page;
$a->numpages = $numberofpages;
$text = get_string('pageof', 'attendance', $a);
$pagingcontrols .= html_writer::tag('span', $text,
array('class' => 'attbtn'));
if ($fcontrols->pageparams->page < $numberofpages) {
$pagingcontrols .= html_writer::link($fcontrols->url(array('curdate' => $fcontrols->curdate,
@ -253,8 +258,8 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table->width = '100%';
$table->head = array(
'#',
get_string('date'),
get_string('time'),
get_string('date', 'attendance'),
get_string('time', 'attendance'),
get_string('sessiontypeshort', 'attendance'),
get_string('description', 'attendance'),
get_string('actions'),
@ -414,6 +419,8 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
protected function render_attendance_take_data(attendance_take_data $takedata) {
user_preference_allow_ajax_update('mod_attendance_statusdropdown', PARAM_TEXT);
$controls = $this->render_attendance_take_controls($takedata);
$table = html_writer::start_div('no-overflow');
if ($takedata->pageparams->viewmode == mod_attendance_take_page_params::SORTED_LIST) {
@ -532,7 +539,11 @@ class mod_attendance_renderer extends plugin_renderer_base {
$controls .= html_writer::link($takedata->url(array('page' => $takedata->pageparams->page - 1)),
$this->output->larrow());
}
$controls .= html_writer::tag('span', "Page {$takedata->pageparams->page} of $numberofpages",
$a = new stdClass();
$a->page = $takedata->pageparams->page;
$a->numpages = $numberofpages;
$text = get_string('pageof', 'attendance', $a);
$controls .= html_writer::tag('span', $text,
array('class' => 'attbtn'));
if ($takedata->pageparams->page < $numberofpages) {
$controls .= html_writer::link($takedata->url(array('page' => $takedata->pageparams->page + 1,
@ -595,6 +606,26 @@ class mod_attendance_renderer extends plugin_renderer_base {
return $controls;
}
/**
* get statusdropdown
*
* @return \single_select
*/
private function statusdropdown() {
$pref = get_user_preferences('mod_attendance_statusdropdown');
if (empty($pref)) {
$pref = 'unselected';
}
$options = array('all' => get_string('statusall', 'attendance'),
'unselected' => get_string('statusunselected', 'attendance'));
$select = new \single_select(new \moodle_url('/'), 'setallstatus-select', $options,
$pref, null, 'setallstatus-select');
$select->label = get_string('setallstatuses', 'attendance');
return $select;
}
/**
* Render take list.
*
@ -602,7 +633,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
protected function render_attendance_take_list(attendance_take_data $takedata) {
global $PAGE, $CFG;
global $CFG;
$table = new html_table();
$table->width = '0%';
$table->head = array(
@ -627,10 +658,17 @@ class mod_attendance_renderer extends plugin_renderer_base {
$table->align[] = 'center';
$table->size[] = '20px';
// JS to select all radios of this status and prevent default behaviour of # link.
$PAGE->requires->js_amd_inline("
$this->page->requires->js_amd_inline("
require(['jquery'], function($) {
$('#checkstatus".$st->id."').click(function(e) {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
if ($('select[name=\"setallstatus-select\"] option:selected').val() == 'all') {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
M.util.set_user_preference('mod_attendance_statusdropdown','all');
}
else {
$('#attendancetakeform').find('input:indeterminate.st".$st->id."').prop('checked', true);
M.util.set_user_preference('mod_attendance_statusdropdown','unselected');
}
e.preventDefault();
});
});");
@ -644,12 +682,14 @@ class mod_attendance_renderer extends plugin_renderer_base {
// Show a 'select all' row of radio buttons.
$row = new html_table_row();
$row->cells[] = '';
$row->attributes['class'] = 'setallstatusesrow';
foreach ($extrasearchfields as $field) {
$row->cells[] = '';
}
$row->cells[] = html_writer::div(get_string('setallstatuses', 'attendance'), 'setallstatuses');
$cell = new html_table_cell(html_writer::div($this->output->render($this->statusdropdown()), 'setallstatuses'));
$cell->colspan = 2;
$row->cells[] = $cell;
foreach ($takedata->statuses as $st) {
$attribs = array(
'id' => 'radiocheckstatus'.$st->id,
@ -660,10 +700,17 @@ class mod_attendance_renderer extends plugin_renderer_base {
);
$row->cells[] = html_writer::empty_tag('input', $attribs);
// Select all radio buttons of the same status.
$PAGE->requires->js_amd_inline("
$this->page->requires->js_amd_inline("
require(['jquery'], function($) {
$('#radiocheckstatus".$st->id."').click(function(e) {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
if ($('select[name=\"setallstatus-select\"] option:selected').val() == 'all') {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
M.util.set_user_preference('mod_attendance_statusdropdown','all');
}
else {
$('#attendancetakeform').find('input:indeterminate.st".$st->id."').prop('checked', true);
M.util.set_user_preference('mod_attendance_statusdropdown','unselected');
}
});
});");
}
@ -713,7 +760,6 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
protected function render_attendance_take_grid(attendance_take_data $takedata) {
global $PAGE;
$table = new html_table();
for ($i = 0; $i < $takedata->pageparams->gridcols; $i++) {
$table->align[] = 'center';
@ -721,15 +767,24 @@ class mod_attendance_renderer extends plugin_renderer_base {
}
$table->attributes['class'] = 'generaltable takegrid';
$table->headspan = $takedata->pageparams->gridcols;
$head = array();
$head[] = html_writer::div($this->output->render($this->statusdropdown()), 'setallstatuses');
foreach ($takedata->statuses as $st) {
$head[] = html_writer::link("#", $st->acronym, array('id' => 'checkstatus'.$st->id,
'title' => get_string('setallstatusesto', 'attendance', $st->description)));
// JS to select all radios of this status and prevent default behaviour of # link.
$PAGE->requires->js_amd_inline("
$this->page->requires->js_amd_inline("
require(['jquery'], function($) {
$('#checkstatus".$st->id."').click(function(e) {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
if ($('select[name=\"setallstatus-select\"] option:selected').val() == 'unselected') {
$('#attendancetakeform').find('input:indeterminate.st".$st->id."').prop('checked', true);
M.util.set_user_preference('mod_attendance_statusdropdown','unselected');
}
else {
$('#attendancetakeform').find('.st".$st->id."').prop('checked', true);
M.util.set_user_preference('mod_attendance_statusdropdown','all');
}
e.preventDefault();
});
});");
@ -1046,7 +1101,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
private function construct_user_sessions_log(attendance_user_data $userdata) {
global $OUTPUT, $USER;
global $USER;
$context = context_module::instance($userdata->filtercontrols->cm->id);
$shortform = false;
@ -1141,7 +1196,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
'sessionid' => $sess->id,
'grouptype' => $sess->groupid);
$url = new moodle_url('/mod/attendance/take.php', $params);
$icon = $OUTPUT->pix_icon('redo', get_string('changeattendance', 'attendance'), 'attendance');
$icon = $this->output->pix_icon('redo', get_string('changeattendance', 'attendance'), 'attendance');
$row->cells[] = html_writer::link($url, $icon);
}
@ -1171,7 +1226,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
protected function render_attendance_report_data(attendance_report_data $reportdata) {
global $PAGE, $COURSE;
global $COURSE;
// Initilise Javascript used to (un)check all checkboxes.
$this->page->requires->js_init_call('M.mod_attendance.init_manage');
@ -1201,7 +1256,7 @@ class mod_attendance_renderer extends plugin_renderer_base {
$summaryrows = $this->get_summary_rows($reportdata, $startwithcontrast);
// 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);
$bulkmessagecapability = has_capability('moodle/course:bulkmessaging', $this->page->context);
// Extract rows from each part and collate them into one row each.
$sessiondetailsleft = $reportdata->pageparams->sessiondetailspos == 'left';
@ -1244,10 +1299,9 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return array Array of html_table_row objects
*/
protected function get_user_rows(attendance_report_data $reportdata) {
global $OUTPUT, $PAGE;
$rows = array();
$bulkmessagecapability = has_capability('moodle/course:bulkmessaging', $PAGE->context);
$bulkmessagecapability = has_capability('moodle/course:bulkmessaging', $this->page->context);
$extrafields = get_extra_user_fields($reportdata->att->context);
$showextrauserdetails = $reportdata->pageparams->showextrauserdetails;
$params = $reportdata->pageparams->get_significant_params();
@ -1256,12 +1310,12 @@ class mod_attendance_renderer extends plugin_renderer_base {
if ($showextrauserdetails) {
$params['showextrauserdetails'] = 0;
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/switch_minus',
$text .= $this->output->action_icon($url, new pix_icon('t/switch_minus',
get_string('hideextrauserdetails', 'attendance')), null, null);
} else {
$params['showextrauserdetails'] = 1;
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/switch_plus',
$text .= $this->output->action_icon($url, new pix_icon('t/switch_plus',
get_string('showextrauserdetails', 'attendance')), null, null);
$extrafields = array();
}
@ -1479,7 +1533,6 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return array Array of html_table_row objects
*/
protected function get_session_rows(attendance_report_data $reportdata, $startwithcontrast=false) {
global $OUTPUT;
$rows = array();
@ -1492,13 +1545,13 @@ class mod_attendance_renderer extends plugin_renderer_base {
if ($showsessiondetails) {
$params['showsessiondetails'] = 0;
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/switch_minus',
$text .= $this->output->action_icon($url, new pix_icon('t/switch_minus',
get_string('hidensessiondetails', 'attendance')), null, null);
$colspan = count($reportdata->sessions);
} else {
$params['showsessiondetails'] = 1;
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/switch_plus',
$text .= $this->output->action_icon($url, new pix_icon('t/switch_plus',
get_string('showsessiondetails', 'attendance')), null, null);
$colspan = 1;
}
@ -1510,11 +1563,11 @@ class mod_attendance_renderer extends plugin_renderer_base {
if ($reportdata->pageparams->sessiondetailspos == 'left') {
$params['sessiondetailspos'] = 'right';
$url = $reportdata->att->url_report($params);
$text .= $OUTPUT->action_icon($url, new pix_icon('t/right', get_string('moveright', 'attendance')), null, null);
$text .= $this->output->action_icon($url, new pix_icon('t/right', get_string('moveright', 'attendance')), null, null);
} else {
$params['sessiondetailspos'] = 'left';
$url = $reportdata->att->url_report($params);
$text = $OUTPUT->action_icon($url, new pix_icon('t/left', get_string('moveleft', 'attendance')), null, null) . $text;
$text = $this->output->action_icon($url, new pix_icon('t/left', get_string('moveleft', 'attendance')), null, null) . $text;
}
$row->cells[] = $this->build_header_cell($text, '', true, $colspan);
@ -1863,23 +1916,22 @@ class mod_attendance_renderer extends plugin_renderer_base {
* @return string
*/
private function construct_preferences_actions_icons($st, $prefdata) {
global $OUTPUT;
$params = array('sesskey' => sesskey(),
'statusid' => $st->id);
if ($st->visible) {
$params['action'] = mod_attendance_preferences_page_params::ACTION_HIDE;
$showhideicon = $OUTPUT->action_icon(
$showhideicon = $this->output->action_icon(
$prefdata->url($params),
new pix_icon("t/hide", get_string('hide')));
} else {
$params['action'] = mod_attendance_preferences_page_params::ACTION_SHOW;
$showhideicon = $OUTPUT->action_icon(
$showhideicon = $this->output->action_icon(
$prefdata->url($params),
new pix_icon("t/show", get_string('show')));
}
if (empty($st->haslogs)) {
$params['action'] = mod_attendance_preferences_page_params::ACTION_DELETE;
$deleteicon = $OUTPUT->action_icon(
$deleteicon = $this->output->action_icon(
$prefdata->url($params),
new pix_icon("t/delete", get_string('delete')));
} else {

3
settings.php

@ -136,6 +136,9 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_configcheckbox('attendance/includeqrcode_default',
get_string('includeqrcode', 'attendance'), '', 0));
$settings->add(new admin_setting_configcheckbox('attendance/rotateqrcode_default',
get_string('rotateqrcode', 'attendance'), '', 0));
$settings->add(new admin_setting_configcheckbox('attendance/autoassignstatus',
get_string('autoassignstatus', 'attendance'), '', 0));

16
templates/mobile_view_page.mustache

@ -74,7 +74,7 @@
<ion-item>
<ion-grid>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.sessionscompleted' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -82,7 +82,7 @@
</ion-col>
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.pointssessionscompleted' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -90,7 +90,7 @@
</ion-col>
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.percentagesessionscompleted' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -99,7 +99,7 @@
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.sessionstotal' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -107,7 +107,7 @@
</ion-col>
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.pointsallsessions' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -115,7 +115,7 @@
</ion-col>
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.percentageallsessions' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -123,7 +123,7 @@
</ion-col>
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.maxpossiblepoints' | translate }}
</ion-col>
<ion-col col-2 class="text-left">
@ -131,7 +131,7 @@
</ion-col>
</ion-row>
<ion-row>
<ion-col col-9 class="text-left">
<ion-col col-9 text-wrap class="text-left">
{{ 'plugin.mod_attendance.maxpossiblepercentage' | translate }}
</ion-col>
<ion-col col-2 class="text-left">

18
tests/behat/extra_features.feature

@ -182,16 +182,16 @@ Feature: Test the various new features in the attendance module
And I click on "submitbutton" "button"
When I click on "Take attendance" "link" in the "10AM" "table_row"
Then "Set status for all users to «Present»" "link" should exist
And "Set status for all users to «Late»" "link" should exist
And "Set status for all users to «Excused»" "link" should exist
And "Set status for all users to «Absent»" "link" should exist
Then "Set status to «Present»" "link" should exist
And "Set status to «Late»" "link" should exist
And "Set status to «Excused»" "link" should exist
And "Set status to «Absent»" "link" should exist
When I follow "Sessions"
And I click on "Take attendance" "link" in the "12PM" "table_row"
Then "Set status for all users to «Great»" "link" should exist
And "Set status for all users to «OK»" "link" should exist
And "Set status for all users to «Bad»" "link" should exist
Then "Set status to «Great»" "link" should exist
And "Set status to «OK»" "link" should exist
And "Set status to «Bad»" "link" should exist
Scenario: A teacher can use the radio buttons to set attendance values for all users
Given I log in as "teacher1"
@ -202,8 +202,8 @@ Feature: Test the various new features in the attendance module
| id_addmultiply | 0 |
And I click on "submitbutton" "button"
And I click on "Take attendance" "link"
When I click on "setallstatuses" "field" in the ".takelist tbody td.c4" "css_element"
And I set the field "Set status for" to "all"
When I click on "setallstatuses" "field" in the ".takelist tbody td.c3" "css_element"
And I press "Save attendance"
And I follow "Report"
Then "L" "text" should exist in the "Student 1" "table_row"

18
tests/externallib_test.php

@ -39,6 +39,7 @@ require_once($CFG->dirroot . '/mod/attendance/externallib.php');
* @category external
* @copyright 2015 Caio Bressan Doneda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @group mod_attendance
*/
class mod_attendance_external_testcase extends externallib_advanced_testcase {
/** @var coursecat */
@ -354,25 +355,28 @@ class mod_attendance_external_testcase extends externallib_advanced_testcase {
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$this->setUser($teacher);
// Create attendance.
$attendance = mod_wsattendance_external::add_attendance($course->id, 'test', 'test', NOGROUPS);
// Create attendance with no groups mode.
$attendancenogroups = mod_wsattendance_external::add_attendance($course->id, 'nogroups',
'test', NOGROUPS);
$attendancenogroups = external_api::clean_returnvalue(mod_wsattendance_external::add_attendance_returns(),
$attendancenogroups);
// Check attendance exist.
$this->assertCount(1, $DB->get_records('attendance', ['course' => $course->id]));
// Create session.
$result0 = mod_wsattendance_external::add_session($attendance['attendanceid'], 'test0', time(), 3600, 0, false);
$result1 = mod_wsattendance_external::add_session($attendance['attendanceid'], 'test1', time(), 3600, 0, false);
$result0 = mod_wsattendance_external::add_session($attendancenogroups['attendanceid'], 'test0', time(), 3600, 0, false);
$result1 = mod_wsattendance_external::add_session($attendancenogroups['attendanceid'], 'test1', time(), 3600, 0, false);
$this->assertCount(2, $DB->get_records('attendance_sessions', ['attendanceid' => $attendance['attendanceid']]));
$this->assertCount(2, $DB->get_records('attendance_sessions', ['attendanceid' => $attendancenogroups['attendanceid']]));
// Delete session 0.
mod_wsattendance_external::remove_session($result0['sessionid']);
$this->assertCount(1, $DB->get_records('attendance_sessions', ['attendanceid' => $attendance['attendanceid']]));
$this->assertCount(1, $DB->get_records('attendance_sessions', ['attendanceid' => $attendancenogroups['attendanceid']]));
// Delete session 1.
mod_wsattendance_external::remove_session($result1['sessionid']);
$this->assertCount(0, $DB->get_records('attendance_sessions', ['attendanceid' => $attendance['attendanceid']]));
$this->assertCount(0, $DB->get_records('attendance_sessions', ['attendanceid' => $attendancenogroups['attendanceid']]));
}
public function test_add_session_creates_calendar_event() {

2
tests/generator/lib.php

@ -40,7 +40,7 @@ class mod_attendance_generator extends testing_module_generator {
*
* @param array|stdClass $record
* @param array $options
* @return stdClass activity record with extra cmid field
* @return stdClass mod_attendance_structure
*/
public function create_instance($record = null, array $options = null) {
global $CFG;

2
thirdpartylibs.xml

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<libraries>
<library>
<location>js/qrcode/qrcode.js</location>
<location>js/qrcode</location>
<name>jquery.qrcode.js</name>
<version>1.0</version>
<license>MIT</license>

6
version.php

@ -23,9 +23,9 @@
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019072100;
$plugin->requires = 2018102700; // Requires 3.6.
$plugin->release = '3.6.7';
$plugin->version = 2019072404;
$plugin->requires = 2019052000; // Requires 3.7.
$plugin->release = '3.7.2';
$plugin->maturity = MATURITY_STABLE;
$plugin->cron = 0;
$plugin->component = 'mod_attendance';

23
view.php

@ -78,6 +78,29 @@ if (isset($pageparams->studentid) && $USER->id != $pageparams->studentid) {
}
$userdata = new attendance_user_data($att, $userid);
// Create url for link in log screen.
$filterparams = array(
'view' => $userdata->pageparams->view,
'curdate' => $userdata->pageparams->curdate,
'startdate' => $userdata->pageparams->startdate,
'enddate' => $userdata->pageparams->enddate
);
$params = array_merge($userdata->pageparams->get_significant_params(), $filterparams);
if (empty($userdata->pageparams->studentid)) {
$relateduserid = $USER->id;
} else {
$relateduserid = $userdata->pageparams->studentid;
}
// Trigger viewed event.
$event = \mod_attendance\event\session_report_viewed::create(array(
'relateduserid' => $relateduserid,
'context' => $context,
'other' => $params));
$event->add_record_snapshot('course_modules', $cm);
$event->trigger();
$header = new mod_attendance_header($att);
echo $output->header();

Loading…
Cancel
Save