Merge notification/warning feature into master. (#267)
authorDan Marsden <dan@danmarsden.com>
Wed, 21 Jun 2017 02:53:23 +0000 (14:53 +1200)
committerDan Marsden <dan@danmarsden.com>
Wed, 21 Jun 2017 23:29:38 +0000 (11:29 +1200)
New feature/reports to allow warning thresholds to be set and e-mail notifications.

18 files changed:
CHANGELOG.md
atrisk.php [new file with mode: 0644]
backup/moodle2/backup_attendance_stepslib.php
backup/moodle2/restore_attendance_stepslib.php
classes/add_warning_form.php [new file with mode: 0644]
classes/structure.php
classes/task/notify.php [new file with mode: 0644]
db/install.xml
db/tasks.php
db/upgrade.php
lang/en/attendance.php
lib.php
locallib.php
renderables.php
settings.php
tests/behat/report.feature
version.php
warnings.php [new file with mode: 0644]

index 07a401f..47827fe 100644 (file)
@@ -1,6 +1,7 @@
-### Date:              2017-June-14
-### Release:   3.2.13
+### Date:              2017-June-22
+### Release:   3.2.14
 - New Feature: Allow automatic marking using site logs.
+- New Feature: Warn users when attendance drops below threshold.
 - Improvement: Allow default view for teachers to be set at admin level.
 - Improvement: All courses user report now displays as table.
 - Bug fix: Restored attendances do not create calendar events correctly.
diff --git a/atrisk.php b/atrisk.php
new file mode 100644 (file)
index 0000000..86cb146
--- /dev/null
@@ -0,0 +1,141 @@
+<?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/>.
+
+/**
+ * Attendance course summary report.
+ *
+ * @package    mod_attendance
+ * @copyright  2017 onwards Dan Marsden http://danmarsden.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../../config.php');
+require_once($CFG->dirroot.'/mod/attendance/lib.php');
+require_once($CFG->dirroot.'/mod/attendance/locallib.php');
+require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->libdir.'/coursecatlib.php');
+
+$category = optional_param('category', 0, PARAM_INT);
+$attendancecm = optional_param('id', 0, PARAM_INT);
+$download = optional_param('download', '', PARAM_ALPHA);
+$sort = optional_param('tsort', '', PARAM_ALPHA);
+
+if (!empty($category)) {
+    $context = context_coursecat::instance($category);
+    $coursecat = coursecat::get($category);
+    $courses = $coursecat->get_courses(array('recursive' => true, 'idonly' => true));
+    $PAGE->set_category_by_id($category);
+    require_login();
+} else if (!empty($attendancecm)) {
+    $cm             = get_coursemodule_from_id('attendance', $attendancecm, 0, false, MUST_EXIST);
+    $course         = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+    $att            = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST);
+    $courses = array($course->id);
+    $context = context_module::instance($cm->id);
+    require_login($course, false, $cm);
+} else {
+    $context = context_system::instance();
+    $courses = array(); // Show all courses.
+    $PAGE->set_context($context);
+    require_login();
+}
+// Check permissions.
+require_capability('mod/attendance:viewreports', $context);
+
+$exportfilename = 'attendanceatrisk.csv';
+
+$PAGE->set_url('/mod/attendance/atrisk.php', array('category' => $category));
+
+$PAGE->set_heading($SITE->fullname);
+
+$table = new flexible_table('attendanceatrisk');
+$table->define_baseurl($PAGE->url);
+
+if (!$table->is_downloading($download, $exportfilename)) {
+    if (!empty($attendancecm)) {
+        $pageparams = new mod_attendance_sessions_page_params();
+        $att = new mod_attendance_structure($att, $cm, $course, $context, $pageparams);
+        $output = $PAGE->get_renderer('mod_attendance');
+        $tabs = new attendance_tabs($att, attendance_tabs::TAB_ATRISK);
+        echo $output->header();
+        echo $output->heading(get_string('attendanceforthecourse', 'attendance').' :: ' .format_string($course->fullname));
+        echo $output->render($tabs);
+    } else {
+        echo $OUTPUT->header();
+        echo $OUTPUT->heading(get_string('atriskreport', 'mod_attendance'));
+        if (empty($category)) {
+            // Only show tabs if displaying via the admin page.
+            $tabmenu = attendance_print_settings_tabs('atrisk');
+            echo $tabmenu;
+        }
+    }
+
+}
+
+$table->define_columns(array('course', 'attendance', 'userid', 'numtakensessions', 'percent', 'timesent'));
+$table->define_headers(array(get_string('course'),
+    get_string('pluginname', 'attendance'),
+    get_string('user'),
+    get_string('takensessions', 'attendance'),
+    get_string('averageattendance', 'attendance'),
+    get_string('triggered', 'attendance')));
+$table->sortable(true);
+$table->no_sorting('course');
+$table->set_attribute('cellspacing', '0');
+$table->set_attribute('class', 'generaltable generalbox');
+$table->show_download_buttons_at(array(TABLE_P_BOTTOM));
+$table->setup();
+
+// Work out direction of sort required.
+$sortcolumns = $table->get_sort_columns();
+// Now do sorting if specified.
+
+$orderby = ' ORDER BY percent ASC';
+if (!empty($sort)) {
+    $direction = ' DESC';
+    if (!empty($sortcolumns[$sort]) && $sortcolumns[$sort] == SORT_ASC) {
+        $direction = ' ASC';
+    }
+    $orderby = " ORDER BY $sort $direction";
+
+}
+
+$records = attendance_get_users_to_notify($courses, $orderby);
+foreach ($records as $record) {
+    if (!$table->is_downloading($download, $exportfilename)) {
+        $url = new moodle_url('/mod/attendance/index.php', array('id' => $record->courseid));
+        $name = html_writer::link($url, $record->coursename);
+    } else {
+        $name = $record->coursename;
+    }
+    $url = new moodle_url('/mod/attendance/view.php', array('studentid' => $record->userid,
+                                                                'id' => $record->cmid, 'view' => ATT_VIEW_ALL));
+    $attendancename = html_writer::link($url, $record->aname);
+
+    $username = html_writer::link($url, fullname($record));
+    $percent = round($record->percent * 100)."%";
+    $timesent = "-";
+    if (!empty($record->timesent)) {
+        $timesent = userdate($record->timesent);
+    }
+
+    $table->add_data(array($name, $attendancename, $username, $record->numtakensessions, $percent, $timesent));
+}
+$table->finish_output();
+
+if (!$table->is_downloading()) {
+    echo $OUTPUT->footer();
+}
\ No newline at end of file
index 1d67563..9275784 100644 (file)
@@ -50,6 +50,10 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
         $status  = new backup_nested_element('status', array('id'), array(
             'acronym', 'description', 'grade', 'studentavailability', 'setunmarked', 'visible', 'deleted', 'setnumber'));
 
+        $warnings = new backup_nested_element('warnings');
+        $warning  = new backup_nested_element('warning', array('id'), array(
+            'warningpercent', 'warnafter', 'emailuser', 'emailsubject', 'emailcontent', 'emailcontentformat', 'thirdpartyemails'));
+
         $sessions = new backup_nested_element('sessions');
         $session  = new backup_nested_element('session', array('id'), array(
             'groupid', 'sessdate', 'duration', 'lasttaken', 'lasttakenby',
@@ -65,6 +69,9 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
         $attendance->add_child($statuses);
         $statuses->add_child($status);
 
+        $attendance->add_child($warnings);
+        $warnings->add_child($warning);
+
         $attendance->add_child($sessions);
         $sessions->add_child($session);
 
@@ -77,6 +84,9 @@ class backup_attendance_activity_structure_step extends backup_activity_structur
 
         $status->set_source_table('attendance_statuses', array('attendanceid' => backup::VAR_PARENTID));
 
+        $warning->set_source_table('attendance_warning',
+            array('idnumber' => backup::VAR_PARENTID));
+
         $session->set_source_table('attendance_sessions', array('attendanceid' => backup::VAR_PARENTID));
 
         // Data sources - user related data.
index ba6b6fd..8a11e11 100644 (file)
@@ -49,6 +49,9 @@ class restore_attendance_activity_structure_step extends restore_activity_struct
         $paths[] = new restore_path_element('attendance_status',
                        '/activity/attendance/statuses/status');
 
+        $paths[] = new restore_path_element('attendance_warning',
+            '/activity/attendance/warnings/warning');
+
         $paths[] = new restore_path_element('attendance_session',
                        '/activity/attendance/sessions/session');
 
@@ -101,6 +104,21 @@ class restore_attendance_activity_structure_step extends restore_activity_struct
     }
 
     /**
+     * Process attendance warning restore
+     * @param object $data The data in object form
+     * @return void
+     */
+    protected function process_attendance_warning($data) {
+        global $DB;
+
+        $data = (object)$data;
+
+        $data->idnumber = $this->get_new_parentid('attendance');
+
+        $DB->insert_record('attendance_warning', $data);
+    }
+
+    /**
      * Process attendance session restore
      * @param object $data The data in object form
      * @return void
diff --git a/classes/add_warning_form.php b/classes/add_warning_form.php
new file mode 100644 (file)
index 0000000..a8682a2
--- /dev/null
@@ -0,0 +1,108 @@
+<?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/>.
+
+/**
+ * Contains class mod_attendance_add_warning_form
+ *
+ * @package   mod_attendance
+ * @copyright 2017 Dan Marsden
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class mod_attendance_add_warning_form
+ *
+ * @package   mod_attendance
+ * @copyright 2017 Dan Marsden
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_attendance_add_warning_form extends moodleform {
+    /**
+     * Form definition
+     */
+    public function definition() {
+        global $COURSE;
+        $mform = $this->_form;
+
+        // Load global defaults.
+        $config = get_config('attendance');
+
+        $options = array();
+        for ($i = 1; $i <= 100; $i++) {
+            $options[$i] = "$i%";
+        }
+        $mform->addElement('select', 'warningpercent', get_string('warningpercent', 'mod_attendance'), $options);
+        $mform->addHelpButton('warningpercent', 'warningpercent', 'mod_attendance');
+        $mform->setType('warningpercent', PARAM_INT);
+        $mform->setDefault('warningpercent', $config->warningpercent);
+
+        $options = array();
+        for ($i = 1; $i <= 50; $i++) {
+            $options[$i] = "$i";
+        }
+        $mform->addElement('select', 'warnafter', get_string('warnafter', 'mod_attendance'), $options);
+        $mform->addHelpButton('warnafter', 'warnafter', 'mod_attendance');
+        $mform->setType('warnafter', PARAM_INT);
+        $mform->setDefault('warnafter', $config->warnafter);
+
+        $mform->addElement('checkbox', 'emailuser', get_string('emailuser', 'mod_attendance'));
+        $mform->addHelpButton('emailuser', 'emailuser', 'mod_attendance');
+        $mform->setDefault('emailuser', $config->emailuser);
+
+        $mform->addElement('text', 'emailsubject', get_string('emailsubject', 'mod_attendance'), array('size' => '64'));
+        $mform->setType('emailsubject', PARAM_TEXT);
+        $mform->addRule('emailsubject', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+        $mform->addHelpButton('emailsubject', 'emailsubject', 'mod_attendance');
+        $mform->setDefault('emailsubject', $config->emailsubject);
+
+        $mform->addElement('editor', 'emailcontent', get_string('emailcontent', 'mod_attendance'), null, null);
+        $mform->setDefault('emailcontent', array('text' => format_text($config->emailcontent)));
+        $mform->setType('emailcontent', PARAM_RAW);
+        $mform->addHelpButton('emailcontent', 'emailcontent', 'mod_attendance');
+
+        $users = get_users_by_capability(context_course::instance($COURSE->id), 'mod/attendance:viewreports');
+        $options = array();
+        foreach ($users as $user) {
+            $options[$user->id] = fullname($user);
+        }
+
+        $select = $mform->addElement('searchableselector', 'thirdpartyemails',
+            get_string('thirdpartyemails', 'mod_attendance'), $options);
+        $mform->setType('thirdpartyemails', PARAM_TEXT);
+        $mform->addHelpButton('thirdpartyemails', 'thirdpartyemails', 'mod_attendance');
+        $select->setMultiple(true);
+
+        // Need to set hidden elements when adding default options.
+        $mform->addElement('hidden', 'idnumber', 0); // Default options use 0 as the idnumber.
+        $mform->setType('idnumber', PARAM_INT);
+
+        $mform->addElement('hidden', 'notid', 0); // The id of warning record.
+        $mform->setType('notid', PARAM_INT);
+
+        $mform->addElement('hidden', 'id', $this->_customdata['id']); // The id of course module record if attendance level.
+        $mform->setType('id', PARAM_INT);
+
+        if (!empty($this->_customdata['notid'])) {
+            $btnstring = get_string('update', 'attendance');
+        } else {
+            $btnstring = get_string('add', 'attendance');
+        }
+        $this->add_action_buttons(true, $btnstring);
+
+    }
+}
\ No newline at end of file
index 5176f6c..abf04be 100644 (file)
@@ -369,6 +369,16 @@ class mod_attendance_structure {
     }
 
     /**
+     * Get url for report.
+     * @param array $params
+     * @return moodle_url of report.php for attendance instance
+     */
+    public function url_atrisk($params=array()) {
+        $params = array_merge(array('id' => $this->cm->id), $params);
+        return new moodle_url('/mod/attendance/atrisk.php', $params);
+    }
+
+    /**
      * Get url for export.
      *
      * @return moodle_url of export.php for attendance instance
@@ -393,6 +403,20 @@ class mod_attendance_structure {
     }
 
     /**
+     * Get preferences url
+     * @param array $params
+     * @return moodle_url of attsettings.php for attendance instance
+     */
+    public function url_warnings($params=array()) {
+        // Add the statusset params.
+        if (isset($this->pageparams->statusset) && !isset($params['statusset'])) {
+            $params['statusset'] = $this->pageparams->statusset;
+        }
+        $params = array_merge(array('id' => $this->cm->id), $params);
+        return new moodle_url('/mod/attendance/warnings.php', $params);
+    }
+
+    /**
      * Get take url.
      * @param array $params
      * @return moodle_url of attendances.php for attendance instance
diff --git a/classes/task/notify.php b/classes/task/notify.php
new file mode 100644 (file)
index 0000000..01531b1
--- /dev/null
@@ -0,0 +1,122 @@
+<?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/>.
+
+/**
+ * Attendance task - Send warnings.
+ *
+ * @package    mod_attendance
+ * @copyright  2017 onwards Dan Marsden http://danmarsden.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_attendance\task;
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/attendance/locallib.php');
+/**
+ * Task class
+ *
+ * @package    mod_attendance
+ * @copyright  2017 onwards Dan Marsden http://danmarsden.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class notify extends \core\task\scheduled_task {
+    public function get_name() {
+        // Shown in admin screens.
+        return get_string('notifytask', 'mod_attendance');
+    }
+    public function execute() {
+        global $DB;
+        if (empty(get_config('attendance', 'enablewarnings'))) {
+            return; // Warnings not enabled.
+        }
+        $now = time(); // Store current time to use in queries so they all match nicely.
+        $lastrun = get_config('mod_attendance', 'notifylastrun');
+        if (empty($lastrun)) {
+            $lastrun = 0;
+        }
+
+        $orderby = 'ORDER BY cm.id, atl.studentid, n.warningpercent ASC';
+        $records = attendance_get_users_to_notify(array(), $orderby, $lastrun, true);
+        $sentnotifications = array();
+        $thirdpartynotifications = array();
+        $numsentusers = 0;
+        $numsentthird = 0;
+        foreach ($records as $record) {
+            if (empty($sentnotifications[$record->userid])) {
+                $sentnotifications[$record->userid] = array();
+            }
+
+            if (!empty($record->emailuser)) {
+                // Only send one warning to this user from each attendance in this run. - flag any higher percent notifications as sent.
+                if (empty($sentnotifications[$record->userid]) || !in_array($record->aid, $sentnotifications[$record->userid])) {
+                    // Convert variables in emailcontent.
+                    $record = attendance_template_variables($record);
+                    $user = $DB->get_record('user', array('id' => $record->userid));
+                    $from = \core_user::get_noreply_user();
+
+                    $emailcontent = format_text($record->emailcontent, $record->emailcontentformat);
+
+                    email_to_user($user, $from, $record->emailsubject, $emailcontent, $emailcontent);
+
+                    $sentnotifications[$record->userid][] = $record->aid;
+                    $numsentusers++;
+                }
+            }
+            // Only send one warning to this user from each attendance in this run. - flag any higher percent notifications as sent.
+            if (!empty($record->thirdpartyemails)) {
+                $sendto = explode(',', $record->thirdpartyemails);
+                $record->percent = round($record->percent * 100)."%";
+                foreach ($sendto as $senduser) {
+                    if (empty($thirdpartynotifications[$senduser])) {
+                        $thirdpartynotifications[$senduser] = array();
+                    }
+                    if (!isset($thirdpartynotifications[$senduser][$record->aid.'_'.$record->userid])) {
+                        $thirdpartynotifications[$senduser][$record->aid.'_'.$record->userid] = get_string('thirdpartyemailtext', 'attendance', $record);
+                    }
+                }
+            }
+
+            $notify = new \stdClass();
+            $notify->userid = $record->userid;
+            $notify->notifyid = $record->notifyid;
+            $notify->timesent = $now;
+            $DB->insert_record('attendance_warning_done', $notify);
+        }
+        if (!empty($numsentusers)) {
+            mtrace($numsentusers ." user emails sent");
+        }
+        if (!empty($thirdpartynotifications)) {
+            foreach ($thirdpartynotifications as $sendid => $notifications) {
+                $user = $DB->get_record('user', array('id' => $sendid));
+                $from = \core_user::get_noreply_user();
+
+                $emailcontent = implode("\n", $notifications);
+                $emailcontent .= "\n\n".get_string('thirdpartyemailtextfooter', 'attendance');
+                $emailcontent = format_text($emailcontent);
+                $emailsubject = get_string('thirdpartyemailsubject', 'attendance');
+
+                email_to_user($user, $from, $emailsubject, $emailcontent, $emailcontent);
+                $numsentthird++;
+            }
+            if (!empty($numsentthird)) {
+                mtrace($numsentthird ." thirdparty emails sent");
+            }
+        }
+
+        set_config('notifylastrun', $now, 'mod_attendance');
+    }
+}
\ No newline at end of file
index f1900bb..b310deb 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/attendance/db" VERSION="20170511" COMMENT="XMLDB file for Moodle mod/attendance"
+<XMLDB PATH="mod/attendance/db" VERSION="20170620" COMMENT="XMLDB file for Moodle mod/attendance"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
         <INDEX NAME="studentid" UNIQUE="true" FIELDS="studentid"/>
       </INDEXES>
     </TABLE>
+    <TABLE NAME="attendance_warning" COMMENT="Warning configuration">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="idnumber" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="attendance id or other id relating to this warning."/>
+        <FIELD NAME="warningpercent" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Percentage that triggers this warning."/>
+        <FIELD NAME="warnafter" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Start warning after this number of taken sessions."/>
+        <FIELD NAME="emailuser" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="Should the user be notified at this level."/>
+        <FIELD NAME="emailsubject" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Email subject line for emails going to user"/>
+        <FIELD NAME="emailcontent" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The html-formatted text that should be sent to the user"/>
+        <FIELD NAME="emailcontentformat" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="Format of the emailcontent field"/>
+        <FIELD NAME="thirdpartyemails" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="list of extra users to receive warnings"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="level_id" TYPE="unique" FIELDS="idnumber, warningpercent"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="attendance_warning_done" COMMENT="Warnings processed">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="notifyid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="id of warning"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="user id of user"/>
+        <FIELD NAME="timesent" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time warning sent to user."/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="notifyid_userid" UNIQUE="true" FIELDS="notifyid, userid"/>
+      </INDEXES>
+    </TABLE>
   </TABLES>
-</XMLDB>
+</XMLDB>
\ No newline at end of file
index 0abad70..361f9f3 100644 (file)
@@ -32,5 +32,13 @@ $tasks = array(
         'hour' => '*',
         'day' => '*',
         'dayofweek' => '*',
+        'month' => '*'),
+    array(
+        'classname' => 'mod_attendance\task\notify',
+        'blocking' => 0,
+        'minute' => '30',
+        'hour' => '1',
+        'day' => '*',
+        'dayofweek' => '*',
         'month' => '*')
 );
\ No newline at end of file
index fb1bb3a..31a4a63 100644 (file)
@@ -331,5 +331,56 @@ function xmldb_attendance_upgrade($oldversion=0) {
 
         upgrade_mod_savepoint(true, 2016121314, 'attendance');
     }
+
+    // Add new warning table.
+    if ($oldversion < 2016121315) {
+
+        // Define table attendance_warning to be created.
+        $table = new xmldb_table('attendance_warning');
+
+        // Adding fields to table attendance_warning.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('idnumber', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('warningpercent', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('warnafter', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('emailuser', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('emailsubject', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('emailcontent', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+        $table->add_field('emailcontentformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('thirdpartyemails', XMLDB_TYPE_TEXT, null, null, null, null, null);
+
+        // Adding keys to table attendance_warning.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('level_id', XMLDB_KEY_UNIQUE, array('idnumber, warningpercent'));
+
+        // Conditionally launch create table for attendance_warning.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table attendance_warning_done to be created.
+        $table = new xmldb_table('attendance_warning_done');
+
+        // Adding fields to table attendance_warning_done.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('notifyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timesent', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table attendance_warning_done.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        // Adding indexes to table attendance_warning_done.
+        $table->add_index('notifyid_userid', XMLDB_INDEX_UNIQUE, array('notifyid', 'userid'));
+
+        // Conditionally launch create table for attendance_warning_done.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Attendance savepoint reached.
+        upgrade_mod_savepoint(true, 2016121315, 'attendance');
+    }
+
     return $result;
 }
index 7669c46..a69e975 100644 (file)
@@ -33,12 +33,14 @@ $string['Pfull'] = 'Present';
 $string['acronym'] = 'Acronym';
 $string['add'] = 'Add';
 $string['addmultiplesessions'] = 'Multiple sessions';
+$string['addwarning'] = 'Add warning';
 $string['addsession'] = 'Add session';
 $string['adduser'] = 'Add user';
 $string['all'] = 'All';
 $string['allcourses'] = 'All courses';
 $string['allpast'] = 'All past';
 $string['allsessions'] = 'All sessions';
+$string['atriskreport'] = 'At-risk report';
 $string['attendance:addinstance'] = 'Add a new attendance activity';
 $string['attendance:canbelisted'] = 'Appears in the roster';
 $string['attendance:changeattendances'] = 'Changing Attendances';
@@ -104,6 +106,9 @@ The sessions begin on the date of the base session and continue until the \'repe
 $string['createonesession'] = 'Create one session for the course';
 $string['days'] = 'Days';
 $string['defaultdisplaymode'] = 'Default display mode';
+$string['defaultwarnings'] = 'Default warning set';
+$string['defaultwarningsettings'] = 'Default warning settings';
+$string['defaultwarningsettings_help'] = 'These settings define the defaults for all new warnings';
 $string['defaults'] = 'Defaults';
 $string['defaultsessionsettings'] = 'Default session settings';
 $string['defaultsessionsettings_help'] = 'These settings define the defaults for all new sessions';
@@ -115,6 +120,7 @@ $string['defaultsubnet_help'] = 'Attendance recording may be restricted to parti
 $string['defaultview'] = 'Default view on login';
 $string['defaultview_desc'] = 'This is the default view shown to teachers on first login.';
 $string['delete'] = 'Delete';
+$string['deletewarningconfirm'] = 'Are you sure you want to delete this warning?';
 $string['deletedgroup'] = 'The group associated with this session has been deleted';
 $string['deletehiddensessions'] = 'Delete all hidden sessions';
 $string['deletelogs'] = 'Delete attendance data';
@@ -134,8 +140,35 @@ $string['downloadtext'] = 'Download in text format';
 $string['duration'] = 'Duration';
 $string['editsession'] = 'Edit Session';
 $string['edituser'] = 'Edit user';
+$string['emailcontent_default'] = 'Hi %userfirstname%,
+Your attendance in %coursename% %attendancename% has dropped below %warningpercent% and is currently %percent% - we hope you are ok!
+
+To get the most out of this course you should improve your attendance, please get in touch if you require any further support.';
+$string['emailcontent'] = 'Email content';
+$string['emailcontent_help'] = 'When a warning is sent to a student, it takes the email content from this field. The following wildcards can be used:
+<ul>
+<li>%coursename%</li>
+<li>%userfirstname%</li>
+<li>%userlastname%</li>
+<li>%userid%</li>
+<li>%warningpercent%</li>
+<li>%attendancename%</li>
+<li>%cmid%</li>
+<li>%numtakensessions%</li>
+<li>%points%</li>
+<li>%maxpoints%</li>
+<li>%precent%</li>
+</ul>';
+
+$string['emailsubject'] = 'Email subject';
+$string['emailsubject_help'] = 'When a warning is sent to a student, it takes the email subject from this field.';
+$string['emailsubject_default'] = 'Attendance warning';
+$string['emailuser'] = 'Email user';
+$string['emailuser_help'] = 'If checked, a warning will be sent to the student.';
 $string['emptyacronym'] = 'Empty acronyms are not allowed. Status record not updated.';
 $string['emptydescription'] = 'Empty descriptions are not allowed. Status record not updated.';
+$string['enablewarnings'] = 'Enable warnings';
+$string['enablewarnings_desc'] = 'This allows a warning set to be defined for an attendance and email notifications to users when attendance drops below the configured threshold. <br/><strong>WARNING: This is a new feature and has not been tested extensively. Please use at your own-risk and provide feeback in the moodle forums if you find it works well.</strong>';
 $string['endofperiod'] = 'End of period';
 $string['endtime'] = 'Session end time';
 $string['enrolmentend'] = 'User enrolment ends {$a}';
@@ -175,6 +208,7 @@ $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['indetail'] = 'In detail...';
 $string['invalidaction'] = 'You must select an action';
+$string['invalidemails'] = 'You must specify addresses of existing user accounts, could not find: {$a}';
 $string['invalidsessionenddate'] = 'This date can not be earlier than the session date';
 $string['invalidsessionendtime'] = 'The end time must be greater than start time';
 $string['invalidstatus'] = 'You have selected an invalid status, please try again';
@@ -217,9 +251,15 @@ $string['noofdayspresent'] = 'No of days present';
 $string['nosessiondayselected'] = 'No Session day selected';
 $string['nosessionexists'] = 'No Session exists for this course';
 $string['nosessionsselected'] = 'No sessions selected';
+$string['warningdeleted'] = 'Warning deleted';
+$string['warningdesc'] = 'These warnings will be automatically added to any new attendance activities. If more than one warning is triggered at exactly the same time, only the warning with the lower warning threshold will be sent.';
+$string['warnings'] = 'Warnings set';
+$string['warningupdated'] = 'Updated warnings';
+$string['notifytask'] = 'Send warnings to users';
 $string['notfound'] = 'Attendance activity not found in this course!';
 $string['notmember'] = 'not&nbsp;member';
 $string['noupgradefromthisversion'] = 'The Attendance module cannot upgrade from the version of attforblock you have installed. - please delete attforblock or upgrade it to the latest version before isntalling the new attendance module';
+$string['numsessions'] = 'Number of sessions';
 $string['olddate'] = 'Old date';
 $string['onlyselectedusers'] = 'Export specific users';
 $string['overallsessions'] = 'Over all sessions';
@@ -384,10 +424,16 @@ $string['tempusermerge'] = 'Merge temporary user';
 $string['tempusers'] = 'Temporary users';
 $string['tempusersedit'] = 'Edit temporary user';
 $string['tempuserslist'] = 'Temporary users';
+$string['thirdpartyemailsubject'] = 'Attendance warning';
+$string['thirdpartyemailtext'] = '{$a->firstname} {$a->lastname} attendance within {$a->coursename} {$a->aname} is lower than {$a->warningpercent} ({$a->percent})';
+$string['thirdpartyemailtextfooter'] = 'You are receiving this because the teacher of this course has added your email to the recipient’s list';
+$string['thirdpartyemails'] = 'Notify other users';
+$string['thirdpartyemails_help'] = 'List of other users who will be notified. (requires the capability mod/attendance:viewreports)';
 $string['thiscourse'] = 'This course';
 $string['time'] = 'Time';
 $string['timeahead'] = 'Multiple sessions that exceed one year cannot be created, please adjust the start and end dates.';
 $string['to'] = 'to:';
+$string['triggered'] = 'First triggered';
 $string['tuseremail'] = 'Email';
 $string['tusername'] = 'Full name';
 $string['unknowngroup'] = 'Unknown group';
@@ -400,6 +446,12 @@ $string['variable'] = 'variable';
 $string['variablesupdated'] = 'Variables successfully updated';
 $string['versionforprinting'] = 'version for printing';
 $string['viewmode'] = 'View mode';
+$string['warnafter'] = 'Number of sessions taken before warning';
+$string['warnafter_help'] = 'Warnings will only be triggered when the user has had their attendance taken for at least this number of sessions.';
+$string['warningfailed'] = 'You cannot create a warning that uses the same percentage and number of sessions.';
+$string['warningpercent'] = 'Warn if percentage falls under';
+$string['warningpercent_help'] = 'A warning will be triggered when the overall percentage falls below this number.';
+$string['warningthreshold'] = 'Warning threshold';
 $string['week'] = 'week(s)';
 $string['weeks'] = 'Weeks';
 $string['youcantdo'] = 'You can\'t do anything';
diff --git a/lib.php b/lib.php
index 61e6acb..429ef0d 100644 (file)
--- a/lib.php
+++ b/lib.php
@@ -70,6 +70,25 @@ function att_add_default_statuses($attid) {
 }
 
 /**
+ * Add default set of warnings to the new attendance.
+ *
+ * @param int $attid - id of attendance instance.
+ */
+function attendance_add_default_warnings($cmid) {
+    global $DB, $CFG;
+    require_once($CFG->dirroot.'/mod/attendance/locallib.php');
+
+    $warnings = $DB->get_recordset('attendance_warning',
+        array('idnumber' => 0), 'id');
+    foreach ($warnings as $n) {
+        $rec = $n;
+        $rec->idnumber = $cmid;
+        $DB->insert_record('attendance_warning', $rec);
+    }
+    $warnings->close();
+}
+
+/**
  * Add new attendance instance.
  *
  * @param stdClass $attendance
@@ -84,6 +103,8 @@ function attendance_add_instance($attendance) {
 
     att_add_default_statuses($attendance->id);
 
+    attendance_add_default_warnings($attendance->coursemodule);
+
     attendance_grade_item_update($attendance);
 
     return $attendance->id;
@@ -117,7 +138,8 @@ function attendance_update_instance($attendance) {
  * @return bool
  */
 function attendance_delete_instance($id) {
-    global $DB;
+    global $DB, $CFG;
+    require_once($CFG->dirroot.'/mod/attendance/locallib.php');
 
     if (! $attendance = $DB->get_record('attendance', array('id' => $id))) {
         return false;
@@ -132,6 +154,8 @@ function attendance_delete_instance($id) {
     }
     $DB->delete_records('attendance_statuses', array('attendanceid' => $id));
 
+    $DB->delete_records('attendance_warning', array('idnumber' => $id));
+
     $DB->delete_records('attendance', array('id' => $id));
 
     attendance_grade_item_delete($attendance);
@@ -450,9 +474,18 @@ function attendance_print_settings_tabs($selected = 'settings') {
     $tabs[] = new tabobject('defaultstatus', $CFG->wwwroot.'/mod/attendance/defaultstatus.php',
         get_string('defaultstatus', 'attendance'), get_string('defaultstatus', 'attendance'), false);
 
+    if (get_config('attendance', 'enablewarnings')) {
+        $tabs[] = new tabobject('defaultwarnings', $CFG->wwwroot . '/mod/attendance/warnings.php',
+            get_string('defaultwarnings', 'attendance'), get_string('defaultwarnings', 'attendance'), false);
+    }
+
     $tabs[] = new tabobject('coursesummary', $CFG->wwwroot.'/mod/attendance/coursesummary.php',
         get_string('coursesummary', 'attendance'), get_string('coursesummary', 'attendance'), false);
 
+    if (get_config('attendance', 'enablewarnings')) {
+        $tabs[] = new tabobject('atrisk', $CFG->wwwroot . '/mod/attendance/atrisk.php',
+            get_string('atriskreport', 'attendance'), get_string('atriskreport', 'attendance'), false);
+    }
     ob_start();
     print_tabs(array($tabs), $selected);
     $tabmenu = ob_get_contents();
index 397faee..b540213 100644 (file)
@@ -42,6 +42,7 @@ define('ATT_SORT_FIRSTNAME', 2);
 define('ATTENDANCE_AUTOMARK_DISABLED', 0);
 define('ATTENDANCE_AUTOMARK_ALL', 1);
 define('ATTENDANCE_AUTOMARK_CLOSE', 2);
+
 /**
  * Get statuses,
  *
@@ -709,4 +710,114 @@ SELECT a.id, a.course as courseid, c.fullname as coursename, atl.studentid AS us
                 ) p GROUP by courseid, coursename {$orderby}";
 
     return $DB->get_records_sql($sql, $params);
+}
+
+/**
+ * Generates a list of users flagged at-risk.
+ *
+ * @param array $courseids optional list of courses to return
+ * @param array $sincetime optional allows a list to be calculated for cron processing.
+ * @param bool $allfornotify get notification list for scheduled task.
+ * @return stdClass
+ */
+function attendance_get_users_to_notify($courseids = array(), $orderby = '', $sincetime = 0, $allfornotify = false) {
+    global $DB;
+
+    $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
+    $where = ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
+    $params = array();
+
+    if (!empty($courseids)) {
+        list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+        $where .= ' AND c.id ' . $insql;
+        $params = array_merge($params, $inparams);
+    }
+    if ($allfornotify) {
+        // Exclude warnings that have already been sent.
+        $where .= ' AND ns.id IS NULL ';
+    }
+
+    $unames = get_all_user_name_fields(true);
+    $unames2 = get_all_user_name_fields(true, 'u');
+
+    $idfield = $DB->sql_concat('cm.id', 'atl.studentid', 'n.id');
+    $sql = "SELECT {$idfield} as uniqueid, a.id as aid, {$unames2}, a.name as aname, cm.id as cmid, c.id as courseid,
+                    c.fullname as coursename, atl.studentid AS userid, n.id as notifyid, n.warningpercent, n.emailsubject,
+                    n.emailcontent, n.emailcontentformat, n.emailuser, n.thirdpartyemails, ns.timesent, n.warnafter,
+                     COUNT(DISTINCT ats.id) AS numtakensessions, SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints,
+                      SUM(stg.grade) / SUM(stm.maxgrade) AS percent
+                   FROM {attendance_sessions} ats
+                   JOIN {attendance} a ON a.id = ats.attendanceid
+                   JOIN {course_modules} cm ON cm.instance = a.id
+                   JOIN {course} c on c.id = cm.course
+                   JOIN {modules} md ON md.id = cm.module AND md.name = 'attendance'
+                   JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
+                   JOIN {user} u ON (u.id = atl.studentid)
+                   JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
+                   JOIN {attendance_warning} n ON n.idnumber = cm.id
+                   LEFT JOIN {attendance_warning_done} ns ON ns.notifyid = n.id AND ns.userid = atl.studentid
+                   JOIN (SELECT attendanceid, setnumber, MAX(grade) AS maxgrade
+                           FROM {attendance_statuses}
+                          WHERE deleted = 0
+                            AND visible = 1
+                         GROUP BY attendanceid, setnumber) stm
+                     ON (stm.setnumber = ats.statusset AND stm.attendanceid = ats.attendanceid)
+                  {$joingroup}
+                  WHERE ats.sessdate >= {$sincetime} {$where}
+                    AND ats.lasttaken != 0
+                GROUP BY uniqueid, a.id, a.name, a.course, c.fullname, atl.studentid, n.id, n.warningpercent,
+                         n.emailsubject, n.emailcontent, n.emailcontentformat, n.warnafter,
+                         n.emailuser, n.thirdpartyemails, ns.timesent, cm.id, c.id, {$unames2}
+                HAVING n.warnafter <= COUNT(DISTINCT ats.id) AND n.warningpercent > ((SUM(stg.grade) / SUM(stm.maxgrade)) * 100)
+                      {$orderby}";
+
+    if (!$allfornotify) {
+        $idfield = $DB->sql_concat('cmid', 'userid');
+        // Only show one record per attendance for teacher reports.
+        $sql = "SELECT {$idfield} as id, {$unames}, aid, cmid, courseid, aname, coursename, userid, MIN(warningpercent),
+                        numtakensessions, points, maxpoints, percent, timesent
+              FROM ({$sql}) as m
+         GROUP BY id, aid, cmid, courseid, aname, userid, numtakensessions, points, maxpoints,
+                  percent, coursename, timesent, {$unames} {$orderby}";
+    }
+
+    return $DB->get_records_sql($sql, $params);
+
+}
+
+/**
+ * Template variables into place in supplied email content.
+ *
+ * @param object $record db record of details
+ * @return array - the content of the fields after templating.
+ */
+function attendance_template_variables($record) {
+    $templatevars = array(
+        '/%coursename%/' => $record->coursename,
+        '/%courseid%/' => $record->courseid,
+        '/%userfirstname%/' => $record->firstname,
+        '/%userlastname%/' => $record->lastname,
+        '/%userid%/' => $record->userid,
+        '/%warningpercent%/' => $record->warningpercent,
+        '/%attendancename%/' => $record->aname,
+        '/%cmid%/' => $record->cmid,
+        '/%numtakensessions%/' => $record->numtakensessions,
+        '/%points%/' => $record->points,
+        '/%maxpoints%/' => $record->maxpoints,
+        '/%precent%/' => $record->percent,
+    );
+    $extrauserfields = get_all_user_name_fields();
+    foreach ($extrauserfields as $extra) {
+        $templatevars['/%'.$extra.'%/'] = $record->$extra;
+    }
+    $patterns = array_keys($templatevars); // The placeholders which are to be replaced.
+    $replacements = array_values($templatevars); // The values which are to be templated in for the placeholders.
+    // Array to describe which fields in reengagement object should have a template replacement.
+    $replacementfields = array('emailsubject', 'emailcontent');
+
+    // Replace %variable% with relevant value everywhere it occurs in reengagement->field.
+    foreach ($replacementfields as $field) {
+        $record->$field = preg_replace($patterns, $replacements, $record->$field);
+    }
+    return $record;
 }
\ No newline at end of file
index a73c98a..0f2d483 100644 (file)
@@ -51,7 +51,10 @@ class attendance_tabs implements renderable {
     const TAB_TEMPORARYUSERS = 6; // Tab for managing temporary users.
     /** Update tab */
     const TAB_UPDATE        = 7;
-
+    /** Warnings tab */
+    const TAB_WARNINGS = 8;
+    /** At-risk tab */
+    const TAB_ATRISK        = 9;
     /** @var int current tab */
     public $currenttab;
 
@@ -97,6 +100,12 @@ class attendance_tabs implements renderable {
                             get_string('report', 'attendance'));
         }
 
+        if (has_capability('mod/attendance:viewreports', $context) &&
+            get_config('attendance', 'enablewarnings')) {
+            $toprow[] = new tabobject(self::TAB_ATRISK, $this->att->url_atrisk()->out(),
+                get_string('atriskreport', 'attendance'));
+        }
+
         if (has_capability('mod/attendance:export', $context)) {
             $toprow[] = new tabobject(self::TAB_EXPORT, $this->att->url_export()->out(),
                             get_string('export', 'attendance'));
@@ -105,6 +114,11 @@ class attendance_tabs implements renderable {
         if (has_capability('mod/attendance:changepreferences', $context)) {
             $toprow[] = new tabobject(self::TAB_PREFERENCES, $this->att->url_preferences()->out(),
                             get_string('statussetsettings', 'attendance'));
+
+            if (get_config('attendance', 'enablewarnings')) {
+                $toprow[] = new tabobject(self::TAB_WARNINGS, $this->att->url_warnings()->out(),
+                    get_string('warnings', 'attendance'));
+            }
         }
         if (has_capability('mod/attendance:managetemporaryusers', $context)) {
             $toprow[] = new tabobject(self::TAB_TEMPORARYUSERS, $this->att->url_managetemp()->out(),
index 120a32b..e99e3d2 100644 (file)
@@ -74,6 +74,10 @@ if ($ADMIN->fulltree) {
         get_string('defaultview', 'attendance'),
             get_string('defaultview_desc', 'attendance'), ATT_VIEW_WEEKS, $options));
 
+    $settings->add(new admin_setting_configcheckbox('attendance/enablewarnings',
+        get_string('enablewarnings', 'attendance'),
+        get_string('enablewarnings_desc', 'attendance'), 0));
+
     $name = new lang_string('defaultsettings', 'mod_attendance');
     $description = new lang_string('defaultsettings_help', 'mod_attendance');
     $settings->add(new admin_setting_heading('defaultsettings', $name, $description));
@@ -98,4 +102,35 @@ if ($ADMIN->fulltree) {
 
     $settings->add(new admin_setting_configcheckbox('attendance/randompassword_default',
         get_string('randompassword', 'attendance'), '', 0));
+
+    $name = new lang_string('defaultwarningsettings', 'mod_attendance');
+    $description = new lang_string('defaultwarningsettings_help', 'mod_attendance');
+    $settings->add(new admin_setting_heading('defaultwarningsettings', $name, $description));
+
+    $options = array();
+    for ($i = 1; $i <= 100; $i++) {
+        $options[$i] = "$i%";
+    }
+    $settings->add(new admin_setting_configselect('attendance/warningpercent',
+        get_string('warningpercent', 'attendance'), get_string('warningpercent_help', 'attendance'), 70, $options));
+
+    $options = array();
+    for ($i = 1; $i <= 50; $i++) {
+        $options[$i] = "$i";
+    }
+    $settings->add(new admin_setting_configselect('attendance/warnafter',
+        get_string('warnafter', 'attendance'), get_string('warnafter_help', 'attendance'), 5, $options));
+
+    $settings->add(new admin_setting_configcheckbox('attendance/emailuser',
+        get_string('emailuser', 'attendance'), get_string('emailuser_help', 'attendance'), 1));
+
+    $settings->add(new admin_setting_configtext('attendance/emailsubject',
+        get_string('emailsubject', 'attendance'), get_string('emailsubject_help', 'attendance'),
+        get_string('emailsubject_default', 'attendance'), PARAM_RAW));
+
+
+    $settings->add(new admin_setting_configtextarea('attendance/emailcontent',
+        get_string('emailcontent', 'attendance'), get_string('emailcontent_help', 'attendance'),
+        get_string('emailcontent_default', 'attendance'), PARAM_RAW));
+
 }
index 2a5bc70..da8fd61 100644 (file)
@@ -1,5 +1,6 @@
 @javascript @mod @uon @mod_attendance
 Feature: Visiting reports
+<<<<<<< HEAD
     As a teacher I visit the reports
 
     Background:
@@ -239,4 +240,254 @@ Feature: Visiting reports
          And "5 / 6" "text" should exist in the "Maximum possible points:" "table_row"
          And "83.3%" "text" should exist in the "Maximum possible percentage:" "table_row"
 
-         And I log out
\ No newline at end of file
+         And I log out
+=======
+  As a teacher I visit the reports
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | summary                             | category | timecreated   | timemodified  |
+      | Course 1 | C1        | Prove the attendance activity works | 0        | ##yesterday## | ##yesterday## |
+    And the following "users" exist:
+      | username    | firstname | lastname | email            | idnumber | department       | institution |
+      | student1    | Student   | 1  | student1@asd.com | 1234     | computer science | University of Nottingham |
+      | teacher1    | Teacher   | 1  | teacher1@asd.com | 5678     | computer science | University of Nottingham |
+    And the following "course enrolments" exist:
+      | course | user     | role           | timestart     |
+      | C1     | student1 | student        | ##yesterday## |
+      | C1     | teacher1 | editingteacher | ##yesterday## |
+    And the following config values are set as admin:
+      | enablewarnings | 1 | attendance |
+
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I add a "Attendance" to section "1" and I fill the form with:
+      | Name        | Attendance       |
+    And I follow "Attendance"
+    And I follow "Add a block"
+    And I follow "Administration"
+    And I follow "Add session"
+    And I set the following fields to these values:
+      | id_sestime_starthour | 01 |
+      | id_sestime_endhour   | 02 |
+    And I click on "id_submitbutton" "button"
+    And I follow "Warnings set"
+    And I press "Add warning"
+    And I set the following fields to these values:
+      | id_warningpercent | 84 |
+      | id_warnafter   | 2 |
+    And I click on "id_submitbutton" "button"
+    And I log out
+
+  Scenario: Teacher takes attendance
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "Attendance"
+    And I follow "Edit settings"
+    Then I set the following fields to these values:
+      | id_grade_modgrade_type  | Point |
+      | id_grade_modgrade_point | 50   |
+    And I press "Save and display"
+
+    When I follow "Report"
+    Then "0 / 0" "text" should exist in the "Student 1" "table_row"
+    And "0.0%" "text" should exist in the "Student 1" "table_row"
+
+    When I follow "Grades" in the user menu
+    And I follow "Course 1"
+    And "-" "text" should exist in the "Student 1" "table_row"
+
+    When I follow "Attendance"
+    Then I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
+    # Late
+    And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Report"
+    Then "1 / 2" "text" should exist in the "Student 1" "table_row"
+    And "50.0%" "text" should exist in the "Student 1" "table_row"
+
+    When I follow "Grades" in the user menu
+    And I follow "Course 1"
+    And "25.00" "text" should exist in the "Student 1" "table_row"
+
+    And I log out
+
+  Scenario: Teacher changes the maximum points in the attendance settings
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "Attendance"
+    And I follow "Edit settings"
+    Then I set the following fields to these values:
+      | id_grade_modgrade_type  | Point |
+      | id_grade_modgrade_point | 50   |
+    And I press "Save and display"
+
+    When I follow "Attendance"
+    Then I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
+    # Excused
+    And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Attendance"
+    And I follow "Edit settings"
+    Then I set the following fields to these values:
+      | id_grade_modgrade_type  | Point |
+      | id_grade_modgrade_point | 70   |
+    And I press "Save and display"
+
+    When I follow "Report"
+    Then "1 / 2" "text" should exist in the "Student 1" "table_row"
+    And "50.0%" "text" should exist in the "Student 1" "table_row"
+
+    When I follow "Grades" in the user menu
+    And I follow "Course 1"
+    Then "35.00" "text" should exist in the "Student 1" "table_row"
+    And I log out
+
+  Scenario: Teacher take attendance of group session
+    Given the following "groups" exist:
+      | course | name   | idnumber |
+      | C1     | Group1 | Group1   |
+    And the following "group members" exist:
+      | group  | user     |
+      | Group1 | student1 |
+
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "Attendance"
+    And I follow "Edit settings"
+    And I set the following fields to these values:
+      | id_grade_modgrade_type  | Point |
+      | id_grade_modgrade_point | 50   |
+      | id_groupmode            | Visible groups |
+    And I press "Save and display"
+
+    When I follow "Attendance"
+    Then I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
+    # Excused
+    And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Add session"
+    And I set the following fields to these values:
+      | id_sestime_starthour | 03 |
+      | id_sestime_endhour   | 04 |
+      | id_sessiontype_1     | 1  |
+      | id_groups            | Group1 |
+    And I click on "id_submitbutton" "button"
+    Then I should see "3AM - 4AM"
+    And "Group: Group1" "text" should exist in the "3AM - 4AM" "table_row"
+
+    When I click on "Take attendance" "link" in the "3AM - 4AM" "table_row"
+    # Present
+    And I click on "td.cell.c3 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Report"
+    Then "3 / 4" "text" should exist in the "Student 1" "table_row"
+    And "75.0%" "text" should exist in the "Student 1" "table_row"
+
+    When I follow "Grades" in the user menu
+    And I follow "Course 1"
+    Then "37.50" "text" should exist in the "Student 1" "table_row"
+
+    And I log out
+
+  Scenario: Teacher visit summary report and at-risk report
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "Attendance"
+    And I follow "Edit settings"
+    And I set the following fields to these values:
+       | id_grade_modgrade_type  | Point |
+       | id_grade_modgrade_point | 50   |
+    And I press "Save and display"
+
+    When I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
+    # Late
+    And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Add session"
+    And I set the following fields to these values:
+      | id_sestime_starthour | 03 |
+      | id_sestime_endhour   | 04 |
+    And I click on "id_submitbutton" "button"
+    Then I should see "3AM - 4AM"
+
+    When I click on "Take attendance" "link" in the "3AM - 4AM" "table_row"
+    # Present
+    And I click on "td.cell.c3 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Add session"
+    And I set the following fields to these values:
+      | id_sestime_starthour | 05 |
+      | id_sestime_endhour   | 06 |
+    And I click on "id_submitbutton" "button"
+    Then I should see "5AM - 6AM"
+
+    When I follow "Report"
+    And I click on "Summary" "link" in the "All" "table_row"
+
+    Then "3 / 6" "text" should exist in the "Student 1" "table_row"
+    And "50.0%" "text" should exist in the "Student 1" "table_row"
+    And "5 / 6" "text" should exist in the "Student 1" "table_row"
+    And "83.3%" "text" should exist in the "Student 1" "table_row"
+
+    And I follow "At-risk report"
+    And I should see "Student 1"
+
+    And I log out
+
+  Scenario: Student visit user report
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "Attendance"
+    And I follow "Edit settings"
+    Then I set the following fields to these values:
+      | id_grade_modgrade_type  | Point |
+      | id_grade_modgrade_point | 50   |
+    And I press "Save and display"
+
+    When I click on "Take attendance" "link" in the "1AM - 2AM" "table_row"
+    # Late
+    And I click on "td.cell.c4 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Add session"
+    And I set the following fields to these values:
+      | id_sestime_starthour | 03 |
+      | id_sestime_endhour   | 04 |
+    And I click on "id_submitbutton" "button"
+
+    When I click on "Take attendance" "link" in the "3AM - 4AM" "table_row"
+    # Present
+    And I click on "td.cell.c3 input" "css_element" in the "Student 1" "table_row"
+    And I press "Save attendance"
+
+    When I follow "Add session"
+    And I set the following fields to these values:
+      | id_sestime_starthour | 05 |
+      | id_sestime_endhour   | 06 |
+    And I click on "id_submitbutton" "button"
+
+    Then I log out
+
+    When I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I follow "Attendance"
+    And I click on "All" "link" in the ".attfiltercontrols" "css_element"
+
+    Then "2" "text" should exist in the "Taken sessions" "table_row"
+    And "3 / 4" "text" should exist in the "Points over taken sessions:" "table_row"
+    And "75.0%" "text" should exist in the "Percentage over taken sessions:" "table_row"
+    And "3" "text" should exist in the "Total number of sessions:" "table_row"
+    And "3 / 6" "text" should exist in the "Points over all sessions:" "table_row"
+    And "50.0%" "text" should exist in the "Percentage over all sessions:" "table_row"
+    And "5 / 6" "text" should exist in the "Maximum possible points:" "table_row"
+    And "83.3%" "text" should exist in the "Maximum possible percentage:" "table_row"
+
+    And I log out
+>>>>>>> 664dfe0... Merge notification/warning feature into master. (#267)
index 91527c8..a7af682 100644 (file)
@@ -23,9 +23,9 @@
  */
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version  = 2016121314;
+$plugin->version  = 2016121315;
 $plugin->requires = 2016111800;
-$plugin->release = '3.2.13';
+$plugin->release = '3.2.14';
 $plugin->maturity  = MATURITY_STABLE;
 $plugin->cron     = 0;
 $plugin->component = 'mod_attendance';
diff --git a/warnings.php b/warnings.php
new file mode 100644 (file)
index 0000000..d9cde6b
--- /dev/null
@@ -0,0 +1,199 @@
+<?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/>.
+
+/**
+ * Allows default warnings to be modified.
+ *
+ * @package   mod_attendance
+ * @copyright 2017 Dan Marsden http://danmarsden.com
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__.'/../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/formslib.php');
+require_once($CFG->dirroot.'/mod/attendance/lib.php');
+require_once($CFG->dirroot.'/mod/attendance/locallib.php');
+
+$action = optional_param('action', '', PARAM_ALPHA);
+$notid = optional_param('notid', 0, PARAM_INT);
+$id = optional_param('id', 0, PARAM_INT);
+
+$url = new moodle_url('/mod/attendance/warnings.php');
+
+// This page is used for configuring default set and for configuring attendance level set.
+if (empty($id)) {
+    // This is the default status set - show appropriate admin stuff and check admin permissions.
+    admin_externalpage_setup('managemodules');
+
+    $output = $PAGE->get_renderer('mod_attendance');
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('defaultwarnings', 'mod_attendance'));
+    $tabmenu = attendance_print_settings_tabs('defaultwarnings');
+    echo $tabmenu;
+
+} else {
+    // This is an attendance level config.
+    $cm             = get_coursemodule_from_id('attendance', $id, 0, false, MUST_EXIST);
+    $course         = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+    $att            = $DB->get_record('attendance', array('id' => $cm->instance), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $context = context_module::instance($cm->id);
+    require_capability('mod/attendance:changepreferences', $context);
+
+    $att = new mod_attendance_structure($att, $cm, $course, $PAGE->context);
+
+    $PAGE->set_url($url);
+    $PAGE->set_title($course->shortname. ": ".$att->name);
+    $PAGE->set_heading($course->fullname);
+    $PAGE->navbar->add($att->name);
+
+    $output = $PAGE->get_renderer('mod_attendance');
+    $tabs = new attendance_tabs($att, attendance_tabs::TAB_WARNINGS);
+    echo $output->header();
+    echo $output->heading(get_string('attendanceforthecourse', 'attendance').' :: ' .format_string($course->fullname));
+    echo $output->render($tabs);
+
+}
+
+$mform = new mod_attendance_add_warning_form($url, array('notid' => $notid, 'id' => $id));
+
+if ($data = $mform->get_data()) {
+    if (empty($data->notid)) {
+        // Insert new record.
+        $notify = new stdClass();
+        if (empty($id)) {
+            $notify->idnumber = 0;
+        } else {
+            $notify->idnumber = $cm->id;
+        }
+
+        $notify->warningpercent = $data->warningpercent;
+        $notify->warnafter = $data->warnafter;
+        $notify->emailuser = empty($data->emailuser) ? 0 : $data->emailuser;
+        $notify->emailsubject = $data->emailsubject;
+        $notify->emailcontent = $data->emailcontent['text'];
+        $notify->emailcontentformat = $data->emailcontent['format'];
+        $notify->thirdpartyemails = '';
+        if (!empty($data->thirdpartyemails)) {
+            $notify->thirdpartyemails = implode(',', $data->thirdpartyemails);
+        }
+        $existingrecord = $DB->record_exists('attendance_warning', array('idnumber' => $notify->idnumber,
+                                                                         'warningpercent' => $notify->warningpercent));
+        if (empty($existingrecord)) {
+            $DB->insert_record('attendance_warning', $notify);
+            echo $OUTPUT->notification(get_string('warningupdated', 'mod_attendance'), 'success');
+        } else {
+            echo $OUTPUT->notification(get_string('warningfailed', 'mod_attendance'), 'warning');
+        }
+
+    } else {
+        $notify = $DB->get_record('attendance_warning', array('id' => $data->notid));
+        if (!empty($id) && $data->idnumber != $id) {
+            // Someone is trying to update a record for a different attendance.
+            print_error('invalidcoursemodule');
+        } else {
+            $notify = new stdClass();
+            $notify->id = $data->notid;
+            $notify->idnumber = $data->idnumber;
+            $notify->warningpercent = $data->warningpercent;
+            $notify->warnafter = $data->warnafter;
+            $notify->emailuser = empty($data->emailuser) ? 0 : $data->emailuser;
+            $notify->emailsubject = $data->emailsubject;
+            $notify->emailcontentformat = $data->emailcontent['format'];
+            $notify->emailcontent = $data->emailcontent['text'];
+            $notify->thirdpartyemails = '';
+            if (!empty($data->thirdpartyemails)) {
+                $notify->thirdpartyemails = implode(',', $data->thirdpartyemails);
+            }
+            $existingrecord = $DB->get_record('attendance_warning', array('idnumber' => $notify->idnumber,
+                'warningpercent' => $notify->warningpercent));
+            if (empty($existingrecord) || $existingrecord->id == $notify->id) {
+                $DB->update_record('attendance_warning', $notify);
+                echo $OUTPUT->notification(get_string('warningupdated', 'mod_attendance'), 'success');
+            } else {
+                echo $OUTPUT->notification(get_string('warningfailed', 'mod_attendance'), 'error');
+            }
+        }
+    }
+}
+if ($action == 'delete' && !empty($notid)) {
+    if (!optional_param('confirm', false, PARAM_BOOL)) {
+        $cancelurl = $url;
+        $url->params(array('action' => 'delete', 'notid' => $notid, 'sesskey' => sesskey(), 'confirm' => true, 'id' => $id));
+        echo $OUTPUT->confirm(get_string('deletewarningconfirm', 'mod_attendance'), $url, $cancelurl);
+        echo $OUTPUT->footer();
+        exit;
+    } else {
+        require_sesskey();
+        $params = array('id' => $notid);
+        if (!empty($id)) {
+            // Add id/level to array.
+            $params['idnumber'] = $cm->id;
+        }
+        $DB->delete_records('attendance_warning', $params);
+        echo $OUTPUT->notification(get_string('warningdeleted', 'mod_attendance'), 'success');
+    }
+}
+if ($action == 'update' && !empty($notid)) {
+    $existing = $DB->get_record('attendance_warning', array('id' => $notid));
+    $content = $existing->emailcontent;
+    $existing->emailcontent = array();
+    $existing->emailcontent['text'] = $content;
+    $existing->emailcontent['format'] = $existing->emailcontentformat;
+    $existing->notid = $existing->id;
+    $existing->id = $id;
+    $mform->set_data($existing);
+    $mform->display();
+} else if ($action == 'add' && confirm_sesskey()) {
+    $mform->display();
+} else {
+    if (empty($id)) {
+        echo $OUTPUT->box(get_string('warningdesc', 'mod_attendance'), 'generalbox', 'notice');
+
+        $existingnotifications = $DB->get_records('attendance_warning',
+            array('idnumber' => 0),
+            'warningpercent');
+    } else {
+        $existingnotifications = $DB->get_records('attendance_warning',
+            array('idnumber' => $cm->id),
+            'warningpercent');
+    }
+    if (!empty($existingnotifications)) {
+        $table = new html_table();
+        $table->head = array(get_string('warningthreshold', 'mod_attendance'),
+            get_string('numsessions', 'mod_attendance'),
+            get_string('emailsubject', 'mod_attendance'),
+            '');
+        foreach ($existingnotifications as $notification) {
+            $url->params(array('action' => 'delete', 'notid' => $notification->id, 'id' => $id));
+            $actionbuttons = $OUTPUT->action_icon($url, new pix_icon('t/delete',
+                get_string('delete', 'attendance')), null, null);
+            $url->params(array('action' => 'update', 'notid' => $notification->id, 'id' => $id));
+            $actionbuttons .= $OUTPUT->action_icon($url, new pix_icon('t/edit',
+                get_string('update', 'attendance')), null, null);
+            $table->data[] = array($notification->warningpercent, $notification->warnafter,
+                                   $notification->emailsubject, $actionbuttons);
+        }
+        echo html_writer::table($table);
+    }
+    $addurl = new moodle_url('/mod/attendance/warnings.php', array('action' => 'add', 'id' => $id));
+    echo $OUTPUT->single_button($addurl, get_string('addwarning', 'mod_attendance'));
+
+}
+
+echo $OUTPUT->footer();
\ No newline at end of file