Skip to content

Commit 3d3752c

Browse files
Certificate: Fix 500: generate certificates via Resource and fill file_content - refs #5074
1 parent 5127ba0 commit 3d3752c

File tree

1 file changed

+102
-57
lines changed

1 file changed

+102
-57
lines changed

public/main/inc/lib/certificate.lib.php

Lines changed: 102 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -168,36 +168,56 @@ public function deleteCertificate(): bool
168168
}
169169

170170
/**
171-
* Generates an HTML Certificate and fills the path_certificate field in the DB.
171+
* Generates (or updates) the user's certificate as a Resource.
172+
*
173+
* - Stores the HTML in a ResourceNode (resource_type = user_certificate or fallback handled at repository level).
174+
* - Fills $this->certificate_data['file_content'] with the HTML to avoid PDF errors.
175+
* - Keeps legacy DB info via registerUserInfoAboutCertificate() (no PersonalFile usage).
172176
*
173177
* @param array $params
174178
* @param bool $sendNotification
175179
*
176-
* @return bool|int
180+
* @return bool
177181
*/
178182
public function generate($params = [], $sendNotification = false)
179183
{
180-
$result = false;
181-
$params['hide_print_button'] = isset($params['hide_print_button']) ? true : false;
184+
// Safe defaults
185+
$params = is_array($params) ? $params : [];
186+
$params['hide_print_button'] = isset($params['hide_print_button'])
187+
? (bool) $params['hide_print_button']
188+
: false;
189+
190+
$certRepo = Container::getGradeBookCertificateRepository();
191+
182192
$categoryId = 0;
193+
$category = null;
183194
$isCertificateAvailableInCategory = false;
184-
$category = null;
195+
196+
// If the certificate is linked to a Gradebook category, check availability
185197
if (isset($this->certificate_data['cat_id'])) {
186198
$categoryId = (int) $this->certificate_data['cat_id'];
199+
200+
// Category::load() returns an array
187201
$myCategory = Category::load($categoryId);
202+
188203
$repo = Container::getGradeBookCategoryRepository();
189-
/** @var GradebookCategory $category */
204+
/** @var \Chamilo\CoreBundle\Entity\GradebookCategory|null $category */
190205
$category = $repo->find($categoryId);
191-
$isCertificateAvailableInCategory = !empty($categoryId) && $myCategory[0]->is_certificate_available($this->user_id);
192-
}
193206

194-
$certRepo = Container::getGradeBookCertificateRepository();
207+
if (!empty($categoryId) && !empty($myCategory) && isset($myCategory[0])) {
208+
$isCertificateAvailableInCategory = $myCategory[0]->is_certificate_available($this->user_id);
209+
}
210+
}
195211

