Skip to content

Commit e3f051f

Browse files
Gradebook: check course certificate resource from gradebook - refs #5074
1 parent 20dcb95 commit e3f051f

File tree

1 file changed

+111
-34
lines changed

1 file changed

+111
-34
lines changed

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

Lines changed: 111 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,12 @@ public function deleteCertificate(): bool
237237
/**
238238
* Generates (or updates) the user's certificate as a Resource.
239239
*
240-
* - Stores the HTML in a ResourceNode (resource_type = user_certificate or fallback handled at repository level).
240+
* Template strategy is centralized here:
241+
* 1) If the Gradebook category HAS a default template (document) => use it.
242+
* 2) If it DOES NOT => fall back to the portal "custom" template (legacy behavior).
243+
* In both cases we store a category-bound Resource (cat_id), so pages don't need to re-implement fallbacks.
244+
*
245+
* - Stores the HTML in a ResourceNode (resource_type = user_certificate).
241246
* - Fills $this->certificate_data['file_content'] with the HTML to avoid PDF errors.
242247
* - Keeps legacy DB info via registerUserInfoAboutCertificate() (no PersonalFile usage).
243248
*
@@ -254,7 +259,17 @@ public function generate($params = [], $sendNotification = false)
254259
? (bool) $params['hide_print_button']
255260
: false;
256261

257-
$certRepo = Container::getGradeBookCertificateRepository();
262+
// Repository (required).
263+
try {
264+
$certRepo = Container::getGradeBookCertificateRepository();
265+
} catch (\Throwable $e) {
266+
error_log('[CERT::generate] FATAL: cannot get GradeBookCertificateRepository: '.$e->getMessage());
267+
return false;
268+
}
269+
if (!$certRepo) {
270+
error_log('[CERT::generate] FATAL: GradeBookCertificateRepository is NULL');
271+
return false;
272+
}
258273

259274
$categoryId = 0;
260275
$category = null;
@@ -267,33 +282,57 @@ public function generate($params = [], $sendNotification = false)
267282
// Category::load() returns an array
268283
$myCategory = Category::load($categoryId);
269284

270-
$repo = Container::getGradeBookCategoryRepository();
271-
/** @var \Chamilo\CoreBundle\Entity\GradebookCategory|null $category */
272-
$category = $repo->find($categoryId);
285+
try {
286+
$repo = Container::getGradeBookCategoryRepository();
287+
/** @var GradebookCategory|null $category */
288+
$category = $repo ? $repo->find($categoryId) : null;
289+
} catch (\Throwable $e) {
290+
error_log('[CERT::generate] category repo fetch failed: '.$e->getMessage());
291+
$category = null;
292+
}
273293

274294
if (!empty($categoryId) && !empty($myCategory) && isset($myCategory[0])) {
275295
$isCertificateAvailableInCategory = $myCategory[0]->is_certificate_available($this->user_id);
276296
}
277297
}
278298

