Skip to content

Commit

Permalink
Optimize email certificate task by reducing database reads/writes.
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamedmohamedatia committed Apr 4, 2024
1 parent f4f7933 commit 3172f46
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 74 deletions.
94 changes: 49 additions & 45 deletions classes/task/email_certificate_task.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,14 @@ public function get_name() {
public function execute() {
global $DB;

// Get the certificatesPerRun, includeinnotvisiblecourses and certificateexecutionperiod configurations.
$certificatesPerRun = (int)get_config('customcert', 'certificatesperrun');
$includeInNotVisibleCourses = (bool)get_config('customcert', 'includeinnotvisiblecourses');
$certificateExecutionPeriod = (int)get_config('customcert', 'certificateexecutionperiod');

// Get the certificatesperrun, includeinnotvisiblecourses, and certificateexecutionperiod configurations.
$certificatesperrun = (int)get_config('customcert', 'certificatesperrun');
$includeinnotvisiblecourses = (bool)get_config('customcert', 'includeinnotvisiblecourses');
$certificateexecutionperiod = (int)get_config('customcert', 'certificateexecutionperiod');

// Get the last processed batch and total certificates to process.
$taskProgress = $DB->get_record('customcert_task_progress', ['taskname' => 'email_certificate_task']);
$lastProcessedBatch = $taskProgress->last_processed;

$taskprogress = $DB->get_record('customcert_task_progress', ['taskname' => 'email_certificate_task']);
$lastprocessed = $taskprogress->last_processed;

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

// Check the includeinnotvisiblecourses configuration.
if (!$includeInNotVisibleCourses) {
if (!$includeinnotvisiblecourses) {
// Exclude certificates from hidden courses.
$sql .= " AND co.visible = 1 AND cat.visible = 1";
}

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

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

// Store the total count of certificates in the database.
$totalCertificatesToProcess = count($customcerts);
$DB->set_field('customcert_task_progress', 'total_certificate_to_process', $totalCertificatesToProcess, [
$totalcertificatestoprocess = count($customcerts);
$DB->set_field('customcert_task_progress', 'total_certificate_to_process', $totalcertificatestoprocess, [
'taskname' => 'email_certificate_task',
]);


// Check if we need to reset and start from the beginning.
if ($lastProcessedBatch >= count($customcerts)) {
$lastProcessedBatch = 0; // Reset to the beginning.
if ($lastprocessed >= count($customcerts)) {
$lastprocessed = 0; // Reset to the beginning.
}

if ($certificatesPerRun <= 0) {
if ($certificatesperrun <= 0) {
// Process all certificates in a single run.
$certificates = $customcerts;
} else {
// Process certificates in batches, starting from the last processed batch.
$certificates = array_slice($customcerts, $lastProcessedBatch, $certificatesPerRun);
$certificates = array_slice($customcerts, $lastprocessed, $certificatesperrun);
}

foreach ($certificates as $customcert) {
// Check if the certificate is hidden, quit early.
$fastmoduleinfo = get_fast_modinfo($customcert->courseid)->instances['customcert'][$customcert->id];
if (!$fastmoduleinfo->visible) {
continue;
}
// Do not process an empty certificate.
$sql = "SELECT ce.*
FROM {customcert_elements} ce
Expand Down Expand Up @@ -166,27 +168,28 @@ public function execute() {
WHERE ci.customcertid = :customcertid";
$issuedusers = $DB->get_records_sql($sql, ['customcertid' => $customcert->id]);

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

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

// Don't want to email those with the capability to manage the certificate.
if (has_capability('mod/customcert:manage', $context, $enroluser->id)) {
// Now, get a list of users who can Issue the certificate but have not yet.
// Get users with the specified capability in the Custom Certificate module context.
$userwithissue = get_users_by_capability($context, 'mod/customcert:receiveissue', 'id, firstname, lastname, email');
$infomodule = new \core_availability\info_module($fastmoduleinfo);
// Filter who can't access due to availability restriction, from the full list.
$userscanissue = $infomodule->filter_user_list($userwithissue);

foreach ($userscanissue as $enroluser) {
// Check if the user has already been issued.
if (in_array($enroluser->id, array_keys((array)$issuedusers))) {
continue;
}

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

Expand Down Expand Up @@ -219,7 +222,7 @@ public function execute() {
}
}

// If there are no users to email we can return early.
// If there are no users to email, we can return early.
if (!$issuedusers) {
continue;
}
Expand All @@ -230,7 +233,7 @@ public function execute() {
return;
}

$issueIds = array();
$issueids = [];
// Now, email the people we need to.
foreach ($issuedusers as $user) {
// Set up the user.
Expand Down Expand Up @@ -304,17 +307,18 @@ public function execute() {
}

// Set the field so that it is emailed.
$issueIds[] = $user->issueid;
//$DB->set_field('customcert_issues', 'emailed', 1, ['id' => $user->issueid]);
$issueids[] = $user->issueid;
}
if (!empty($issueIds)) {
$DB->set_field_select('customcert_issues', 'emailed', 1 , 'id IN (' . implode(',', $issueIds) . ')');
if (!empty($issueids)) {
$DB->set_field_select('customcert_issues', 'emailed', 1, 'id IN (' . implode(',', $issueids) . ')');
}
}
// Update the last processed batch.
$newLastProcessedBatch = $lastProcessedBatch + count($certificates);
$DB->set_field('customcert_task_progress', 'last_processed', $newLastProcessedBatch, [
'taskname' => 'email_certificate_task',
]);
// Update the last processed position, if run in batches.
if ($certificatesperrun > 0) {
$newlastprocessed = $lastprocessed + count($certificates);
$DB->set_field('customcert_task_progress', 'last_processed', $newlastprocessed, [
'taskname' => 'email_certificate_task',
]);
}
}
}
52 changes: 52 additions & 0 deletions db/install.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?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/>.

/**
* Customcert module upgrade code.
*
* @package mod_customcert
* @copyright 2016 Mark Nelson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

/**
* Customcert module upgrade code.
*
* @param int $oldversion the version we are upgrading from
* @return bool always true
*/

/**
* Custom code to be run on installing the plugin.
*/
function xmldb_customcert_install() {
global $DB;

$dbman = $DB->get_manager();

// Add a default row to the customcert_task_progress table.
$defaultdata = new stdClass();
$defaultdata->taskname = 'email_certificate_task';
$defaultdata->last_processed = 0;
$defaultdata->total_certificate_to_process = 0;

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

// Insert the default data into the table.
$DB->insert_record('customcert_task_progress', $defaultdata);
return true;
}
6 changes: 1 addition & 5 deletions db/install.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/customcert/db" VERSION="20240306" COMMENT="XMLDB file for Moodle mod/customcert"
<XMLDB PATH="mod/customcert/db" VERSION="20240313" COMMENT="XMLDB file for Moodle mod/customcert"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
Expand Down Expand Up @@ -106,13 +106,9 @@
<FIELD NAME="taskname" TYPE="char" LENGTH="255" NOTNULL="true" DEFAULT="email_certificate_task" SEQUENCE="false"/>
<FIELD NAME="last_processed" TYPE="int" LENGTH="20" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<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"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
Expand Down
19 changes: 5 additions & 14 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,6 @@ function xmldb_customcert_upgrade($oldversion) {
$table->add_field('taskname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'email_certificate_task');
$table->add_field('last_processed', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, '0');
$table->add_field('total_certificate_to_process', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, '0');
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');

// Adding keys to table customcert_task_progress.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
Expand All @@ -257,25 +254,19 @@ function xmldb_customcert_upgrade($oldversion) {
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
// Add a default row to the customcert_task_progress table.
$defaultData = new stdClass();
$defaultData->taskname = 'email_certificate_task';
$defaultData->last_processed = 0;
$defaultData->total_certificate_to_process = 0;
$defaultData->usermodified = get_admin();;
$defaultData->timecreated = time();
$defaultData->timemodified = time();
$defaultdata = new stdClass();
$defaultdata->taskname = 'email_certificate_task';
$defaultdata->last_processed = 0;
$defaultdata->total_certificate_to_process = 0;

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

// Insert the default data into the table.
$DB->insert_record('customcert_task_progress', $defaultData);
$DB->insert_record('customcert_task_progress', $defaultdata);
}

// Customcert savepoint reached.
upgrade_mod_savepoint(true, 2023042408, 'customcert');
}


return true;
}
12 changes: 6 additions & 6 deletions lang/en/customcert.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,11 @@
// Acess API.
$string['customcert:managelanguages'] = 'Manage language on edit form';

$string['certificatesperrun'] = 'Certificates Per Run';
$string['certificatesperrun_desc'] = 'Enter the number of certificates to process per scheduled task run where 0 means it will process all certifiate.';
$string['includeinnotvisiblecourses'] = 'Include Certificates in Not Visible Courses';
$string['certificatesperrun'] = 'Certificates per run';
$string['certificatesperrun_desc'] = 'Enter the number of certificates to process per scheduled task run <b>where 0 means it will process all certificates</b>.';
$string['includeinnotvisiblecourses'] = 'Include certificates in hidden courses';
$string['includeinnotvisiblecourses_desc'] = 'Check this box to include certificates in courses that are not visible to the user.';
$string['certificateexecutionperiod'] = 'Certificate Execution Period';
$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.';
$string['scheduledtaskconfigheading'] = 'Scheduled Task Configuration';
$string['certificateexecutionperiod'] = 'Certificate execution period';
$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>';
$string['scheduledtaskconfigheading'] = 'Scheduled task configuration';
$string['scheduledtaskconfigdesc'] = 'Configure the settings for the scheduled task that processes certificates.';
9 changes: 5 additions & 4 deletions settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,18 @@
get_string('uploadimage', 'customcert'), new moodle_url('/mod/customcert/upload_image.php'), ''));


$settings->add(new admin_setting_heading('scheduledtaskconfig',
$settings->add(new admin_setting_heading('scheduledtaskconfig',
get_string('scheduledtaskconfigheading', 'customcert'),
get_string('scheduledtaskconfigdesc', 'customcert')));

$settings->add(new admin_setting_configtext('customcert/certificatesperrun', get_string('certificatesperrun', 'customcert'),
$settings->add(new admin_setting_configtext('customcert/certificatesperrun',
get_string('certificatesperrun', 'customcert'),
get_string('certificatesperrun_desc', 'customcert'),
50, PARAM_INT));
0, PARAM_INT));
$settings->add(new admin_setting_configcheckbox('customcert/includeinnotvisiblecourses',
get_string('includeinnotvisiblecourses', 'customcert'),
get_string('includeinnotvisiblecourses_desc', 'customcert'), 0));
$settings->add(
$settings->add(
new admin_setting_configduration(
'customcert/certificateexecutionperiod',
new \lang_string('certificateexecutionperiod', 'customcert'),
Expand Down

0 comments on commit 3172f46

Please sign in to comment.