212+
// Path A: course/session-bound certificate
196213
if ($isCertificateAvailableInCategory && null !== $category) {
197-
$courseInfo = api_get_course_info($category->getCourse()->getCode());
214+
// Course/session info
215+
$course = $category->getCourse();
216+
$courseInfo = api_get_course_info($course->getCode());
198217
$courseId = $courseInfo['real_id'];
199-
$sessionId = $category->getSession() ? $category->getSession()->getId() : 0;
218+
$sessionId = $category->getSession() ? (int) $category->getSession()->getId() : 0;
200219

220+
// Award related skill
201221
$skill = new SkillModel();
202222
$skill->addSkillToUser(
203223
$this->user_id,
@@ -206,63 +226,88 @@ public function generate($params = [], $sendNotification = false)
206226
$sessionId
207227
);
208228

209-
if (!empty($this->certificate_data)) {
210-
$gb = GradebookUtils::get_user_certificate_content(
211-
$this->user_id,
212-
$category->getCourse()->getId(),
213-
$category->getSession() ? $category->getSession()->getId() : 0,
214-
false,
215-
$params['hide_print_button']
216-
);
229+
// Build certificate HTML and score
230+
$gb = GradebookUtils::get_user_certificate_content(
231+
$this->user_id,
232+
$course->getId(),
233+
$sessionId,
234+
false,
235+
$params['hide_print_button']
236+
);
217237

218-
$html = is_array($gb) && isset($gb['content']) ? $gb['content'] : '';
219-
$score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
238+
$html = '';
239+
$score = 100.0;
220240

221-
try {
222-
// Upsert como Resource (resource_type = user_certificate)
223-
$certEntity = $certRepo->upsertCertificateResource($categoryId, $this->user_id, $score, $html);
224-
$certRepo->registerUserInfoAboutCertificate($categoryId, $this->user_id, $score);
225-
226-
$this->certification_user_path = 'resource://user_certificate';
227-
$this->certificate_data['path_certificate'] = '';
228-
229-
if ($this->isHtmlFileGenerated() && $sendNotification) {
230-
$subject = get_lang('Certificate notification');
231-
$message = nl2br(get_lang('((user_first_name)),'));
232-
$htmlUrl = $certRepo->getResourceFileUrl($certEntity);
233-
234-
self::sendNotification(
235-
$subject,
236-
$message,
237-
api_get_user_info($this->user_id),
238-
$courseInfo,
239-
[
240-
'score_certificate' => $score,
241-
'html_url' => $htmlUrl,
242-
]
243-
);
244-
}
241+
if (is_array($gb)) {
242+
$html = isset($gb['content']) ? (string) $gb['content'] : '';
243+
$score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
244+
} elseif (is_string($gb) && $gb !== '') {
245+
// Some custom implementations might return a raw string
246+
$html = $gb;
247+
}
245248

246-
return true;
247-
} catch (\Throwable $e) {
248-
error_log('[CERTIFICATE::generate] upsert error: '.$e->getMessage());
249-
return false;
250-
}
249+
if ($html === '') {
250+
error_log('[CERT::generate] Empty HTML content for category certificate (cat='.$categoryId.', user='.$this->user_id.').');
251+
return false;
251252
}
252-
} else {
253-
$gbHtml = $this->generateCustomCertificate();
253+
254254
try {
255-
$certEntity = $certRepo->upsertCertificateResource(0, $this->user_id, 100.0, $gbHtml);
256-
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($certEntity);
257-
$this->certificate_data['path_certificate'] = '';
255+
// Persist as Resource and register legacy info (no PersonalFile)
256+
$entity = $certRepo->upsertCertificateResource($categoryId, $this->user_id, $score, $html);
257+
$certRepo->registerUserInfoAboutCertificate($categoryId, $this->user_id, $score);
258+
259+
// Ensure PDF flow has the HTML in memory
260+
$this->certificate_data['file_content'] = $html;
261+
$this->certificate_data['path_certificate'] = ''; // stored as resource, no legacy file path
262+
263+
// Send notification if required (we have course context here)
264+
if ($sendNotification) {
265+
$subject = get_lang('Certificate notification');
266+
$message = nl2br(get_lang('((user_first_name)),'));
267+
$htmlUrl = $certRepo->getResourceFileUrl($entity);
268+
269+
self::sendNotification(
270+
$subject,
271+
$message,
272+
api_get_user_info($this->user_id),
273+
$courseInfo,
274+
[
275+
'score_certificate' => $score,
276+
'html_url' => $htmlUrl,
277+
]
278+
);
279+
}
280+
258281
return true;
259282
} catch (\Throwable $e) {
260-
error_log('[CERTIFICATE::generate] general certificate upsert error: '.$e->getMessage());
283+
error_log('[CERT::generate] Upsert failed for category certificate (cat='.$categoryId.', user='.$this->user_id.'): '.$e->getMessage());
261284
return false;
262285
}
263286
}
264287

265-
return false;
288+
// Path B: general (portal-wide) certificate
289+
try {
290+
$html = $this->generateCustomCertificate('');
291+
$score = 100.0;
292+
293+
if ($html === '') {
294+
error_log('[CERT::generate] Empty HTML content for general certificate (user='.$this->user_id.').');
295+
return false;
296+
}
297+
298+
$entity = $certRepo->upsertCertificateResource(0, $this->user_id, $score, $html);
299+
$certRepo->registerUserInfoAboutCertificate(0, $this->user_id, $score);
300+
301+
// Ensure PDF flow has the HTML in memory
302+
$this->certificate_data['file_content'] = $html;
303+
$this->certificate_data['path_certificate'] = ''; // stored as resource
304+
305+
// No course context here, so we skip notification (sendNotification would fail its own checks)
306+
return true;
307+
} catch (\Throwable $e) {
308+
error_log('[CERT::generate] General certificate upsert failed (user='.$this->user_id.'): '.$e->getMessage());
309+
return false;
310+
}
266311
}
267312

268313
/**

0 commit comments

Comments
 (0)