Skip to content

Commit 98392d6

Browse files
committed
Prevent duplicate emails on certificate task failure
1 parent 820a6c9 commit 98392d6

File tree

1 file changed

+147
-34
lines changed

1 file changed

+147
-34
lines changed

classes/task/email_certificate_task.php

Lines changed: 147 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ public function execute() {
5353

5454
$issueid = $customdata->issueid;
5555
$customcertid = $customdata->customcertid;
56+
57+
// Check if already emailed to prevent duplicates on retry.
58+
$issue = $DB->get_record('customcert_issues', ['id' => $issueid], 'emailed');
59+
if ($issue && $issue->emailed) {
60+
mtrace("Certificate issue ID $issueid already emailed, skipping.");
61+
return; // Already processed, skip to prevent duplicate emails.
62+
}
63+
5664
$sql = "SELECT c.*, ct.id as templateid, ct.name as templatename, ct.contextid, co.id as courseid,
5765
co.fullname as coursefullname, co.shortname as courseshortname
5866
FROM {customcert} c
@@ -62,6 +70,11 @@ public function execute() {
6270

6371
$customcert = $DB->get_record_sql($sql, ['id' => $customcertid]);
6472

73+
if (!$customcert) {
74+
mtrace("Certificate with ID $customcertid not found.");
75+
return;
76+
}
77+
6578
// The renderers used for sending emails.
6679
$page = new \moodle_page();
6780
$htmlrenderer = $page->get_renderer('mod_customcert', 'email', 'htmlemail');
@@ -94,10 +107,15 @@ public function execute() {
94107
AND ci.id = :issueid";
95108
$user = $DB->get_record_sql($sql, ['customcertid' => $customcertid, 'issueid' => $issueid]);
96109

110+
if (!$user) {
111+
mtrace("User or certificate issue not found for issue ID $issueid.");
112+
return;
113+
}
114+
97115
// Create a directory to store the PDF we will be sending.
98116
$tempdir = make_temp_directory('certificate/attachment');
99117
if (!$tempdir) {
100-
return;
118+
throw new \moodle_exception('Failed to create temporary directory for certificate attachment');
101119
}
102120

103121
// Setup the user for the cron.
@@ -112,7 +130,15 @@ public function execute() {
112130
$template->name = $customcert->templatename;
113131
$template->contextid = $customcert->contextid;
114132
$template = new \mod_customcert\template($template);
115-
$filecontents = $template->generate_pdf(false, $user->id, true);
133+
134+
try {
135+
$filecontents = $template->generate_pdf(false, $user->id, true);
136+
} catch (\Exception $e) {
137+
// Log PDF generation failure and allow retry by throwing exception.
138+
mtrace('Certificate PDF generation failed for issue ID ' . $issueid . ': ' . $e->getMessage());
139+
debugging('Certificate PDF generation failed: ' . $e->getMessage(), DEBUG_DEVELOPER);
140+
throw new \moodle_exception('PDF generation failed: ' . $e->getMessage());
141+
}
116142

117143
// Set the name of the file we are going to send.
118144
$filename = $courseshortname . '_' . $certificatename;
@@ -122,57 +148,144 @@ public function execute() {
122148
$filename = str_replace('&', '_', $filename) . '.pdf';
123149

124150
// Create the file we will be sending.
125-
$tempfile = $tempdir . '/' . md5(microtime() . $user->id) . '.pdf';
126-
file_put_contents($tempfile, $filecontents);
151+
$tempfile = $tempdir . '/' . md5(microtime() . $user->id . random_int(1000, 9999)) . '.pdf';
152+
if (file_put_contents($tempfile, $filecontents) === false) {
153+
mtrace('Certificate PDF could not be written to temp file for issue ID ' . $issueid);
154+
debugging('Certificate PDF write failed for issue ID ' . $issueid, DEBUG_DEVELOPER);
155+
throw new \moodle_exception('Failed to write PDF to temporary file');
156+
}
157+
158+
$transaction = $DB->start_delegated_transaction();
159+
try {
160+
// Note: emailed flag is set before email sending.
161+
// This is intentional to prevent infinite retries if emails fail.
162+
$DB->set_field('customcert_issues', 'emailed', 1, ['id' => $issueid]);
163+
mtrace("Marked certificate issue ID $issueid as emailed to prevent retries.");
127164

165+
// Track email sending results for logging
166+
$emailresults = [];
167+
$emailfailures = [];
168+
169+
// Now try to send emails; log any failures but DO NOT retry.
128170
if ($customcert->emailstudents) {
129-
$renderable = new \mod_customcert\output\email_certificate(true, $userfullname, $courseshortname,
130-
$coursefullname, $certificatename, $context->instanceid);
131-
132-
$subject = get_string('emailstudentsubject', 'customcert', $info);
133-
$message = $textrenderer->render($renderable);
134-
$messagehtml = $htmlrenderer->render($renderable);
135-
email_to_user($user, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message,
136-
$messagehtml, $tempfile, $filename);
171+
try {
172+
$renderable = new \mod_customcert\output\email_certificate(
173+
true,
174+
$userfullname,
175+
$courseshortname,
176+
$coursefullname,
177+
$certificatename,
178+
$context->instanceid
179+
);
180+
181+
$subject = get_string('emailstudentsubject', 'customcert', $info);
182+
$message = $textrenderer->render($renderable);
183+
$messagehtml = $htmlrenderer->render($renderable);
184+
185+
$result = email_to_user($user, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message,
186+
$messagehtml, $tempfile, $filename);
187+
188+
if ($result) {
189+
$emailresults[] = "Student email sent to {$user->email}";
190+
} else {
191+
$emailfailures[] = "Failed to send student email to {$user->email}";
192+
}
193+
} catch (\Exception $e) {
194+
$emailfailures[] = "Exception sending student email: " . $e->getMessage();
195+
}
137196
}
138197

139198
if ($customcert->emailteachers) {
140-
$teachers = get_enrolled_users($context, 'moodle/course:update');
199+
try {
200+
$teachers = get_enrolled_users($context, 'moodle/course:update');
141201

142-
$renderable = new \mod_customcert\output\email_certificate(false, $userfullname, $courseshortname,
143-
$coursefullname, $certificatename, $context->instanceid);
202+
$renderable = new \mod_customcert\output\email_certificate(false, $userfullname, $courseshortname,
203+
$coursefullname, $certificatename, $context->instanceid);
144204

145-
$subject = get_string('emailnonstudentsubject', 'customcert', $info);
146-
$message = $textrenderer->render($renderable);
147-
$messagehtml = $htmlrenderer->render($renderable);
148-
foreach ($teachers as $teacher) {
149-
email_to_user($teacher, $userfrom, html_entity_decode($subject, ENT_COMPAT),
150-
$message, $messagehtml, $tempfile, $filename);
205+
$subject = get_string('emailnonstudentsubject', 'customcert', $info);
206+
$message = $textrenderer->render($renderable);
207+
$messagehtml = $htmlrenderer->render($renderable);
208+
209+
foreach ($teachers as $teacher) {
210+
try {
211+
$result = email_to_user($teacher, $userfrom, html_entity_decode($subject, ENT_COMPAT),
212+
$message, $messagehtml, $tempfile, $filename);
213+
214+
if ($result) {
215+
$emailresults[] = "Teacher email sent to {$teacher->email}";
216+
} else {
217+
$emailfailures[] = "Failed to send teacher email to {$teacher->email}";
218+
}
219+
} catch (\Exception $e) {
220+
$emailfailures[] = "Exception sending teacher email to {$teacher->email}: " . $e->getMessage();
221+
}
222+
}
223+
} catch (\Exception $e) {
224+
$emailfailures[] = "Exception getting teachers or sending teacher emails: " . $e->getMessage();
151225
}
152226
}
153227

154228
if (!empty($customcert->emailothers)) {
155-
$others = explode(',', $customcert->emailothers);
156-
foreach ($others as $email) {
157-
$email = trim($email);
158-
if (validate_email($email)) {
159-
$renderable = new \mod_customcert\output\email_certificate(false, $userfullname,
160-
$courseshortname, $coursefullname, $certificatename, $context->instanceid);
229+
try {
230+
$others = explode(',', $customcert->emailothers);
231+
$renderable = new \mod_customcert\output\email_certificate(false, $userfullname,
232+
$courseshortname, $coursefullname, $certificatename, $context->instanceid);
161233

162234
$subject = get_string('emailnonstudentsubject', 'customcert', $info);
163235
$message = $textrenderer->render($renderable);
164236
$messagehtml = $htmlrenderer->render($renderable);
165237

166-
$emailuser = new \stdClass();
167-
$emailuser->id = -1;
168-
$emailuser->email = $email;
169-
email_to_user($emailuser, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message,
170-
$messagehtml, $tempfile, $filename);
238+
foreach ($others as $email) {
239+
$email = trim($email);
240+
if (validate_email($email)) {
241+
try {
242+
$emailuser = new \stdClass();
243+
$emailuser->id = -1;
244+
$emailuser->email = $email;
245+
246+
$result = email_to_user($emailuser, $userfrom, html_entity_decode($subject, ENT_COMPAT), $message,
247+
$messagehtml, $tempfile, $filename);
248+
249+
if ($result) {
250+
$emailresults[] = "Other email sent to {$email}";
251+
} else {
252+
$emailfailures[] = "Failed to send other email to {$email}";
253+
}
254+
} catch (\Exception $e) {
255+
$emailfailures[] = "Exception sending other email to {$email}: " . $e->getMessage();
256+
}
257+
} else {
258+
$emailfailures[] = "Invalid email address in others list: {$email}";
259+
}
171260
}
261+
} catch (\Exception $e) {
262+
$emailfailures[] = "Exception processing other email addresses: " . $e->getMessage();
172263
}
173264
}
174265

175-
// Set the field so that it is emailed.
176-
$DB->set_field('customcert_issues', 'emailed', 1, ['id' => $issueid]);
266+
// Log results
267+
if (!empty($emailresults)) {
268+
mtrace("Email successes for issue ID $issueid: " . implode(', ', $emailresults));
269+
}
270+
271+
if (!empty($emailfailures)) {
272+
mtrace("Email failures for issue ID $issueid: " . implode(', ', $emailfailures));
273+
debugging("Certificate email failures for issue ID $issueid: " . implode('; ', $emailfailures), DEBUG_DEVELOPER);
274+
}
275+
276+
if (empty($emailresults)) {
277+
throw new \moodle_exception("No emails sent successfully for issue ID $issueid; retrying later.");
278+
}
279+
$transaction->allow_commit();
280+
// Clean up temporary file
281+
if (file_exists($tempfile)) {
282+
unlink($tempfile);
283+
}
284+
} catch (\Exception $e) {
285+
$emailfailures[] = "Email sending failed: " . $e->getMessage();
286+
$transaction->rollback($e);
287+
throw $e;
288+
}
289+
mtrace("Certificate email task completed for issue ID $issueid.");
177290
}
178291
}

0 commit comments

Comments
 (0)