@@ -53,6 +53,14 @@ public function execute() {
53
53
54
54
$ issueid = $ customdata ->issueid ;
55
55
$ 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
+
56
64
$ sql = "SELECT c.*, ct.id as templateid, ct.name as templatename, ct.contextid, co.id as courseid,
57
65
co.fullname as coursefullname, co.shortname as courseshortname
58
66
FROM {customcert} c
@@ -62,6 +70,11 @@ public function execute() {
62
70
63
71
$ customcert = $ DB ->get_record_sql ($ sql , ['id ' => $ customcertid ]);
64
72
73
+ if (!$ customcert ) {
74
+ mtrace ("Certificate with ID $ customcertid not found. " );
75
+ return ;
76
+ }
77
+
65
78
// The renderers used for sending emails.
66
79
$ page = new \moodle_page ();
67
80
$ htmlrenderer = $ page ->get_renderer ('mod_customcert ' , 'email ' , 'htmlemail ' );
@@ -94,10 +107,15 @@ public function execute() {
94
107
AND ci.id = :issueid " ;
95
108
$ user = $ DB ->get_record_sql ($ sql , ['customcertid ' => $ customcertid , 'issueid ' => $ issueid ]);
96
109
110
+ if (!$ user ) {
111
+ mtrace ("User or certificate issue not found for issue ID $ issueid. " );
112
+ return ;
113
+ }
114
+
97
115
// Create a directory to store the PDF we will be sending.
98
116
$ tempdir = make_temp_directory ('certificate/attachment ' );
99
117
if (!$ tempdir ) {
100
- return ;
118
+ throw new \ moodle_exception ( ' Failed to create temporary directory for certificate attachment ' ) ;
101
119
}
102
120
103
121
// Setup the user for the cron.
@@ -112,7 +130,15 @@ public function execute() {
112
130
$ template ->name = $ customcert ->templatename ;
113
131
$ template ->contextid = $ customcert ->contextid ;
114
132
$ 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
+ }
116
142
117
143
// Set the name of the file we are going to send.
118
144
$ filename = $ courseshortname . '_ ' . $ certificatename ;
@@ -122,57 +148,144 @@ public function execute() {
122
148
$ filename = str_replace ('& ' , '_ ' , $ filename ) . '.pdf ' ;
123
149
124
150
// 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. " );
127
164
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.
128
170
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
+ }
137
196
}
138
197
139
198
if ($ customcert ->emailteachers ) {
140
- $ teachers = get_enrolled_users ($ context , 'moodle/course:update ' );
199
+ try {
200
+ $ teachers = get_enrolled_users ($ context , 'moodle/course:update ' );
141
201
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 );
144
204
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 ();
151
225
}
152
226
}
153
227
154
228
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 );
161
233
162
234
$ subject = get_string ('emailnonstudentsubject ' , 'customcert ' , $ info );
163
235
$ message = $ textrenderer ->render ($ renderable );
164
236
$ messagehtml = $ htmlrenderer ->render ($ renderable );
165
237
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
+ }
171
260
}
261
+ } catch (\Exception $ e ) {
262
+ $ emailfailures [] = "Exception processing other email addresses: " . $ e ->getMessage ();
172
263
}
173
264
}
174
265
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. " );
177
290
}
178
291
}
0 commit comments