Skip to content

Commit b31cc81

Browse files
Optimize email certificate task by reducing database reads/writes.
1 parent fdb7308 commit b31cc81

File tree

6 files changed

+118
-74
lines changed

6 files changed

+118
-74
lines changed

classes/task/email_certificate_task.php

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,14 @@ public function get_name() {
4949
public function execute() {
5050
global $DB;
5151

52-
// Get the certificatesPerRun, includeinnotvisiblecourses and certificateexecutionperiod configurations.
53-
$certificatesPerRun = (int)get_config('customcert', 'certificatesperrun');
54-
$includeInNotVisibleCourses = (bool)get_config('customcert', 'includeinnotvisiblecourses');
55-
$certificateExecutionPeriod = (int)get_config('customcert', 'certificateexecutionperiod');
56-
52+
// Get the certificatesperrun, includeinnotvisiblecourses, and certificateexecutionperiod configurations.
53+
$certificatesperrun = (int)get_config('customcert', 'certificatesperrun');
54+
$includeinnotvisiblecourses = (bool)get_config('customcert', 'includeinnotvisiblecourses');
55+
$certificateexecutionperiod = (int)get_config('customcert', 'certificateexecutionperiod');
5756

5857
// Get the last processed batch and total certificates to process.
59-
$taskProgress = $DB->get_record('customcert_task_progress', ['taskname' => 'email_certificate_task']);
60-
$lastProcessedBatch = $taskProgress->last_processed;
61-
58+
$taskprogress = $DB->get_record('customcert_task_progress', ['taskname' => 'email_certificate_task']);
59+
$lastprocessed = $taskprogress->last_processed;
6260

6361
// Get all the certificates that have requested someone get emailed.
6462
$emailotherslengthsql = $DB->sql_length('c.emailothers');
@@ -79,16 +77,16 @@ public function execute() {
7977
OR $emailotherslengthsql >= 3)";
8078

8179
// Check the includeinnotvisiblecourses configuration.
82-
if (!$includeInNotVisibleCourses) {
80+
if (!$includeinnotvisiblecourses) {
8381
// Exclude certificates from hidden courses.
8482
$sql .= " AND co.visible = 1 AND cat.visible = 1";
8583
}
8684

8785
// Add condition based on certificate execution period.
88-
if ($certificateExecutionPeriod > 0) {
86+
if ($certificateexecutionperiod > 0) {
8987
// Include courses with no end date or end date greater than the specified period.
9088
$sql .= " AND (co.enddate = 0 OR co.enddate > :enddate)";
91-
$params['enddate'] = time() - $certificateExecutionPeriod;
89+
$params['enddate'] = time() - $certificateexecutionperiod;
9290
}
9391

9492
// Execute the SQL query.
@@ -102,26 +100,30 @@ public function execute() {
102100
$textrenderer = $page->get_renderer('mod_customcert', 'email', 'textemail');
103101

104102
// Store the total count of certificates in the database.
105-
$totalCertificatesToProcess = count($customcerts);
106-
$DB->set_field('customcert_task_progress', 'total_certificate_to_process', $totalCertificatesToProcess, [
103+
$totalcertificatestoprocess = count($customcerts);
104+
$DB->set_field('customcert_task_progress', 'total_certificate_to_process', $totalcertificatestoprocess, [
107105
'taskname' => 'email_certificate_task',
108106
]);
109107

110-
111108
// Check if we need to reset and start from the beginning.
112-
if ($lastProcessedBatch >= count($customcerts)) {
113-
$lastProcessedBatch = 0; // Reset to the beginning.
109+
if ($lastprocessed >= count($customcerts)) {
110+
$lastprocessed = 0; // Reset to the beginning.
114111
}
115112

116-
if ($certificatesPerRun <= 0) {
113+
if ($certificatesperrun <= 0) {
117114
// Process all certificates in a single run.
118115
$certificates = $customcerts;
119116
} else {
120117
// Process certificates in batches, starting from the last processed batch.
121-
$certificates = array_slice($customcerts, $lastProcessedBatch, $certificatesPerRun);
118+
$certificates = array_slice($customcerts, $lastprocessed, $certificatesperrun);
122119
}
123120

124121
foreach ($certificates as $customcert) {
122+
// Check if the certificate is hidden, quit early.
123+
$fastmoduleinfo = get_fast_modinfo($customcert->courseid)->instances['customcert'][$customcert->id];
124+
if (!$fastmoduleinfo->visible) {
125+
continue;
126+
}
125127
// Do not process an empty certificate.
126128
$sql = "SELECT ce.*
127129
FROM {customcert_elements} ce
@@ -163,27 +165,28 @@ public function execute() {
163165
WHERE ci.customcertid = :customcertid";
164166
$issuedusers = $DB->get_records_sql($sql, ['customcertid' => $customcert->id]);
165167

166-
// Now, get a list of users who can access the certificate but have not yet.
167-
$enrolledusers = get_enrolled_users(\context_course::instance($customcert->courseid), 'mod/customcert:view');
168-
foreach ($enrolledusers as $enroluser) {
169-
// Check if the user has already been issued.
170-
if (in_array($enroluser->id, array_keys((array) $issuedusers))) {
171-
continue;
172-
}
168+
// Now, get a list of users who can Manage the certificate.
169+
$userswithmanage = get_users_by_capability($context, 'mod/customcert:manage', 'id');
173170

174-
// Now check if the certificate is not visible to the current user.
175-
$cm = get_fast_modinfo($customcert->courseid, $enroluser->id)->instances['customcert'][$customcert->id];
176-
if (!$cm->uservisible) {
177-
continue;
178-
}
171+
// Get the context of the Custom Certificate module.
172+
$cm = get_coursemodule_from_instance('customcert', $customcert->id, $customcert->course);
173+
$context = \context_module::instance($cm->id);
179174

180-
// Don't want to email those with the capability to manage the certificate.
181-
if (has_capability('mod/customcert:manage', $context, $enroluser->id)) {
175+
// Now, get a list of users who can Issue the certificate but have not yet.
176+
// Get users with the specified capability in the Custom Certificate module context.
177+
$userwithissue = get_users_by_capability($context, 'mod/customcert:receiveissue', 'id, firstname, lastname, email');
178+
$infomodule = new \core_availability\info_module($fastmoduleinfo);
179+
// Filter who can't access due to availability restriction, from the full list.
180+
$userscanissue = $infomodule->filter_user_list($userwithissue);
181+
182+
foreach ($userscanissue as $enroluser) {
183+
// Check if the user has already been issued.
184+
if (in_array($enroluser->id, array_keys((array)$issuedusers))) {
182185
continue;
183186
}
184187

185-
// Only email those with the capability to receive the certificate.
186-
if (!has_capability('mod/customcert:receiveissue', $context, $enroluser->id)) {
188+
// Don't want to email those with the capability to manage the certificate.
189+
if (in_array($enroluser->id, array_keys((array)$userswithmanage))) {
187190
continue;
188191
}
189192

@@ -216,7 +219,7 @@ public function execute() {
216219
}
217220
}
218221

219-
// If there are no users to email we can return early.
222+
// If there are no users to email, we can return early.
220223
if (!$issuedusers) {
221224
continue;
222225
}
@@ -227,7 +230,7 @@ public function execute() {
227230
return;
228231
}
229232

230-
$issueIds = array();
233+
$issueids = [];
231234
// Now, email the people we need to.
232235
foreach ($issuedusers as $user) {
233236
// Set up the user.
@@ -301,17 +304,18 @@ public function execute() {
301304
}
302305

303306
// Set the field so that it is emailed.
304-
$issueIds[] = $user->issueid;
305-
//$DB->set_field('customcert_issues', 'emailed', 1, ['id' => $user->issueid]);
307+
$issueids[] = $user->issueid;
306308
}
307-
if (!empty($issueIds)) {
308-
$DB->set_field_select('customcert_issues', 'emailed', 1 , 'id IN (' . implode(',', $issueIds) . ')');
309+
if (!empty($issueids)) {
310+
$DB->set_field_select('customcert_issues', 'emailed', 1, 'id IN (' . implode(',', $issueids) . ')');
309311
}
310312
}
311-
// Update the last processed batch.
312-
$newLastProcessedBatch = $lastProcessedBatch + count($certificates);
313-
$DB->set_field('customcert_task_progress', 'last_processed', $newLastProcessedBatch, [
314-
'taskname' => 'email_certificate_task',
315-
]);
313+
// Update the last processed position, if run in batches.
314+
if ($certificatesperrun > 0) {
315+
$newlastprocessed = $lastprocessed + count($certificates);
316+
$DB->set_field('customcert_task_progress', 'last_processed', $newlastprocessed, [
317+
'taskname' => 'email_certificate_task',
318+
]);
319+
}
316320
}
317321
}

db/install.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* Customcert module upgrade code.
19+
*
20+
* @package mod_customcert
21+
* @copyright 2016 Mark Nelson <[email protected]>
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
*/
24+
25+
/**
26+
* Customcert module upgrade code.
27+
*
28+
* @param int $oldversion the version we are upgrading from
29+
* @return bool always true
30+
*/
31+
32+
/**
33+
* Custom code to be run on installing the plugin.
34+
*/
35+
function xmldb_customcert_install() {
36+
global $DB;
37+
38+
$dbman = $DB->get_manager();
39+
40+
// Add a default row to the customcert_task_progress table.
41+
$defaultdata = new stdClass();
42+
$defaultdata->taskname = 'email_certificate_task';
43+
$defaultdata->last_processed = 0;
44+
$defaultdata->total_certificate_to_process = 0;
45+
46+
// Write close to ensure the transaction is committed.
47+
\core\session\manager::write_close();
48+
49+
// Insert the default data into the table.
50+
$DB->insert_record('customcert_task_progress', $defaultdata);
51+
return true;
52+
}

db/install.xml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
2-
<XMLDB PATH="mod/customcert/db" VERSION="20240306" COMMENT="XMLDB file for Moodle mod/customcert"
2+
<XMLDB PATH="mod/customcert/db" VERSION="20240313" COMMENT="XMLDB file for Moodle mod/customcert"
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
55
>
@@ -106,13 +106,9 @@
106106
<FIELD NAME="taskname" TYPE="char" LENGTH="255" NOTNULL="true" DEFAULT="email_certificate_task" SEQUENCE="false"/>
107107
<FIELD NAME="last_processed" TYPE="int" LENGTH="20" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
108108
<FIELD NAME="total_certificate_to_process" TYPE="int" LENGTH="20" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="to store the total count of certificates that should be processed"/>
109-
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
110-
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
111-
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
112109
</FIELDS>
113110
<KEYS>
114111
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
115-
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
116112
</KEYS>
117113
</TABLE>
118114
</TABLES>

db/upgrade.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,6 @@ function xmldb_customcert_upgrade($oldversion) {
245245
$table->add_field('taskname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'email_certificate_task');
246246
$table->add_field('last_processed', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, '0');
247247
$table->add_field('total_certificate_to_process', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, '0');
248-
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
249-
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
250-
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
251248

252249
// Adding keys to table customcert_task_progress.
253250
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
@@ -257,25 +254,19 @@ function xmldb_customcert_upgrade($oldversion) {
257254
if (!$dbman->table_exists($table)) {
258255
$dbman->create_table($table);
259256
// Add a default row to the customcert_task_progress table.
260-
$defaultData = new stdClass();
261-
$defaultData->taskname = 'email_certificate_task';
262-
$defaultData->last_processed = 0;
263-
$defaultData->total_certificate_to_process = 0;
264-
$defaultData->usermodified = get_admin();;
265-
$defaultData->timecreated = time();
266-
$defaultData->timemodified = time();
257+
$defaultdata = new stdClass();
258+
$defaultdata->taskname = 'email_certificate_task';
259+
$defaultdata->last_processed = 0;
260+
$defaultdata->total_certificate_to_process = 0;
267261

268262
// Write close to ensure the transaction is committed.
269263
\core\session\manager::write_close();
270264

271265
// Insert the default data into the table.
272-
$DB->insert_record('customcert_task_progress', $defaultData);
266+
$DB->insert_record('customcert_task_progress', $defaultdata);
273267
}
274-
275268
// Customcert savepoint reached.
276269
upgrade_mod_savepoint(true, 2023042408, 'customcert');
277270
}
278-
279-
280271
return true;
281272
}

lang/en/customcert.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,11 @@
229229
// Acess API.
230230
$string['customcert:managelanguages'] = 'Manage language on edit form';
231231

232-
$string['certificatesperrun'] = 'Certificates Per Run';
233-
$string['certificatesperrun_desc'] = 'Enter the number of certificates to process per scheduled task run where 0 means it will process all certifiate.';
234-
$string['includeinnotvisiblecourses'] = 'Include Certificates in Not Visible Courses';
232+
$string['certificatesperrun'] = 'Certificates per run';
233+
$string['certificatesperrun_desc'] = 'Enter the number of certificates to process per scheduled task run <b>where 0 means it will process all certificates</b>.';
234+
$string['includeinnotvisiblecourses'] = 'Include certificates in hidden courses';
235235
$string['includeinnotvisiblecourses_desc'] = 'Check this box to include certificates in courses that are not visible to the user.';
236-
$string['certificateexecutionperiod'] = 'Certificate Execution Period';
237-
$string['certificateexecutionperiod_desc'] = 'Specify the period for which certificates should be executed based on their end date. Set to 0 to execute all certificates, regardless of their age.';
238-
$string['scheduledtaskconfigheading'] = 'Scheduled Task Configuration';
236+
$string['certificateexecutionperiod'] = 'Certificate execution period';
237+
$string['certificateexecutionperiod_desc'] = 'Specify the period for which certificates should be executed based on their end date. <b>Set to 0 to execute all certificates, regardless of their age.</b>';
238+
$string['scheduledtaskconfigheading'] = 'Scheduled task configuration';
239239
$string['scheduledtaskconfigdesc'] = 'Configure the settings for the scheduled task that processes certificates.';

settings.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,18 @@
5959
}
6060

6161

62-
$settings->add(new admin_setting_heading('scheduledtaskconfig',
62+
$settings->add(new admin_setting_heading('scheduledtaskconfig',
6363
get_string('scheduledtaskconfigheading', 'customcert'),
6464
get_string('scheduledtaskconfigdesc', 'customcert')));
6565

66-
$settings->add(new admin_setting_configtext('customcert/certificatesperrun', get_string('certificatesperrun', 'customcert'),
66+
$settings->add(new admin_setting_configtext('customcert/certificatesperrun',
67+
get_string('certificatesperrun', 'customcert'),
6768
get_string('certificatesperrun_desc', 'customcert'),
68-
50, PARAM_INT));
69+
0, PARAM_INT));
6970
$settings->add(new admin_setting_configcheckbox('customcert/includeinnotvisiblecourses',
7071
get_string('includeinnotvisiblecourses', 'customcert'),
7172
get_string('includeinnotvisiblecourses_desc', 'customcert'), 0));
72-
$settings->add(
73+
$settings->add(
7374
new admin_setting_configduration(
7475
'customcert/certificateexecutionperiod',
7576
new \lang_string('certificateexecutionperiod', 'customcert'),

0 commit comments

Comments
 (0)