279-
// Path A: course/session-bound certificate
299+
// Path A: course/session-bound certificate (category context)
280300
if ($isCertificateAvailableInCategory && null !== $category) {
281301
// Course/session info
282302
$course = $category->getCourse();
283303
$courseInfo = api_get_course_info($course->getCode());
284304
$courseId = $courseInfo['real_id'];
285305
$sessionId = $category->getSession() ? (int) $category->getSession()->getId() : 0;
286306

287-
// Award related skill
288-
$skill = new SkillModel();
289-
$skill->addSkillToUser(
290-
$this->user_id,
291-
$category,
292-
$courseId,
293-
$sessionId
294-
);
307+
try {
308+
$skill = new SkillModel();
309+
$skill->addSkillToUser(
310+
$this->user_id,
311+
$category,
312+
$courseId,
313+
$sessionId
314+
);
315+
} catch (\Throwable $e) {
316+
error_log('[CERT::generate] addSkillToUser failed: '.$e->getMessage());
317+
}
318+
319+
$categoryHasDefaultTemplate = false;
320+
$documentIdForLog = null;
321+
try {
322+
$doc = $category->getDocument();
323+
if ($doc !== null) {
324+
$categoryHasDefaultTemplate = true;
325+
try {
326+
$documentIdForLog = method_exists($doc, 'getId') ? $doc->getId() : null;
327+
} catch (\Throwable $ignored) {
328+
$documentIdForLog = null;
329+
}
330+
}
331+
} catch (\Throwable $e) {
332+
error_log('[CERT::generate] getDocument() failed (no default template): '.$e->getMessage());
333+
$categoryHasDefaultTemplate = false;
334+
}
295335

296-
// Build certificate HTML and score
297336
$gb = GradebookUtils::get_user_certificate_content(
298337
$this->user_id,
299338
$course->getId(),
@@ -302,19 +341,41 @@ public function generate($params = [], $sendNotification = false)
302341
$params['hide_print_button']
303342
);
304343

305-
$html = '';
306344
$score = 100.0;
345+
if (is_array($gb) && isset($gb['score'])) {
346+
$score = (float)$gb['score'];
347+
}
348+
349+
$html = '';
350+
$source = '';
307351

308-
if (is_array($gb)) {
309-
$html = isset($gb['content']) ? (string) $gb['content'] : '';
310-
$score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
311-
} elseif (is_string($gb) && $gb !== '') {
312-
// Some custom implementations might return a raw string
313-
$html = $gb;
352+
if ($categoryHasDefaultTemplate) {
353+
if (is_array($gb) && !empty($gb['content'])) {
354+
$html = (string)$gb['content'];
355+
$source = 'DEFAULT_TEMPLATE';
356+
} elseif (is_string($gb) && $gb !== '') {
357+
$html = $gb;
358+
$source = 'DEFAULT_TEMPLATE';
359+
}
360+
} else {
361+
error_log(sprintf(
362+
'[CERT::generate] course DEFAULT template NOT found. cat=%d user=%d -> fallback to CUSTOM',
363+
(int)$categoryId,
364+
(int)$this->user_id
365+
));
366+
}
367+
368+
if ($html === '') {
369+
$html = $this->generateCustomCertificate('');
370+
$source = 'CUSTOM_TEMPLATE_FALLBACK';
314371
}
315372

316373
if ($html === '') {
317-
error_log('[CERT::generate] Empty HTML content for category certificate (cat='.$categoryId.', user='.$this->user_id.').');
374+
error_log(sprintf(
375+
'[CERT::generate] Empty HTML on category path. cat=%d user=%d',
376+
(int)$categoryId,
377+
(int)$this->user_id
378+
));
318379
return false;
319380
}
320381

@@ -324,14 +385,19 @@ public function generate($params = [], $sendNotification = false)
324385
$certRepo->registerUserInfoAboutCertificate($categoryId, $this->user_id, $score);
325386

326387
// Ensure PDF flow has the HTML in memory
327-
$this->certificate_data['file_content'] = $html;
328-
$this->certificate_data['path_certificate'] = ''; // stored as resource, no legacy file path
388+
$this->certificate_data['file_content'] = $html;
389+
$this->certificate_data['path_certificate'] = '';
329390

330391
// Send notification if required (we have course context here)
331392
if ($sendNotification) {
332393
$subject = get_lang('Certificate notification');
333394
$message = nl2br(get_lang('((user_first_name)),'));
334-
$htmlUrl = $certRepo->getResourceFileUrl($entity);
395+
$htmlUrl = '';
396+
try {
397+
$htmlUrl = $certRepo->getResourceFileUrl($entity);
398+
} catch (\Throwable $e) {
399+
error_log('[CERT::generate] getResourceFileUrl failed for notification: '.$e->getMessage());
400+
}
335401

336402
self::sendNotification(
337403
$subject,
@@ -347,32 +413,42 @@ public function generate($params = [], $sendNotification = false)
347413

348414
return true;
349415
} catch (\Throwable $e) {
350-
error_log('[CERT::generate] Upsert failed for category certificate (cat='.$categoryId.', user='.$this->user_id.'): '.$e->getMessage());
416+
error_log(sprintf(
417+
'[CERT::generate] Upsert FAILED (course). cat=%d user=%d err=%s',
418+
(int)$categoryId,
419+
(int)$this->user_id,
420+
$e->getMessage()
421+
));
351422
return false;
352423
}
353424
}
354425

355426
// Path B: general (portal-wide) certificate
356427
try {
357-
$html = $this->generateCustomCertificate('');
358-
$score = 100.0;
428+
$html = $this->generateCustomCertificate('');
429+
$score = 100.0;
359430

360431
if ($html === '') {
361-
error_log('[CERT::generate] Empty HTML content for general certificate (user='.$this->user_id.').');
432+
error_log(sprintf(
433+
'[CERT::generate] Empty HTML on general path. user=%d',
434+
(int)$this->user_id
435+
));
362436
return false;
363437
}
364438

365439
$entity = $certRepo->upsertCertificateResource(0, $this->user_id, $score, $html);
366440
$certRepo->registerUserInfoAboutCertificate(0, $this->user_id, $score);
367441

368-
// Ensure PDF flow has the HTML in memory
369442
$this->certificate_data['file_content'] = $html;
370-
$this->certificate_data['path_certificate'] = ''; // stored as resource
443+
$this->certificate_data['path_certificate'] = ''; // resource
371444

372-
// No course context here, so we skip notification (sendNotification would fail its own checks)
373445
return true;
374446
} catch (\Throwable $e) {
375-
error_log('[CERT::generate] General certificate upsert failed (user='.$this->user_id.'): '.$e->getMessage());
447+
error_log(sprintf(
448+
'[CERT::generate] Upsert FAILED (general). user=%d err=%s',
449+
(int)$this->user_id,
450+
$e->getMessage()
451+
));
376452
return false;
377453
}
378454
}
@@ -876,6 +952,7 @@ public function generatePdfFromCustomCertificate(): void
876952
$page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
877953
$pdf = new PDF($page_format, $params['orientation'], $params);
878954

955+
// Safety: ensure HTML content is present; fetch from Resource if needed.
879956
if (empty($this->certificate_data['file_content'])) {
880957
try {
881958
$certRepo = Container::getGradeBookCertificateRepository();

0 commit comments

Comments
 (0)