From fb747fee70abe564ff77bc2dcad1685c1691fd1c Mon Sep 17 00:00:00 2001 From: Hafriz Azhan Date: Wed, 7 Jan 2026 10:31:37 +0800 Subject: [PATCH 1/2] feat: add reminder email functionality and configuration options --- block_completion_progress.php | 22 +++ classes/defaults.php | 15 ++ classes/table/overview.php | 48 ++++- classes/task/send_reminders.php | 256 ++++++++++++++++++++++++++ db/tasks.php | 37 ++++ edit_form.php | 55 ++++++ lang/en/block_completion_progress.php | 18 ++ version.php | 2 +- 8 files changed, 451 insertions(+), 2 deletions(-) create mode 100644 classes/task/send_reminders.php create mode 100644 db/tasks.php diff --git a/block_completion_progress.php b/block_completion_progress.php index a1ed787af5..86b4f206f7 100644 --- a/block_completion_progress.php +++ b/block_completion_progress.php @@ -68,6 +68,28 @@ public function specialization() { } } + /** + * Normalize reminder settings when saving the block config. + * + * @param stdClass $data + * @param bool $nolongerused + */ + public function instance_config_save($data, $nolongerused = false) { + $current = $this->config ?? (object)[]; + $enabled = !empty($data->reminderenabled); + $wasenabled = !empty($current->reminderenabled); + $frequency = $data->reminderfrequency ?? null; + $threshold = $data->reminderthreshold ?? null; + $prevfrequency = $current->reminderfrequency ?? null; + $prevthreshold = $current->reminderthreshold ?? null; + + if (!$enabled || !$wasenabled || $frequency !== $prevfrequency || $threshold !== $prevthreshold) { + $data->reminderlastsent = 0; + } + + parent::instance_config_save($data, $nolongerused); + } + /** * Controls whether multiple instances of the block are allowed on a page * diff --git a/classes/defaults.php b/classes/defaults.php index 90c91c0417..4a3c939113 100644 --- a/classes/defaults.php +++ b/classes/defaults.php @@ -88,4 +88,19 @@ abstract class defaults { * Default number of seconds to cache completion percentages for overview pages. */ const OVERVIEWCACHETIME = 3600; + + /** + * Default reminder email enabled state. + */ + const REMINDERENABLED = 0; + + /** + * Default reminder threshold percentage. + */ + const REMINDERTHRESHOLD = 100; + + /** + * Default reminder frequency. + */ + const REMINDERFREQUENCY = 'weekly'; } diff --git a/classes/table/overview.php b/classes/table/overview.php index 00c8e639fe..cdf57f2f11 100644 --- a/classes/table/overview.php +++ b/classes/table/overview.php @@ -78,6 +78,10 @@ public function __construct(completion_progress $progress, $groups, $roleid, $bu parent::__construct('block-completion_progress-overview'); + $blockconfig = $this->progress->get_block_config(); + $showidnumber = !isset($blockconfig->showidnumber) || (int)$blockconfig->showidnumber === 1; + $showemail = !isset($blockconfig->showemail) || (int)$blockconfig->showemail === 1; + $tablecolumns = []; $tableheaders = []; @@ -95,6 +99,14 @@ public function __construct(completion_progress $progress, $groups, $roleid, $bu $tablecolumns[] = 'fullname'; $tableheaders[] = get_string('fullname'); + if ($showidnumber) { + $tablecolumns[] = 'idnumber'; + $tableheaders[] = get_string('matricnumber', 'block_completion_progress'); + } + if ($showemail) { + $tablecolumns[] = 'email'; + $tableheaders[] = get_string('email'); + } if (get_config('block_completion_progress', 'showlastincourse') != 0) { $tablecolumns[] = 'timeaccess'; @@ -118,6 +130,12 @@ public function __construct(completion_progress $progress, $groups, $roleid, $bu $this->set_attribute('class', 'overviewTable'); $this->column_class('fullname', 'col-fullname'); + if ($showidnumber) { + $this->column_class('idnumber', 'col-idnumber'); + } + if ($showemail) { + $this->column_class('email', 'col-email'); + } $this->column_class('timeaccess', 'col-timeaccess'); $this->column_class('progressbar', 'col-progressbar'); $this->column_class('progress', 'col-progress'); @@ -151,7 +169,7 @@ public function __construct(completion_progress $progress, $groups, $roleid, $bu $params['cachemin'] = time() - $cachetime; $params['bi'] = $this->progress->get_block_instance()->id; $this->set_sql( - "DISTINCT $picturefields, l.timeaccess, b.percentage AS progress, b.timemodified AS progressage", + "DISTINCT $picturefields, u.idnumber, u.email, l.timeaccess, b.percentage AS progress, b.timemodified AS progressage", "{user} u {$enroljoin->joins} {$rolejoin} " . "LEFT JOIN {user_lastaccess} l ON l.userid = u.id AND l.courseid = :courseid " . "LEFT JOIN {block_completion_progress} b ON b.userid = u.id AND " . @@ -224,6 +242,34 @@ public function col_fullname($row) { } } + /** + * Format the user idnumber. + * @param object $row + * @return string + */ + public function col_idnumber($row) { + if ($this->is_downloading()) { + return $row->idnumber; + } + return s($row->idnumber); + } + + /** + * Format the email address. + * @param object $row + * @return string + */ + public function col_email($row) { + if ($this->is_downloading()) { + return $row->email; + } + if (!$row->email) { + return ''; + } + $email = s($row->email); + return \html_writer::link('mailto:' . $email, $email); + } + /** * Format the time last accessed value. * @param object $row diff --git a/classes/task/send_reminders.php b/classes/task/send_reminders.php new file mode 100644 index 0000000000..6d426d0aab --- /dev/null +++ b/classes/task/send_reminders.php @@ -0,0 +1,256 @@ +. + +/** + * Reminder email task for completion progress. + * + * @package block_completion_progress + * @copyright 2025 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_completion_progress\task; + +use block_completion_progress\completion_progress; +use block_completion_progress\defaults; +use context_course; +use core_user; +use moodle_url; + +defined('MOODLE_INTERNAL') || die; + +/** + * Scheduled task that sends reminder emails to students below the configured threshold. + */ +class send_reminders extends \core\task\scheduled_task { + /** + * Task name for display. + * @return string + */ + public function get_name() { + return get_string('task_sendreminders', 'block_completion_progress'); + } + + /** + * Run the reminder task. + */ + public function execute() { + global $CFG, $DB; + + if (empty($CFG->enablecompletion)) { + return; + } + + require_once($CFG->dirroot . '/group/lib.php'); + + $instances = $DB->get_records('block_instances', ['blockname' => 'completion_progress']); + if (!$instances) { + return; + } + + foreach ($instances as $instance) { + $parentcontext = \context::instance_by_id($instance->parentcontextid, IGNORE_MISSING); + if (!$parentcontext || $parentcontext->contextlevel !== CONTEXT_COURSE) { + mtrace("Skipping block {$instance->id}: not in course context."); + continue; + } + if (!$this->is_instance_visible((int)$instance->id, (int)$parentcontext->id)) { + mtrace("Skipping block {$instance->id}: not visible in course context."); + continue; + } + + $courseid = $parentcontext->instanceid; + $course = $DB->get_record('course', ['id' => $courseid], '*', IGNORE_MISSING); + if (!$course) { + mtrace("Skipping block {$instance->id}: course not found for {$courseid}."); + continue; + } + mtrace("Block {$instance->id}: course {$courseid}."); + + $blockcontext = \context_block::instance($instance->id); + + $blockconfig = (object)(array)unserialize(base64_decode($instance->configdata ?? '')); + if (empty($blockconfig->reminderenabled)) { + mtrace("Skipping block {$instance->id}: reminders disabled."); + continue; + } + + $frequency = $blockconfig->reminderfrequency ?? defaults::REMINDERFREQUENCY; + $interval = $this->frequency_to_seconds($frequency); + $lastsent = isset($blockconfig->reminderlastsent) ? (int)$blockconfig->reminderlastsent : 0; + if ($interval > 0 && $lastsent > 0 && (time() - $lastsent) < $interval) { + $remaining = $interval - (time() - $lastsent); + mtrace("Skipping block {$instance->id}: frequency {$frequency}, wait {$remaining}s."); + continue; + } + + $threshold = isset($blockconfig->reminderthreshold) ? (int)$blockconfig->reminderthreshold : + defaults::REMINDERTHRESHOLD; + $threshold = max(0, min(100, $threshold)); + mtrace("Block {$instance->id}: threshold {$threshold}, frequency {$frequency}."); + + $coursecontext = context_course::instance($courseid); + $progressbase = (new completion_progress($course))->for_block_instance($instance); + if (!$progressbase->get_completion_info()->is_enabled()) { + mtrace("Skipping block {$instance->id}: completion disabled in course."); + continue; + } + if (!$progressbase->has_activities()) { + mtrace("Skipping block {$instance->id}: no activities."); + continue; + } + + $users = get_enrolled_users($coursecontext, '', 0, 'u.*', '', 0, 0, true); + if (!$users) { + mtrace("Skipping block {$instance->id}: no enrolled users."); + continue; + } + mtrace("Block {$instance->id}: enrolled users " . count($users) . "."); + + $courseurl = new moodle_url('/course/view.php', ['id' => $courseid]); + $coursename = format_string($course->fullname, true, ['context' => $coursecontext]); + $sentany = false; + + foreach ($users as $user) { + if (!has_capability('block/completion_progress:showbar', $blockcontext, $user->id)) { + mtrace("Block {$instance->id}: user {$user->id} skipped: no showbar capability."); + continue; + } + if (has_capability('block/completion_progress:overview', $blockcontext, $user->id)) { + mtrace("Block {$instance->id}: user {$user->id} skipped: has overview capability."); + continue; + } + if (!$this->user_matches_group($blockconfig->group ?? '0', $courseid, $user->id)) { + mtrace("Block {$instance->id}: user {$user->id} skipped: not in group filter."); + continue; + } + + $progress = clone $progressbase; + $progress->for_user($user); + if (!$progress->has_visible_activities()) { + mtrace("Block {$instance->id}: user {$user->id} skipped: no visible activities."); + continue; + } + + $percent = $progress->get_percentage(); + if ($percent === null || $percent >= $threshold) { + $val = $percent === null ? 'null' : (string)$percent; + mtrace("Block {$instance->id}: user {$user->id} skipped: percent {$val}."); + continue; + } + + $data = (object)[ + 'firstname' => $user->firstname, + 'coursename' => $coursename, + 'percent' => $percent, + 'courseurl' => $courseurl->out(false), + ]; + $subject = get_string('reminderemailsubject', 'block_completion_progress', $data); + $plain = get_string('reminderemailbody', 'block_completion_progress', $data); + $html = text_to_html($plain, false, false, true); + $sent = email_to_user($user, core_user::get_support_user(), $subject, $plain, $html); + $status = $sent ? 'sent' : 'failed'; + mtrace("Block {$instance->id}: user {$user->id} email {$status}."); + if ($sent) { + $sentany = true; + } + } + + if ($sentany) { + $blockconfig->reminderlastsent = time(); + $instance->configdata = base64_encode(serialize((array)$blockconfig)); + $DB->update_record('block_instances', $instance); + mtrace("Block {$instance->id}: reminder last sent updated."); + } else { + mtrace("Block {$instance->id}: no emails sent, last sent unchanged."); + } + } + } + + /** + * Determine whether a block instance is visible on course pages. + * @param int $instanceid + * @param int $contextid + * @return bool + */ + private function is_instance_visible(int $instanceid, int $contextid): bool { + global $DB; + + $sql = "SELECT bp.visible + FROM {block_positions} bp + WHERE bp.blockinstanceid = :instanceid + AND bp.contextid = :contextid + AND " . $DB->sql_like('bp.pagetype', ':pagetype', false); + $params = [ + 'instanceid' => $instanceid, + 'contextid' => $contextid, + 'pagetype' => 'course-view-%', + ]; + $visibles = $DB->get_fieldset_sql($sql, $params); + if (!$visibles) { + return true; + } + foreach ($visibles as $visible) { + if ((int)$visible === 1) { + return true; + } + } + + return false; + } + + /** + * Convert frequency settings into seconds. + * @param string $frequency + * @return int + */ + private function frequency_to_seconds(string $frequency): int { + switch ($frequency) { + case 'daily': + return DAYSECS; + case 'weekly': + return WEEKSECS; + case 'monthly': + return 30 * DAYSECS; + case 'yearly': + return 365 * DAYSECS; + default: + return 0; + } + } + + /** + * Check if a user belongs to the configured group or grouping. + * @param string $group + * @param int $courseid + * @param int $userid + * @return bool + */ + private function user_matches_group(string $group, int $courseid, int $userid): bool { + if ($group === '0' || $group === '') { + return true; + } + if ((substr($group, 0, 6) === 'group-') && ($groupid = (int)substr($group, 6))) { + return groups_is_member($groupid, $userid); + } + if ((substr($group, 0, 9) === 'grouping-') && ($groupingid = (int)substr($group, 9))) { + $usergroups = groups_get_user_groups($courseid, $userid); + return array_key_exists($groupingid, $usergroups); + } + + return true; + } +} diff --git a/db/tasks.php b/db/tasks.php new file mode 100644 index 0000000000..acfbc441fb --- /dev/null +++ b/db/tasks.php @@ -0,0 +1,37 @@ +. + +/** + * Scheduled tasks. + * + * @package block_completion_progress + * @copyright 2025 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +$tasks = [ + [ + 'classname' => '\block_completion_progress\task\send_reminders', + 'blocking' => 0, + 'minute' => '0', + 'hour' => '3', + 'day' => '*', + 'dayofweek' => '*', + 'month' => '*', + ], +]; diff --git a/edit_form.php b/edit_form.php index e7fbe87e7f..8a4a359e25 100644 --- a/edit_form.php +++ b/edit_form.php @@ -120,6 +120,61 @@ protected function specific_definition($mform) { $mform->setDefault('config_showpercentage', defaults::SHOWPERCENTAGE); $mform->addHelpButton('config_showpercentage', 'why_show_precentage', 'block_completion_progress'); + // Choose which overview columns to display. + $mform->addElement('header', 'config_overviewcolumns', get_string('config_overviewcolumns', 'block_completion_progress')); + $mform->addElement( + 'advcheckbox', + 'config_showidnumber', + get_string('config_showidnumber', 'block_completion_progress') + ); + $mform->setDefault('config_showidnumber', 1); + $mform->addElement( + 'advcheckbox', + 'config_showemail', + get_string('config_showemail', 'block_completion_progress') + ); + $mform->setDefault('config_showemail', 1); + $mform->setAdvanced('config_showidnumber', true); + $mform->setAdvanced('config_showemail', true); + + // Configure reminder emails to students below a threshold. + $mform->addElement('header', 'config_reminders', get_string('config_reminders', 'block_completion_progress')); + $mform->addElement( + 'advcheckbox', + 'config_reminderenabled', + get_string('config_reminderenabled', 'block_completion_progress') + ); + $mform->setDefault('config_reminderenabled', defaults::REMINDERENABLED); + + $options = [ + 'daily' => get_string('frequencydaily', 'block_completion_progress'), + 'weekly' => get_string('frequencyweekly', 'block_completion_progress'), + 'monthly' => get_string('frequencymonthly', 'block_completion_progress'), + 'yearly' => get_string('frequencyyearly', 'block_completion_progress'), + ]; + $mform->addElement( + 'select', + 'config_reminderfrequency', + get_string('config_reminderfrequency', 'block_completion_progress'), + $options + ); + $mform->setDefault('config_reminderfrequency', defaults::REMINDERFREQUENCY); + + $mform->addElement( + 'text', + 'config_reminderthreshold', + get_string('config_reminderthreshold', 'block_completion_progress'), + ['size' => 3] + ); + $mform->setType('config_reminderthreshold', PARAM_INT); + $mform->setDefault('config_reminderthreshold', defaults::REMINDERTHRESHOLD); + + $mform->disabledIf('config_reminderfrequency', 'config_reminderenabled', 'notchecked'); + $mform->disabledIf('config_reminderthreshold', 'config_reminderenabled', 'notchecked'); + $mform->setAdvanced('config_reminderenabled', true); + $mform->setAdvanced('config_reminderfrequency', true); + $mform->setAdvanced('config_reminderthreshold', true); + // Allow the block to be visible to a single group or grouping. $groups = groups_get_all_groups($COURSE->id); $groupings = groups_get_all_groupings($COURSE->id); diff --git a/lang/en/block_completion_progress.php b/lang/en/block_completion_progress.php index e5ce237fe6..bd07561933 100644 --- a/lang/en/block_completion_progress.php +++ b/lang/en/block_completion_progress.php @@ -43,9 +43,16 @@ $string['config_orderby_course_order'] = 'Ordering in course'; $string['config_orderby_due_time'] = 'Time using "{$a}" date'; $string['config_percentage'] = 'Show percentage to students'; +$string['config_overviewcolumns'] = 'Overview columns'; $string['config_scroll'] = 'Scroll'; $string['config_selectactivities'] = 'Select activities'; $string['config_selectedactivities'] = 'Selected activities'; +$string['config_showemail'] = 'Show email column'; +$string['config_showidnumber'] = 'Show matric number column'; +$string['config_reminders'] = 'Reminder emails'; +$string['config_reminderenabled'] = 'Enable reminder emails'; +$string['config_reminderfrequency'] = 'Send reminder emails'; +$string['config_reminderthreshold'] = 'Send when progress is below (%)'; $string['config_squeeze'] = 'Squeeze'; $string['config_title'] = 'Alternate title'; $string['config_wrap'] = 'Wrap'; @@ -69,6 +76,7 @@ $string['indeterminate'] = '?'; $string['lastonline'] = 'Last in course'; $string['mouse_over_prompt'] = 'Mouse over or touch bar for info.'; +$string['matricnumber'] = 'Matric Number'; $string['no_activities_config_message'] = 'There are no activities or resources with activity completion set or no activities or resources have been selected. Set activity completion on activities and resources then configure this block.'; $string['no_activities_message'] = 'No activities or resources are being monitored. Use config to set up monitoring.'; $string['no_blocks'] = 'No Completion Progress blocks are set up for your courses.'; @@ -90,6 +98,16 @@ $string['progress'] = 'Progress'; $string['progressbar'] = 'Completion Progress'; $string['progresscachetime'] = 'Calculated {$a} ago'; +$string['frequencydaily'] = 'Daily'; +$string['frequencyweekly'] = 'Weekly'; +$string['frequencymonthly'] = 'Monthly'; +$string['frequencyyearly'] = 'Yearly'; +$string['reminderemailsubject'] = 'Progress reminder for {$a->coursename}'; +$string['reminderemailbody'] = 'Hello {$a->firstname},' . "\n\n" . + 'This is a reminder that your completion progress in {$a->coursename} is {$a->percent}%.' . "\n\n" . + 'Visit the course: {$a->courseurl}' . "\n\n" . + 'Thanks.'; +$string['task_sendreminders'] = 'Send completion progress reminder emails'; $string['selectitem'] = 'Select \'{$a}\''; $string['shortname'] = 'Short course name'; $string['showallinfo'] = 'Show all info'; diff --git a/version.php b/version.php index 9535ae2144..7c0eafdcf6 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2025101300; +$plugin->version = 2025101400; $plugin->requires = 2022112800; $plugin->maturity = MATURITY_STABLE; $plugin->release = 'Version for Moodle 4.1 onwards'; From ef51c5f309c50c3e0a5e23cdadaab9e3b0b82dc4 Mon Sep 17 00:00:00 2001 From: Hafriz Azhan Date: Wed, 7 Jan 2026 11:14:40 +0800 Subject: [PATCH 2/2] feat: add reminder email functionality and overview table enhancements --- README.md | 20 ++++++++ classes/task/send_reminders.php | 86 +++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 60319a6901..408e9fb65b 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,23 @@ Once the Completion Progress block is installed, you can use it in a course as f 6. (Optional) Configure how the block should appear Hidden items will not appear in the Completion Progress block until they are visible to students. This is useful for a scheduled release of activities. + +## New features + +- Overview table can show Matric Number and Email columns; these can be toggled per block. +- Reminder emails: lecturers can enable scheduled reminder emails for students below a configured progress threshold. +- Reminder frequency options: daily, weekly, monthly, or yearly. +- Emails include the student's current completion percentage and a link to the course. + +### Configuration details + +Block settings (per instance): +- Overview columns: toggle Matric Number and Email columns in the Overview table. +- Reminder emails: enable/disable scheduled reminder emails for students. +- Send reminder emails: choose daily, weekly, monthly, or yearly frequency. +- Send when progress is below (%): integer threshold from 0 to 100; students with progress below this value are emailed. + +Operational notes: +- Reminder emails are sent by Moodle scheduled tasks (cron). +- Reminders respect block group restrictions and only target users who can view the block (student role). +- If no emails are sent in a run, the last-sent timestamp is not updated, so the next run can try again. diff --git a/classes/task/send_reminders.php b/classes/task/send_reminders.php index 6d426d0aab..1af3aed2d3 100644 --- a/classes/task/send_reminders.php +++ b/classes/task/send_reminders.php @@ -103,7 +103,7 @@ public function execute() { mtrace("Block {$instance->id}: threshold {$threshold}, frequency {$frequency}."); $coursecontext = context_course::instance($courseid); - $progressbase = (new completion_progress($course))->for_block_instance($instance); + $progressbase = (new completion_progress($course))->for_overview()->for_block_instance($instance); if (!$progressbase->get_completion_info()->is_enabled()) { mtrace("Skipping block {$instance->id}: completion disabled in course."); continue; @@ -112,19 +112,44 @@ public function execute() { mtrace("Skipping block {$instance->id}: no activities."); continue; } + $progressbase->compute_overview_percentages(); - $users = get_enrolled_users($coursecontext, '', 0, 'u.*', '', 0, 0, true); - if (!$users) { - mtrace("Skipping block {$instance->id}: no enrolled users."); - continue; + $groupids = $this->get_group_ids_for_filter($blockconfig->group ?? '0', $courseid); + $enrolsql = get_enrolled_join($coursecontext, 'u.id', false); + $groupjoin = ''; + $groupwhere = ''; + $groupparams = []; + if (!empty($groupids)) { + list($grouptoken, $groupparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED, 'gid'); + $groupjoin = "JOIN {groups_members} gm ON gm.userid = u.id AND gm.groupid {$grouptoken}"; } - mtrace("Block {$instance->id}: enrolled users " . count($users) . "."); + + $cachetime = get_config('block_completion_progress', 'overviewcachetime') ?: defaults::OVERVIEWCACHETIME; + $cachemin = time() - $cachetime; + $sql = "SELECT u.*, b.percentage + FROM {user} u {$enrolsql->joins} + {$groupjoin} + JOIN {block_completion_progress} b + ON b.userid = u.id + AND b.blockinstanceid = :bi + AND b.timemodified > :cachemin + WHERE {$enrolsql->wheres} + AND b.percentage IS NOT NULL + AND b.percentage < :threshold"; + $params = $enrolsql->params + $groupparams + [ + 'bi' => $instance->id, + 'cachemin' => $cachemin, + 'threshold' => $threshold, + ]; + + $recordset = $DB->get_recordset_sql($sql, $params); + $recipients = 0; $courseurl = new moodle_url('/course/view.php', ['id' => $courseid]); $coursename = format_string($course->fullname, true, ['context' => $coursecontext]); $sentany = false; - foreach ($users as $user) { + foreach ($recordset as $user) { if (!has_capability('block/completion_progress:showbar', $blockcontext, $user->id)) { mtrace("Block {$instance->id}: user {$user->id} skipped: no showbar capability."); continue; @@ -133,29 +158,12 @@ public function execute() { mtrace("Block {$instance->id}: user {$user->id} skipped: has overview capability."); continue; } - if (!$this->user_matches_group($blockconfig->group ?? '0', $courseid, $user->id)) { - mtrace("Block {$instance->id}: user {$user->id} skipped: not in group filter."); - continue; - } - - $progress = clone $progressbase; - $progress->for_user($user); - if (!$progress->has_visible_activities()) { - mtrace("Block {$instance->id}: user {$user->id} skipped: no visible activities."); - continue; - } - - $percent = $progress->get_percentage(); - if ($percent === null || $percent >= $threshold) { - $val = $percent === null ? 'null' : (string)$percent; - mtrace("Block {$instance->id}: user {$user->id} skipped: percent {$val}."); - continue; - } + $recipients++; $data = (object)[ 'firstname' => $user->firstname, 'coursename' => $coursename, - 'percent' => $percent, + 'percent' => $user->percentage, 'courseurl' => $courseurl->out(false), ]; $subject = get_string('reminderemailsubject', 'block_completion_progress', $data); @@ -168,6 +176,8 @@ public function execute() { $sentany = true; } } + $recordset->close(); + mtrace("Block {$instance->id}: recipients {$recipients}."); if ($sentany) { $blockconfig->reminderlastsent = time(); @@ -253,4 +263,28 @@ private function user_matches_group(string $group, int $courseid, int $userid): return true; } + + /** + * Resolve group/grouping filter into a list of group IDs. + * @param string $group + * @param int $courseid + * @return array + */ + private function get_group_ids_for_filter(string $group, int $courseid): array { + if ($group === '0' || $group === '') { + return []; + } + if ((substr($group, 0, 6) === 'group-') && ($groupid = (int)substr($group, 6))) { + return [$groupid]; + } + if ((substr($group, 0, 9) === 'grouping-') && ($groupingid = (int)substr($group, 9))) { + $groups = groups_get_all_groups($courseid, 0, $groupingid); + if (!$groups) { + return []; + } + return array_map('intval', array_keys($groups)); + } + + return []; + } }