From 77b557945971f3acebc9b47a027c9ab3a6e8bd9a Mon Sep 17 00:00:00 2001 From: Ibai Mutiloa Date: Tue, 9 Sep 2025 14:31:42 +0000 Subject: [PATCH 1/5] lehen bertsioa --- classes/task/email_certificate_task.php | 13 +++++-- lib.php | 48 +++++++++++++++++++++++++ version.php | 2 +- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/classes/task/email_certificate_task.php b/classes/task/email_certificate_task.php index c3968334..ba690947 100644 --- a/classes/task/email_certificate_task.php +++ b/classes/task/email_certificate_task.php @@ -47,7 +47,8 @@ public function get_name() { * Execute. */ public function execute() { - global $DB; + global $DB; + require_once(__DIR__ . '/../../../lib.php'); $customdata = $this->get_custom_data(); if (empty($customdata) || empty($customdata->issueid) || empty($customdata->customcertid)) { @@ -57,7 +58,7 @@ public function execute() { $issueid = (int)$customdata->issueid; $customcertid = (int)$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 + co.fullname as coursefullname, co.shortname as courseshortname, co.lang as courselang FROM {customcert} c JOIN {customcert_templates} ct ON c.templateid = ct.id JOIN {course} co ON c.course = co.id @@ -68,6 +69,14 @@ public function execute() { return; } + // Build a course object for language selection. + $course = new \stdClass(); + $course->id = $customcert->courseid; + $course->lang = $customcert->courselang ?? ''; + + // Force the correct language for certificate email generation. + mod_customcert_force_language_for_certificate($customcert, $course); + // The renderers used for sending emails. $page = new \moodle_page(); $htmlrenderer = $page->get_renderer('mod_customcert', 'email', 'htmlemail'); diff --git a/lib.php b/lib.php index 69afda51..93b67121 100644 --- a/lib.php +++ b/lib.php @@ -1,4 +1,6 @@ force_language) + * 2. Course language ($course->lang) + * 3. User language ($USER->lang) + * 4. Site default language ($CFG->lang) + * + * @param stdClass $certificate Certificate object (should have force_language property if used) + * @param stdClass $course Course object (should have lang property) + * @return bool True if language was forced, false otherwise + */ +function mod_customcert_force_language_for_certificate($certificate, $course): bool { + global $USER, $CFG; + + $forced = false; + $activelangs = get_string_manager()->get_list_of_translations(); + + // 1. Priority: certificate-specific language + $lang = $certificate->force_language ?? ''; + + // 2. Priority: course language + if (empty($lang) && !empty($course->lang)) { + $lang = $course->lang; + } + + // 3. Priority: user language + if (empty($lang) && !empty($USER->lang)) { + $lang = $USER->lang; + } + + // 4. Priority: site default + if (empty($lang)) { + $lang = $CFG->lang; + } + + // Only force if valid and different from current + if (!empty($lang) && array_key_exists($lang, $activelangs) && $lang != current_language()) { + force_current_language($lang); + $forced = true; + } + + return $forced; +} diff --git a/version.php b/version.php index c4c7e3cd..d1a438c9 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); -$plugin->version = 2024042213; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2024042214; // 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'; From 0786e70d878141c1bf88d433d791e950251c1876 Mon Sep 17 00:00:00 2001 From: Ibai Mutiloa Date: Wed, 10 Sep 2025 11:08:41 +0000 Subject: [PATCH 2/5] Improvement #717 --- classes/task/email_certificate_task.php | 305 +++++++++++++++++------- lib.php | 1 - version.php | 2 +- 3 files changed, 218 insertions(+), 90 deletions(-) diff --git a/classes/task/email_certificate_task.php b/classes/task/email_certificate_task.php index ba690947..45e5aa61 100644 --- a/classes/task/email_certificate_task.php +++ b/classes/task/email_certificate_task.php @@ -47,16 +47,24 @@ public function get_name() { * Execute. */ public function execute() { - global $DB; - require_once(__DIR__ . '/../../../lib.php'); + global $CFG, $DB; + + // Force error_log output to a dedicated debug file for all executions. + ini_set('error_log', '/tmp/customcert_debug.log'); + error_log('Customcert debug: script started'); + + require_once(__DIR__ . '/../../lib.php'); $customdata = $this->get_custom_data(); if (empty($customdata) || empty($customdata->issueid) || empty($customdata->customcertid)) { + error_log('Customcert email: Missing custom data'); return; } $issueid = (int)$customdata->issueid; $customcertid = (int)$customdata->customcertid; + + // Get certificate and course information $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, co.lang as courselang FROM {customcert} c @@ -66,65 +74,58 @@ public function execute() { $customcert = $DB->get_record_sql($sql, ['id' => $customcertid]); if (!$customcert) { + error_log('Customcert email: Certificate not found'); return; } - // Build a course object for language selection. - $course = new \stdClass(); - $course->id = $customcert->courseid; - $course->lang = $customcert->courselang ?? ''; - - // Force the correct language for certificate email generation. - mod_customcert_force_language_for_certificate($customcert, $course); - - // 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'); - - // 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(); - - $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(); - $info->coursename = $courseshortname; // Added for BC, so users who have edited the string don't lose this value. - $info->courseshortname = $courseshortname; - $info->coursefullname = $coursefullname; - $info->certificatename = $certificatename; - - // Get the information about the user and the certificate issue. + // Get user and issue information $userfields = helper::get_all_user_name_fields('u'); - $sql = "SELECT u.id, u.username, $userfields, u.email, u.mailformat, ci.id as issueid, ci.emailed + $sql = "SELECT u.id, u.username, $userfields, u.email, u.mailformat, ci.id as issueid, ci.emailed, u.lang as lang FROM {customcert_issues} ci - JOIN {user} u - ON ci.userid = u.id - WHERE ci.customcertid = :customcertid - AND ci.id = :issueid"; + 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]); + if (!$user) { + error_log('Customcert email: User or issue not found'); return; } - // Create a directory to store the PDF we will be sending. - $tempdir = make_temp_directory('certificate/attachment'); - if (!$tempdir) { - return; + // Store original language to restore later + $originallang = current_language(); + + // --- LANGUAGE SELECTION LOGIC (same hierarchy as certificate view) --- + $lang = $this->resolve_certificate_language($customcert, $user); + error_log('Customcert email: Final resolved language: ' . $lang); + + // Force the resolved language + $activelangs = get_string_manager()->get_list_of_translations(); + if (!empty($lang) && array_key_exists($lang, $activelangs)) { + force_current_language($lang); + get_string_manager()->reset_caches(); + error_log('Customcert email: Language forced to: ' . $lang); + + // Test string fetch after forcing language + $teststring = get_string('emailstudentsubject', 'customcert'); + error_log('Customcert email: Test string after language force: ' . $teststring); } - // Setup the user for the cron. + // Get the context + $context = \context::instance_by_id($customcert->contextid); + + // Setup the user for the cron \core\cron::setup_user($user); - $userfullname = fullname($user); - $info->userfullname = $userfullname; + // Create a directory to store the PDF + $tempdir = make_temp_directory('certificate/attachment'); + if (!$tempdir) { + error_log('Customcert email: Could not create temp directory'); + // Restore original language before returning + force_current_language($originallang); + return; + } - // Now, get the PDF. + // Generate PDF with the forced language $template = new \stdClass(); $template->id = $customcert->templateid; $template->name = $customcert->templatename; @@ -132,65 +133,193 @@ public function execute() { $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. + // Prepare file information + $courseshortname = format_string($customcert->courseshortname, true, ['context' => $context]); + $coursefullname = format_string($customcert->coursefullname, true, ['context' => $context]); + $certificatename = format_string($customcert->name, true, ['context' => $context]); + $userfullname = fullname($user); + + // 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. + // Create the file we will be sending $tempfile = $tempdir . '/' . md5(microtime() . $user->id) . '.pdf'; file_put_contents($tempfile, $filecontents); + // Prepare email information object + $info = new \stdClass(); + $info->coursename = $courseshortname; // Added for BC + $info->courseshortname = $courseshortname; + $info->coursefullname = $coursefullname; + $info->certificatename = $certificatename; + $info->userfullname = $userfullname; + + // Get email renderers + $page = new \moodle_page(); + $htmlrenderer = $page->get_renderer('mod_customcert', 'email', 'htmlemail'); + $textrenderer = $page->get_renderer('mod_customcert', 'email', 'textemail'); + + // Get the person we are going to send this email on behalf of + $userfrom = \core_user::get_noreply_user(); + + // Send email to students 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); + $this->send_email_to_student($user, $userfrom, $info, $context, $htmlrenderer, + $textrenderer, $tempfile, $filename, $userfullname, $courseshortname, + $coursefullname, $certificatename); } + // Send email to teachers if ($customcert->emailteachers) { - $teachers = get_enrolled_users($context, 'moodle/course:update'); - - $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); - } + $this->send_email_to_teachers($context, $userfrom, $info, $htmlrenderer, + $textrenderer, $tempfile, $filename, $userfullname, $courseshortname, + $coursefullname, $certificatename); } + // Send email to others 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); - } - } + $this->send_email_to_others($customcert->emailothers, $userfrom, $info, + $context, $htmlrenderer, $textrenderer, $tempfile, $filename, + $userfullname, $courseshortname, $coursefullname, $certificatename); } - // Set the field so that it is emailed. + // Mark as emailed $DB->set_field('customcert_issues', 'emailed', 1, ['id' => $issueid]); + + error_log('Customcert email: Email sent successfully for issue ' . $issueid); + + // Restore original language + force_current_language($originallang); + get_string_manager()->reset_caches(); + } + + /** + * Resolve the certificate language using the same hierarchy as certificate view + * + * @param object $customcert The certificate record + * @param object $user The user record + * @return string The resolved language code + */ + private function resolve_certificate_language($customcert, $user) { + global $CFG; + + $lang = null; + + // 1. Certificate-specific language (if set) + if (!empty($customcert->force_language)) { + $lang = $customcert->force_language; + error_log('Customcert email: Using certificate-specific language: ' . $lang); + return $lang; + } + + // 2. Course language (if set) + if (!empty($customcert->courselang)) { + $lang = $customcert->courselang; + error_log('Customcert email: Using course language: ' . $lang); + return $lang; + } + + // 3. User profile language (if set) + if (!empty($user->lang)) { + $lang = $user->lang; + error_log('Customcert email: Using user profile language: ' . $lang); + return $lang; + } + + // 4. Site default language + $lang = $CFG->lang; + error_log('Customcert email: Using site default language: ' . $lang); + + return $lang; + } + + /** + * Send email to student + */ + private function send_email_to_student($user, $userfrom, $info, $context, $htmlrenderer, + $textrenderer, $tempfile, $filename, $userfullname, $courseshortname, + $coursefullname, $certificatename) { + + $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); + + // Apply multilang filter to all text content + $subject = format_text($subject, FORMAT_HTML, ['filter' => true, 'context' => $context]); + $message = format_text($message, FORMAT_HTML, ['filter' => true, 'context' => $context]); + $messagehtml = format_text($messagehtml, FORMAT_HTML, ['filter' => true, 'context' => $context]); + + error_log('Customcert email: Sending to student - Subject: ' . $subject); + + email_to_user($user, $userfrom, html_entity_decode($subject, ENT_COMPAT), + $message, $messagehtml, $tempfile, $filename); + } + + /** + * Send email to teachers + */ + private function send_email_to_teachers($context, $userfrom, $info, $htmlrenderer, + $textrenderer, $tempfile, $filename, $userfullname, $courseshortname, + $coursefullname, $certificatename) { + + $teachers = get_enrolled_users($context, 'moodle/course:update'); + + $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); + + // Apply multilang filter + $subject = format_text($subject, FORMAT_HTML, ['filter' => true, 'context' => $context]); + $message = format_text($message, FORMAT_HTML, ['filter' => true, 'context' => $context]); + $messagehtml = format_text($messagehtml, FORMAT_HTML, ['filter' => true, 'context' => $context]); + + foreach ($teachers as $teacher) { + email_to_user($teacher, $userfrom, html_entity_decode($subject, ENT_COMPAT), + $message, $messagehtml, $tempfile, $filename); + } + } + + /** + * Send email to other recipients + */ + private function send_email_to_others($emailothers, $userfrom, $info, $context, + $htmlrenderer, $textrenderer, $tempfile, $filename, $userfullname, + $courseshortname, $coursefullname, $certificatename) { + + $others = explode(',', $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); + + // Apply multilang filter + $subject = format_text($subject, FORMAT_HTML, ['filter' => true, 'context' => $context]); + $message = format_text($message, FORMAT_HTML, ['filter' => true, 'context' => $context]); + $messagehtml = format_text($messagehtml, FORMAT_HTML, ['filter' => true, 'context' => $context]); + + $emailuser = new \stdClass(); + $emailuser->id = -1; + $emailuser->email = $email; + + email_to_user($emailuser, $userfrom, html_entity_decode($subject, ENT_COMPAT), + $message, $messagehtml, $tempfile, $filename); + } + } } -} +} \ No newline at end of file diff --git a/lib.php b/lib.php index 93b67121..aa69f3f1 100644 --- a/lib.php +++ b/lib.php @@ -1,6 +1,5 @@ version = 2024042214; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2024042224; // 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'; From 420c6025dbf56efac67d23bfe6e4c8c7485903a2 Mon Sep 17 00:00:00 2001 From: Ibai Mutiloa Date: Wed, 10 Sep 2025 14:40:41 +0000 Subject: [PATCH 3/5] Quitar logs --- classes/task/email_certificate_task.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/classes/task/email_certificate_task.php b/classes/task/email_certificate_task.php index 45e5aa61..e0252aa3 100644 --- a/classes/task/email_certificate_task.php +++ b/classes/task/email_certificate_task.php @@ -50,14 +50,11 @@ public function execute() { global $CFG, $DB; // Force error_log output to a dedicated debug file for all executions. - ini_set('error_log', '/tmp/customcert_debug.log'); - error_log('Customcert debug: script started'); require_once(__DIR__ . '/../../lib.php'); $customdata = $this->get_custom_data(); if (empty($customdata) || empty($customdata->issueid) || empty($customdata->customcertid)) { - error_log('Customcert email: Missing custom data'); return; } @@ -74,7 +71,6 @@ public function execute() { $customcert = $DB->get_record_sql($sql, ['id' => $customcertid]); if (!$customcert) { - error_log('Customcert email: Certificate not found'); return; } @@ -87,7 +83,6 @@ public function execute() { $user = $DB->get_record_sql($sql, ['customcertid' => $customcertid, 'issueid' => $issueid]); if (!$user) { - error_log('Customcert email: User or issue not found'); return; } @@ -96,18 +91,15 @@ public function execute() { // --- LANGUAGE SELECTION LOGIC (same hierarchy as certificate view) --- $lang = $this->resolve_certificate_language($customcert, $user); - error_log('Customcert email: Final resolved language: ' . $lang); // Force the resolved language $activelangs = get_string_manager()->get_list_of_translations(); if (!empty($lang) && array_key_exists($lang, $activelangs)) { force_current_language($lang); get_string_manager()->reset_caches(); - error_log('Customcert email: Language forced to: ' . $lang); // Test string fetch after forcing language $teststring = get_string('emailstudentsubject', 'customcert'); - error_log('Customcert email: Test string after language force: ' . $teststring); } // Get the context @@ -119,7 +111,6 @@ public function execute() { // Create a directory to store the PDF $tempdir = make_temp_directory('certificate/attachment'); if (!$tempdir) { - error_log('Customcert email: Could not create temp directory'); // Restore original language before returning force_current_language($originallang); return; @@ -190,7 +181,6 @@ public function execute() { // Mark as emailed $DB->set_field('customcert_issues', 'emailed', 1, ['id' => $issueid]); - error_log('Customcert email: Email sent successfully for issue ' . $issueid); // Restore original language force_current_language($originallang); @@ -212,27 +202,23 @@ private function resolve_certificate_language($customcert, $user) { // 1. Certificate-specific language (if set) if (!empty($customcert->force_language)) { $lang = $customcert->force_language; - error_log('Customcert email: Using certificate-specific language: ' . $lang); return $lang; } // 2. Course language (if set) if (!empty($customcert->courselang)) { $lang = $customcert->courselang; - error_log('Customcert email: Using course language: ' . $lang); return $lang; } // 3. User profile language (if set) if (!empty($user->lang)) { $lang = $user->lang; - error_log('Customcert email: Using user profile language: ' . $lang); return $lang; } // 4. Site default language $lang = $CFG->lang; - error_log('Customcert email: Using site default language: ' . $lang); return $lang; } @@ -256,7 +242,6 @@ private function send_email_to_student($user, $userfrom, $info, $context, $htmlr $message = format_text($message, FORMAT_HTML, ['filter' => true, 'context' => $context]); $messagehtml = format_text($messagehtml, FORMAT_HTML, ['filter' => true, 'context' => $context]); - error_log('Customcert email: Sending to student - Subject: ' . $subject); email_to_user($user, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message, $messagehtml, $tempfile, $filename); From 645de882c4750e7b50a1292afbf26b0c22af298a Mon Sep 17 00:00:00 2001 From: Ibai Mutiloa Date: Mon, 15 Sep 2025 06:24:42 +0000 Subject: [PATCH 4/5] PHP Automated checks solved --- classes/output/email_certificate.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/output/email_certificate.php b/classes/output/email_certificate.php index fe3a2c64..9a272ead 100644 --- a/classes/output/email_certificate.php +++ b/classes/output/email_certificate.php @@ -96,7 +96,6 @@ public function export_for_template(\renderer_base $renderer) { $info->certificatename = $this->certificatename; $info->courseshortname = $this->courseshortname; $info->coursefullname = $this->coursefullname; - if ($this->isstudent) { $data->emailgreeting = get_string('emailstudentgreeting', 'customcert', $this->userfullname); $data->emailbody = get_string('emailstudentbody', 'customcert', $info); From 8f4d0982e9b219a4cca46b89edd751e99fd9fb7d Mon Sep 17 00:00:00 2001 From: Ibai Mutiloa Date: Thu, 18 Sep 2025 06:28:26 +0000 Subject: [PATCH 5/5] PHP Automated checks solved 2.0 --- lib.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib.php b/lib.php index aa69f3f1..8a783c61 100644 --- a/lib.php +++ b/lib.php @@ -1,5 +1,18 @@ . // This file is part of the customcert module for Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify @@ -508,7 +521,7 @@ function mod_customcert_force_language_for_certificate($certificate, $course): b $lang = $CFG->lang; } - // Only force if valid and different from current + // Only force if valid and different from current. if (!empty($lang) && array_key_exists($lang, $activelangs) && $lang != current_language()) { force_current_language($lang); $forced = true;