From bcc50f70677750ffd7efa23a451ad8abebbc6f26 Mon Sep 17 00:00:00 2001 From: Oscar Nadjar Date: Fri, 19 Jul 2024 10:18:24 -0500 Subject: [PATCH] DEF-2908: Refactoring task to issuetask and adhoc email tak, setting to improve performance DEF-2908: Improving code a little bit DEF-2908: Fixing pipelines and adding adhoc task test DEF-2908: Fixing pipeline error DEF-2908: Fixing unnecesary changes DEF-2908: Fixing test --- classes/task/email_certificate_task.php | 340 +++++++---------------- classes/task/issue_certificates_task.php | 191 +++++++++++++ db/tasks.php | 2 +- lang/en/customcert.php | 18 ++ settings.php | 4 + tests/email_certificate_task_test.php | 139 ++++++++- version.php | 2 +- 7 files changed, 437 insertions(+), 259 deletions(-) create mode 100644 classes/task/issue_certificates_task.php diff --git a/classes/task/email_certificate_task.php b/classes/task/email_certificate_task.php index 80454565..cf6e9cf5 100644 --- a/classes/task/email_certificate_task.php +++ b/classes/task/email_certificate_task.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * A scheduled task for emailing certificates. + * An adhoc task for emailing certificates. * * @package mod_customcert * @copyright 2017 Mark Nelson @@ -26,13 +26,13 @@ use mod_customcert\helper; /** - * A scheduled task for emailing certificates. + * An adhoc task for emailing certificates per issue. * * @package mod_customcert * @copyright 2017 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class email_certificate_task extends \core\task\scheduled_task { +class email_certificate_task extends \core\task\adhoc_task { /** * Get a descriptive name for this task (shown to admins). @@ -48,107 +48,36 @@ public function get_name() { */ public function execute() { global $DB; + $customdata = $this->get_custom_data(); - // 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_email_task_prgrs', ['taskname' => 'email_certificate_task']); - $lastprocessed = $taskprogress->last_processed; - - // Get all the certificates that have requested someone get emailed. - $emailotherslengthsql = $DB->sql_length('c.emailothers'); + $issueid = $customdata->issueid; + $customcertid = $customdata->customcertid; $sql = "SELECT c.*, ct.id as templateid, ct.name as templatename, ct.contextid, co.id as courseid, co.fullname as coursefullname, co.shortname as courseshortname FROM {customcert} c - JOIN {customcert_templates} ct - ON c.templateid = ct.id - JOIN {course} co - ON c.course = co.id"; - - // Add JOIN with mdl_course_categories to exclude certificates from hidden courses. - $sql .= " JOIN {course_categories} cat ON co.category = cat.id"; - - // Only get certificates where we have to email someone. - $sql .= " WHERE (c.emailstudents = :emailstudents - OR c.emailteachers = :emailteachers - OR $emailotherslengthsql >= 3)"; + JOIN {customcert_templates} ct ON c.templateid = ct.id + JOIN {course} co ON c.course = co.id + WHERE c.id = :id"; - // Check the includeinnotvisiblecourses configuration. - 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) { - // 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; - } - - // Execute the SQL query. - if (!$customcerts = $DB->get_records_sql($sql, ['emailstudents' => 1, 'emailteachers' => 1] + $params)) { - return; - } + $customcert = $DB->get_record_sql($sql, ['id' => $customcertid]); // The renderers used for sending emails. $page = new \moodle_page(); $htmlrenderer = $page->get_renderer('mod_customcert', 'email', 'htmlemail'); $textrenderer = $page->get_renderer('mod_customcert', 'email', 'textemail'); - // Store the total count of certificates in the database. - $totalcertificatestoprocess = count($customcerts); - $DB->set_field('customcert_email_task_prgrs', 'total_certificate_to_process', $totalcertificatestoprocess, [ - 'taskname' => 'email_certificate_task', - ]); - - // Check if we need to reset and start from the beginning. - if ($lastprocessed >= count($customcerts)) { - $lastprocessed = 0; // Reset to the beginning. - } - - 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, $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 - JOIN {customcert_pages} cp - ON cp.id = ce.pageid - JOIN {customcert_templates} ct - ON ct.id = cp.templateid - WHERE ct.contextid = :contextid"; - if (!$DB->record_exists_sql($sql, ['contextid' => $customcert->contextid])) { - continue; - } - - // Get the context. - $context = \context::instance_by_id($customcert->contextid); + // Get the context. + $context = \context::instance_by_id($customcert->contextid); - // Get the person we are going to send this email on behalf of. - $userfrom = \core_user::get_noreply_user(); + // Get the person we are going to send this email on behalf of. + $userfrom = \core_user::get_noreply_user(); - // Store teachers for later. - $teachers = get_enrolled_users($context, 'moodle/course:update'); + // Store teachers for later. + $teachers = get_enrolled_users($context, 'moodle/course:update'); - $courseshortname = format_string($customcert->courseshortname, true, ['context' => $context]); - $coursefullname = format_string($customcert->coursefullname, true, ['context' => $context]); - $certificatename = format_string($customcert->name, true, ['context' => $context]); + $courseshortname = format_string($customcert->courseshortname, true, ['context' => $context]); + $coursefullname = format_string($customcert->coursefullname, true, ['context' => $context]); + $certificatename = format_string($customcert->name, true, ['context' => $context]); // Used to create the email subject. $info = new \stdClass(); @@ -157,176 +86,93 @@ public function execute() { $info->coursefullname = $coursefullname; $info->certificatename = $certificatename; - // Get a list of all the issues. - $userfields = helper::get_all_user_name_fields('u'); - $sql = "SELECT u.id, u.username, $userfields, u.email, ci.id as issueid, ci.emailed - FROM {customcert_issues} ci - JOIN {user} u - ON ci.userid = u.id - WHERE ci.customcertid = :customcertid"; - $issuedusers = $DB->get_records_sql($sql, ['customcertid' => $customcert->id]); - - // Now, get a list of users who can Manage the certificate. - $userswithmanage = get_users_by_capability($context, 'mod/customcert:manage', 'u.id'); - - // Get the context of the Custom Certificate module. - $cm = get_coursemodule_from_instance('customcert', $customcert->id, $customcert->course); - $context = \context_module::instance($cm->id); - - // Now, get a list of users who can view and issue the certificate but have not yet. - // Get users with the mod/customcert:receiveissue capability in the Custom Certificate module context. - $userswithissue = get_users_by_capability($context, 'mod/customcert:receiveissue'); - // Get users with mod/customcert:view capability. - $userswithview = get_users_by_capability($context, 'mod/customcert:view'); - // Users with both mod/customcert:view and mod/customcert:receiveissue capabilities. - $userswithissueview = array_intersect_key($userswithissue, $userswithview); - - foreach ($userswithissueview as $enroluser) { - // Check if the user has already been issued. - if (in_array($enroluser->id, array_keys((array)$issuedusers))) { - continue; - } - - // Don't want to email those with the capability to manage the certificate. - if (in_array($enroluser->id, array_keys((array)$userswithmanage))) { - continue; - } - - // 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; - } - - // Check that they have passed the required time. - if (!empty($customcert->requiredtime)) { - if (\mod_customcert\certificate::get_course_time($customcert->courseid, - $enroluser->id) < ($customcert->requiredtime * 60)) { - continue; - } - } - - // Ensure the cert hasn't already been issued, e.g via the UI (view.php) - a race condition. - $issueid = $DB->get_field('customcert_issues', 'id', - ['userid' => $enroluser->id, 'customcertid' => $customcert->id], IGNORE_MULTIPLE); - if (empty($issueid)) { - // Ok, issue them the certificate. - $issueid = \mod_customcert\certificate::issue_certificate($customcert->id, $enroluser->id); - } - - // Add them to the array so we email them. - $enroluser->issueid = $issueid; - $enroluser->emailed = 0; - $issuedusers[] = $enroluser; - } + // Get the information about the user and the certificate issue. + $userfields = helper::get_all_user_name_fields('u'); + $sql = "SELECT u.id, u.username, $userfields, u.email, ci.id as issueid, ci.emailed + FROM {customcert_issues} ci + JOIN {user} u + ON ci.userid = u.id + WHERE ci.customcertid = :customcertid + AND ci.id = :issueid"; + $user = $DB->get_record_sql($sql, ['customcertid' => $customcertid, 'issueid' => $issueid]); + + // Create a directory to store the PDF we will be sending. + $tempdir = make_temp_directory('certificate/attachment'); + if (!$tempdir) { + return; + } - // Remove all the users who have already been emailed. - foreach ($issuedusers as $key => $issueduser) { - if ($issueduser->emailed) { - unset($issuedusers[$key]); - } - } + // Setup the user for the cron. + \core\cron::setup_user($user); + + $userfullname = fullname($user); + $info->userfullname = $userfullname; + + // Now, get the PDF. + $template = new \stdClass(); + $template->id = $customcert->templateid; + $template->name = $customcert->templatename; + $template->contextid = $customcert->contextid; + $template = new \mod_customcert\template($template); + $filecontents = $template->generate_pdf(false, $user->id, true); + + // Set the name of the file we are going to send. + $filename = $courseshortname . '_' . $certificatename; + $filename = \core_text::entities_to_utf8($filename); + $filename = strip_tags($filename); + $filename = rtrim($filename, '.'); + $filename = str_replace('&', '_', $filename) . '.pdf'; + + // Create the file we will be sending. + $tempfile = $tempdir . '/' . md5(microtime() . $user->id) . '.pdf'; + file_put_contents($tempfile, $filecontents); + + if ($customcert->emailstudents) { + $renderable = new \mod_customcert\output\email_certificate(true, $userfullname, $courseshortname, + $coursefullname, $certificatename, $context->instanceid); + + $subject = get_string('emailstudentsubject', 'customcert', $info); + $message = $textrenderer->render($renderable); + $messagehtml = $htmlrenderer->render($renderable); + email_to_user($user, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message, + $messagehtml, $tempfile, $filename); + } - // If there are no users to email, we can return early. - if (!$issuedusers) { - continue; - } + if ($customcert->emailteachers) { + $renderable = new \mod_customcert\output\email_certificate(false, $userfullname, $courseshortname, + $coursefullname, $certificatename, $context->instanceid); - // Create a directory to store the PDF we will be sending. - $tempdir = make_temp_directory('certificate/attachment'); - if (!$tempdir) { - return; + $subject = get_string('emailnonstudentsubject', 'customcert', $info); + $message = $textrenderer->render($renderable); + $messagehtml = $htmlrenderer->render($renderable); + foreach ($teachers as $teacher) { + email_to_user($teacher, $userfrom, html_entity_decode($subject, ENT_COMPAT), + $message, $messagehtml, $tempfile, $filename); } + } - $issueids = []; - // Now, email the people we need to. - foreach ($issuedusers as $user) { - // Set up the user. - \core\cron::setup_user($user); - - $userfullname = fullname($user); - $info->userfullname = $userfullname; - - // Now, get the PDF. - $template = new \stdClass(); - $template->id = $customcert->templateid; - $template->name = $customcert->templatename; - $template->contextid = $customcert->contextid; - $template = new \mod_customcert\template($template); - $filecontents = $template->generate_pdf(false, $user->id, true); - - // Set the name of the file we are going to send. - $filename = $courseshortname . '_' . $certificatename; - $filename = \core_text::entities_to_utf8($filename); - $filename = strip_tags($filename); - $filename = rtrim($filename, '.'); - $filename = str_replace('&', '_', $filename) . '.pdf'; - - // Create the file we will be sending. - $tempfile = $tempdir . '/' . md5(microtime() . $user->id) . '.pdf'; - file_put_contents($tempfile, $filecontents); - - if ($customcert->emailstudents) { - $renderable = new \mod_customcert\output\email_certificate(true, $userfullname, $courseshortname, - $coursefullname, $certificatename, $context->instanceid); - - $subject = get_string('emailstudentsubject', 'customcert', $info); - $message = $textrenderer->render($renderable); - $messagehtml = $htmlrenderer->render($renderable); - email_to_user($user, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message, - $messagehtml, $tempfile, $filename); - } - - if ($customcert->emailteachers) { - $renderable = new \mod_customcert\output\email_certificate(false, $userfullname, $courseshortname, - $coursefullname, $certificatename, $context->instanceid); + if (!empty($customcert->emailothers)) { + $others = explode(',', $customcert->emailothers); + foreach ($others as $email) { + $email = trim($email); + if (validate_email($email)) { + $renderable = new \mod_customcert\output\email_certificate(false, $userfullname, + $courseshortname, $coursefullname, $certificatename, $context->instanceid); $subject = get_string('emailnonstudentsubject', 'customcert', $info); $message = $textrenderer->render($renderable); $messagehtml = $htmlrenderer->render($renderable); - foreach ($teachers as $teacher) { - email_to_user($teacher, $userfrom, html_entity_decode($subject, ENT_COMPAT), - $message, $messagehtml, $tempfile, $filename); - } - } - if (!empty($customcert->emailothers)) { - $others = explode(',', $customcert->emailothers); - foreach ($others as $email) { - $email = trim($email); - if (validate_email($email)) { - $renderable = new \mod_customcert\output\email_certificate(false, $userfullname, - $courseshortname, $coursefullname, $certificatename, $context->instanceid); - - $subject = get_string('emailnonstudentsubject', 'customcert', $info); - $message = $textrenderer->render($renderable); - $messagehtml = $htmlrenderer->render($renderable); - - $emailuser = new \stdClass(); - $emailuser->id = -1; - $emailuser->email = $email; - email_to_user($emailuser, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message, - $messagehtml, $tempfile, $filename); - } - } + $emailuser = new \stdClass(); + $emailuser->id = -1; + $emailuser->email = $email; + email_to_user($emailuser, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message, + $messagehtml, $tempfile, $filename); } - - // Set the field so that it is emailed. - $issueids[] = $user->issueid; - } - - if (!empty($issueids)) { - list($sql, $params) = $DB->get_in_or_equal($issueids, SQL_PARAMS_NAMED, 'id'); - $DB->set_field_select('customcert_issues', 'emailed', 1, 'id ' . $sql, $params); } } - // Update the last processed position, if run in batches. - if ($certificatesperrun > 0) { - $newlastprocessed = $lastprocessed + count($certificates); - $DB->set_field('customcert_email_task_prgrs', 'last_processed', $newlastprocessed, [ - 'taskname' => 'email_certificate_task', - ]); - } + // Set the field so that it is emailed. + $DB->set_field('customcert_issues', 'emailed', 1, ['id' => $issueid]); } } diff --git a/classes/task/issue_certificates_task.php b/classes/task/issue_certificates_task.php new file mode 100644 index 00000000..0250b31b --- /dev/null +++ b/classes/task/issue_certificates_task.php @@ -0,0 +1,191 @@ +. + +/** + * A scheduled task for issuing certificates that have requested someone get emailed. + * + * @package mod_customcert + * @copyright 2024 Oscar Nadjar + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_customcert\task; + +use mod_customcert\helper; + +/** + * A scheduled task for issuing certificates that have requested someone get emailed. + * + * @package mod_customcert + * @copyright 2024 Oscar Nadjar + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class issue_certificates_task extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('issuecertificate', 'customcert'); + } + + /** + * Execute. + */ + 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'); + $offset = (int)get_config('customcert', 'certificate_offset'); + + // We are going to issue certificates that have requested someone get emailed. + $emailotherslengthsql = $DB->sql_length('c.emailothers'); + $sql = "SELECT c.*, ct.id as templateid, ct.name as templatename, ct.contextid, co.id as courseid, + co.fullname as coursefullname, co.shortname as courseshortname + FROM {customcert} c + JOIN {customcert_templates} ct ON c.templateid = ct.id + JOIN {course} co ON c.course = co.id + JOIN {course_categories} cat ON co.category = cat.id + LEFT JOIN {customcert_issues} ci ON c.id = ci.customcertid"; + + // Add conditions to exclude certificates from hidden courses. + $sql .= " WHERE (c.emailstudents = :emailstudents + OR c.emailteachers = :emailteachers + OR $emailotherslengthsql >= 3)"; + + $params = ['emailstudents' => 1, 'emailteachers' => 1]; + + // Check the includeinnotvisiblecourses configuration. + 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) { + // Include courses with no end date or end date greater than the specified period. + $sql .= " AND (co.enddate > :enddate OR (co.enddate = 0 AND ci.timecreated > :enddate2))"; + $params['enddate'] = time() - $certificateexecutionperiod; + $params['enddate2'] = $params['enddate']; + } + + $sql .= " GROUP BY c.id, ct.id, ct.name, ct.contextid, co.id, co.fullname, co.shortname"; + + // Execute the SQL query. + $customcerts = $DB->get_records_sql($sql, $params, $offset, $certificatesperrun); + + // When we get to the end of the list, reset the offset. + set_config('certificate_offset', !empty($customcerts) ? $offset + $certificatesperrun : 0, 'customcert'); + if (empty($customcerts)) { + return; + } + + foreach ($customcerts as $customcert) { + + // Check if the certificate is hidden, quit early. + $cm = get_course_and_cm_from_instance($customcert->id, 'customcert', $customcert->course)[1]; + if (!$cm->visible) { + continue; + } + + // Do not process an empty certificate. + $sql = "SELECT ce.* + FROM {customcert_elements} ce + JOIN {customcert_pages} cp ON cp.id = ce.pageid + JOIN {customcert_templates} ct ON ct.id = cp.templateid + WHERE ct.contextid = :contextid"; + if (!$DB->record_exists_sql($sql, ['contextid' => $customcert->contextid])) { + continue; + } + + // Get the context. + $context = \context::instance_by_id($customcert->contextid); + + // Get a list of all the issues. + $sql = "SELECT u.id + FROM {customcert_issues} ci + JOIN {user} u + ON ci.userid = u.id + WHERE ci.customcertid = :customcertid + AND ci.emailed = 1"; + $issuedusers = $DB->get_records_sql($sql, ['customcertid' => $customcert->id]); + + // Now, get a list of users who can Manage the certificate. + $userswithmanage = get_users_by_capability($context, 'mod/customcert:manage', 'u.id'); + + // Get the context of the Custom Certificate module. + $cmcontext = \context_module::instance($cm->id); + + // Now, get a list of users who can view and issue the certificate but have not yet. + // Get users with the mod/customcert:receiveissue capability in the Custom Certificate module context. + $userswithissue = get_users_by_capability($cmcontext, 'mod/customcert:receiveissue'); + // Get users with mod/customcert:view capability. + $userswithview = get_users_by_capability($cmcontext, 'mod/customcert:view'); + // Users with both mod/customcert:view and mod/customcert:receiveissue cabapilities. + $userswithissueview = array_intersect_key($userswithissue, $userswithview); + + foreach ($userswithissueview as $enroluser) { + // Check if the user has already been issued and emailed. + if (in_array($enroluser->id, array_keys((array)$issuedusers))) { + continue; + } + + // Don't want to issue to teachers. + if (in_array($enroluser->id, array_keys((array)$userswithmanage))) { + continue; + } + + // 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; + } + + // Check that they have passed the required time. + if (!empty($customcert->requiredtime)) { + if (\mod_customcert\certificate::get_course_time($customcert->courseid, + $enroluser->id) < ($customcert->requiredtime * 60)) { + continue; + } + } + + // Ensure the cert hasn't already been issued, e.g via the UI (view.php) - a race condition. + $issue = $DB->get_record('customcert_issues', + ['userid' => $enroluser->id, 'customcertid' => $customcert->id], 'id, emailed'); + + // Ok, issue them the certificate. + $issueid = empty($issue) ? + \mod_customcert\certificate::issue_certificate($customcert->id, $enroluser->id) : $issue->id; + + // Validate issueid and one last check for emailed. + if (!empty($issueid) && empty($issue->emailed)) { + // We create a new adhoc task to send the email. + $task = new \mod_customcert\task\email_certificate_task(); + $task->set_custom_data(['issueid' => $issueid, 'customcertid' => $customcert->id]); + $useadhoc = get_config('customcert', 'useadhoc'); + if ($useadhoc) { + \core\task\manager::queue_adhoc_task($task); + } else { + $task->execute(); + } + } + } + } + } +} diff --git a/db/tasks.php b/db/tasks.php index fd2ddb07..cfde7be3 100644 --- a/db/tasks.php +++ b/db/tasks.php @@ -27,7 +27,7 @@ $tasks = [ [ - 'classname' => 'mod_customcert\task\email_certificate_task', + 'classname' => 'mod_customcert\task\issue_certificates_task', 'blocking' => 0, 'minute' => '*', 'hour' => '*', diff --git a/lang/en/customcert.php b/lang/en/customcert.php index c4f051b1..48700d55 100644 --- a/lang/en/customcert.php +++ b/lang/en/customcert.php @@ -238,3 +238,21 @@ $string['verifycertificatedesc'] = 'This link will take you to a new screen where you will be able to verify certificates on the site'; $string['width'] = 'Width'; $string['width_help'] = 'This is the width of the certificate PDF in mm. For reference an A4 piece of paper is 210mm wide and a letter is 216mm wide.'; + +$string['userlanguage'] = 'Use user preferences'; +$string['languageoptions'] = 'Force Certificate Language'; +$string['userlanguage_help'] = 'You can force the language of the certificate to override the user\'s language preferences.'; + +// Scheduled task configuration performance settings. +$string['certificateexecutionperiod'] = 'Ignore inactive certificates older than'; +$string['certificateexecutionperiod_desc'] = 'If not 0, the task will not process certificates whose course has been inactive or the last issue is older than the configured time. This may help to improve the performance of the scheduled task.'; +$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 certificates.'; +$string['customcert:managelanguages'] = 'Manage language on edit form'; +$string['includeinnotvisiblecourses'] = 'Include certificates in hidden courses'; +$string['includeinnotvisiblecourses_desc'] = 'Certificates from hidden courses are not proccesed by default. If you want to include them, enable this setting.'; +$string['scheduledtaskconfigheading'] = 'Scheduled task configuration'; +$string['scheduledtaskconfigdesc'] = 'Configure the settings for the scheduled task that processes certificates.'; +$string['issuecertificate'] = 'Issue certificates task'; +$string['useadhoc'] = 'Use Email Certificate adhoc task'; +$string['useadhoc_desc'] = 'If enabled, the email will be processed on an adhoc task created per issue. If disabled, the email will be processed by the scheduled task. This may help to improve the performance of the scheduled task.'; diff --git a/settings.php b/settings.php index 6de6166e..955430de 100644 --- a/settings.php +++ b/settings.php @@ -70,6 +70,10 @@ get_string('includeinnotvisiblecourses', 'customcert'), get_string('includeinnotvisiblecourses_desc', 'customcert'), 0)); +$settings->add(new admin_setting_configcheckbox('customcert/useadhoc', + get_string('useadhoc', 'customcert'), + get_string('useadhoc_desc', 'customcert'), 0)); + $settings->add(new admin_setting_configduration('customcert/certificateexecutionperiod', get_string('certificateexecutionperiod', 'customcert'), get_string('certificateexecutionperiod_desc', 'customcert'), 365 * DAYSECS)); diff --git a/tests/email_certificate_task_test.php b/tests/email_certificate_task_test.php index d5d1c54c..bcf96e01 100644 --- a/tests/email_certificate_task_test.php +++ b/tests/email_certificate_task_test.php @@ -30,6 +30,7 @@ use context_course; use advanced_testcase; use mod_customcert\task\email_certificate_task; +use mod_customcert\task\issue_certificates_task; /** * Unit tests for the email certificate task. @@ -47,11 +48,13 @@ final class email_certificate_task_test extends advanced_testcase { */ public function setUp(): void { $this->resetAfterTest(); + set_config('certificateexecutionperiod', 0, 'customcert'); } /** * Tests the email certificate task when there are no elements. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_no_elements(): void { @@ -69,7 +72,7 @@ public function test_email_certificates_no_elements(): void { // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -80,6 +83,7 @@ public function test_email_certificates_no_elements(): void { /** * Tests the email certificate task for users without a capability to receive a certificate. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_no_cap(): void { @@ -120,7 +124,7 @@ public function test_email_certificates_no_cap(): void { // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -131,6 +135,7 @@ public function test_email_certificates_no_cap(): void { /** * Tests the email certificate task for students. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_students(): void { @@ -180,7 +185,7 @@ public function test_email_certificates_students(): void { // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -205,7 +210,7 @@ public function test_email_certificates_students(): void { // Now, run the task again and ensure we did not issue any more certificates. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -218,6 +223,7 @@ public function test_email_certificates_students(): void { /** * Tests the email certificate task for teachers. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_teachers(): void { @@ -261,7 +267,7 @@ public function test_email_certificates_teachers(): void { // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -278,6 +284,7 @@ public function test_email_certificates_teachers(): void { /** * Tests the email certificate task for others. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_others(): void { @@ -316,7 +323,7 @@ public function test_email_certificates_others(): void { // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -333,6 +340,7 @@ public function test_email_certificates_others(): void { /** * Tests the email certificate task when the certificate is not visible. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_students_not_visible(): void { @@ -372,7 +380,7 @@ public function test_email_certificates_students_not_visible(): void { // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -387,6 +395,7 @@ public function test_email_certificates_students_not_visible(): void { /** * Tests the email certificate task when the student has not met the required time for the course. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_students_havent_met_required_time(): void { @@ -426,7 +435,7 @@ public function test_email_certificates_students_havent_met_required_time(): voi // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -441,6 +450,7 @@ public function test_email_certificates_students_havent_met_required_time(): voi /** * Tests the email certificate task when the student has not met the completion criteria. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_students_havent_met_required_criteria(): void { @@ -507,7 +517,7 @@ public function test_email_certificates_students_havent_met_required_criteria(): // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -522,6 +532,7 @@ public function test_email_certificates_students_havent_met_required_criteria(): /** * Tests the email certificate task when the student has met the completion criteria. * + * @covers \mod_customcert\task\issue_certificates_task * @covers \mod_customcert\task\email_certificate_task */ public function test_email_certificates_students_have_met_required_criteria(): void { @@ -592,7 +603,7 @@ public function test_email_certificates_students_have_met_required_criteria(): v // Run the task. $sink = $this->redirectEmails(); - $task = new email_certificate_task(); + $task = new issue_certificates_task(); $task->execute(); $emails = $sink->get_messages(); @@ -603,4 +614,112 @@ public function test_email_certificates_students_have_met_required_criteria(): v // Confirm an email was sent. $this->assertCount(1, $emails); } + + /** + * Tests the email certificate task running adhoc. + * + * @covers \mod_customcert\task\email_certificate_task + * @covers \mod_customcert\task\issue_certificates_task + */ + public function test_email_certificates_adhoc(): void { + global $CFG, $DB; + + set_config('useadhoc', 1, 'customcert'); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create some users. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(['firstname' => 'Teacher', 'lastname' => 'One']); + + // Enrol two of them in the course as students. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + + // Enrol one of the users as a teacher. + $this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleids['editingteacher']); + + // Create a custom certificate. + $customcert = $this->getDataGenerator()->create_module('customcert', ['course' => $course->id, + 'emailstudents' => 1]); + + // Create template object. + $template = new stdClass(); + $template->id = $customcert->templateid; + $template->name = 'A template'; + $template->contextid = context_course::instance($course->id)->id; + $template = new template($template); + + // Add a page to this template. + $pageid = $template->add_page(); + + // Add an element to the page. + $element = new stdClass(); + $element->pageid = $pageid; + $element->name = 'Image'; + $DB->insert_record('customcert_elements', $element); + + // Ok, now issue this to one user. + \mod_customcert\certificate::issue_certificate($customcert->id, $user1->id); + + // Confirm there is only one entry in this table. + $this->assertEquals(1, $DB->count_records('customcert_issues')); + + // Run the task. + $sink = $this->redirectEmails(); + $task = new issue_certificates_task(); + $task->execute(); + $emails = $sink->get_messages(); + + // Get the issues from the issues table now. + $issues = $DB->get_records('customcert_issues'); + $this->assertCount(2, $issues); + + // Confirm that it wasn't marked as emailed and was not issued to the teacher. + foreach ($issues as $issue) { + $this->assertEquals(0, $issue->emailed); + $this->assertNotEquals($user3->id, $issue->userid); + } + + // Now we send emails to the two users using the adhoc method. + $this->assertCount(0, $emails); + $issues = array_values($issues); + $task = new email_certificate_task(); + $task->set_custom_data((object)['issueid' => $issues[0]->id, 'customcertid' => $customcert->id]); + $task->execute(); + $task->set_custom_data((object)['issueid' => $issues[1]->id, 'customcertid' => $customcert->id]); + $task->execute(); + $emails = $sink->get_messages(); + + // Get the issues from the issues table now. + $issues = $DB->get_records('customcert_issues'); + // Confirm that it wasn't marked as emailed and was not issued to the teacher. + foreach ($issues as $issue) { + $this->assertEquals(1, $issue->emailed); + $this->assertNotEquals($user3->id, $issue->userid); + } + + // Confirm that we sent out emails to the two users. + $this->assertCount(2, $emails); + + $this->assertEquals($CFG->noreplyaddress, $emails[0]->from); + $this->assertEquals($user1->email, $emails[0]->to); + + $this->assertEquals($CFG->noreplyaddress, $emails[1]->from); + $this->assertEquals($user2->email, $emails[1]->to); + + // Now, run the task again and ensure we did not issue any more certificates. + $sink = $this->redirectEmails(); + $task = new issue_certificates_task(); + $task->execute(); + $emails = $sink->get_messages(); + + $issues = $DB->get_records('customcert_issues'); + + $this->assertCount(2, $issues); + $this->assertCount(0, $emails); + } } diff --git a/version.php b/version.php index e9592dd8..25a97cd6 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); -$plugin->version = 2024042203; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2024042204; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2024042200; // Requires this Moodle version (4.4). $plugin->cron = 0; // Period for cron to check this module (secs). $plugin->component = 'mod_customcert';