Skip to content

Commit 1508a1b

Browse files
Certificate: safe resource upsert; avoid undefined file_content
1 parent 3d3752c commit 1508a1b

File tree

1 file changed

+116
-36
lines changed

1 file changed

+116
-36
lines changed

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

Lines changed: 116 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -64,76 +64,143 @@ public function __construct(
6464
$updateCertificateData = true,
6565
$pathToCertificate = ''
6666
) {
67-
$this->table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
68-
$this->user_id = !empty($userId) ? $userId : api_get_user_id();
67+
$this->table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
68+
$this->user_id = !empty($userId) ? (int) $userId : api_get_user_id();
6969

70+
// Load legacy row if an ID is provided
7071
if (!empty($certificate_id)) {
7172
$certificate = $this->get($certificate_id);
72-
if (!empty($certificate) && is_array($certificate)) {
73+
if (is_array($certificate) && !empty($certificate)) {
7374
$this->certificate_data = $certificate;
74-
$this->user_id = $this->certificate_data['user_id'];
75+
$this->user_id = (int) $this->certificate_data['user_id'];
7576
}
7677
}
7778

78-
if ($this->user_id) {
79-
// To force certification generation
80-
if ($this->force_certificate_generation) {
81-
$this->generate(['certificate_path' => ''], $sendNotification);
79+
if (empty($this->user_id)) {
80+
// No user context, nothing else to do.
81+
return;
82+
}
83+
84+
$certRepo = Container::getGradeBookCertificateRepository();
85+
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
86+
87+
// Try to preload an existing resource to avoid unnecessary work.
88+
try {
89+
$existing = $certRepo->getCertificateByUserId($categoryId === 0 ? null : $categoryId, $this->user_id);
90+
if ($existing && $existing->hasResourceNode()) {
91+
// Resource-first model: legacy path is not used anymore.
92+
$this->certification_user_path = 'resource://user_certificate';
93+
$this->html_file = '';
94+
$this->certificate_data['path_certificate'] = '';
95+
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($existing);
8296
}
83-
if (
84-
isset($this->certificate_data)
85-
&& $this->certificate_data
86-
&& empty($this->certificate_data['path_certificate'])
87-
) {
97+
} catch (\Throwable $e) {
98+
// Non-fatal; generation can still proceed if needed.
99+
error_log('[CERT::__construct] preload resource error: '.$e->getMessage());
100+
}
101+
102+
// Keep original behavior: optionally generate on construct.
103+
if ($this->force_certificate_generation) {
104+
try {
88105
$this->generate(['certificate_path' => $pathToCertificate], $sendNotification);
106+
// Refresh in-memory HTML for PDF generation after generate().
107+
$refetched = $certRepo->getCertificateByUserId($categoryId === 0 ? null : $categoryId, $this->user_id);
108+
if ($refetched && $refetched->hasResourceNode()) {
109+
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($refetched);
110+
$this->certification_user_path = 'resource://user_certificate';
111+
}
112+
} catch (\Throwable $e) {
113+
error_log('[CERT::__construct] generate-on-construct failed: '.$e->getMessage());
114+
}
115+
}
116+
117+
// If still empty legacy path but we have a row, keep original fallback.
118+
if (
119+
isset($this->certificate_data) &&
120+
$this->certificate_data &&
121+
empty($this->certificate_data['path_certificate']) &&
122+
!$this->force_certificate_generation
123+
) {
124+
try {
125+
$this->generate(['certificate_path' => $pathToCertificate], $sendNotification);
126+
$refetched = $certRepo->getCertificateByUserId($categoryId === 0 ? null : $categoryId, $this->user_id);
127+
if ($refetched && $refetched->hasResourceNode()) {
128+
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($refetched);
129+
$this->certification_user_path = 'resource://user_certificate';
130+
}
131+
} catch (\Throwable $e) {
132+
error_log('[CERT::__construct] generate (no legacy path) failed: '.$e->getMessage());
89133
}
90134
}
91135

92136
// Setting the qr and html variables
93-
if (isset($certificate_id) &&
137+
if (
138+
isset($certificate_id) &&
94139
!empty($this->certification_user_path) &&
95-
isset($this->certificate_data['path_certificate'])
140+
isset($this->certificate_data['path_certificate']) &&
141+
!empty($this->certificate_data['path_certificate'])
96142
) {
97-
//$pathinfo = pathinfo($this->certificate_data['path_certificate']);
143+
// Legacy: path points to a file name; we only keep it for BC.
98144
$this->html_file = $this->certificate_data['path_certificate'];
99-
//$this->qr_file = $this->certification_user_path.$pathinfo['filename'].'_qr.png';
100145
} else {
101-
//$this->checkCertificatePath();
102146
if ('true' === api_get_setting('certificate.allow_general_certificate')) {
147+
// Guard: if a resource already exists, just populate file_content and exit.
148+
try {
149+
$already = $certRepo->getCertificateByUserId(null, $this->user_id); // general certificate => null cat
150+
if ($already && $already->hasResourceNode()) {
151+
$this->certification_user_path = 'resource://user_certificate';
152+
$this->certificate_data['path_certificate'] = '';
153+
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($already);
154+
return; // Nothing else to do.
155+
}
156+
} catch (\Throwable $e) {
157+
error_log('[CERT::__construct] check-existing general cert error: '.$e->getMessage());
158+
}
159+
103160
// General certificate
104161
// store as a Resource (resource_type = user_certificate) instead of PersonalFile
105-
$repo = Container::getGradeBookCertificateRepository();
106-
$content = $this->generateCustomCertificate();
107-
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
108-
$hash = hash('sha256', $this->user_id.$categoryId);
109-
$fileName = $hash.'.html';
110-
162+
$cert = null;
111163
try {
164+
// Build HTML content (always available for PDF even if upsert fails).
165+
$content = $this->generateCustomCertificate();
166+
167+
$hash = hash('sha256', $this->user_id.$categoryId);
168+
$fileName = $hash.'.html';
169+
112170
// upsertCertificateResource(catId, userId, score, htmlContent, pdfBinary?, legacyFileName?)
113-
$cert = $repo->upsertCertificateResource(0, $this->user_id, 100.0, $content, null, $fileName);
171+
$cert = $certRepo->upsertCertificateResource(0, $this->user_id, 100.0, $content, null, $fileName);
114172

115-
// Keep legacy compatibility fields
173+
// Keep legacy compatibility fields in DB if required
116174
if ($updateCertificateData) {
117-
$repo->registerUserInfoAboutCertificate(0, $this->user_id, 100.0, $fileName);
175+
$certRepo->registerUserInfoAboutCertificate(0, $this->user_id, 100.0, $fileName);
118176
}
119177

178+
// Update in-memory fields for downstream consumers (PDF)
179+
$this->certification_user_path = 'resource://user_certificate';
120180
$this->certificate_data['path_certificate'] = $fileName;
121-
// Optionally keep the in-memory HTML content
122-
$this->certificate_data['file_content'] = $repo->getResourceFileContent($cert);
123-
} catch (\Throwable $e) {
124-
error_log('[CERT] general certificate upsert error: '.$e->getMessage());
125-
}
126181

182+
// Ensure file_content is always available (avoid undefined index)
183+
try {
184+
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($cert);
185+
} catch (\Throwable $ignored) {
186+
// Fallback: use raw generated HTML so PDF creation never crashes.
187+
$this->certificate_data['file_content'] = $content;
188+
}
127189

128-
if (null !== $cert) {
129-
// Updating the path
130-
self::updateUserCertificateInfo(
190+
// Optional: keep the legacy user-certificate metadata updated
191+
$this->updateUserCertificateInfo(
131192
0,
132193
$this->user_id,
133194
$fileName,
134195
$updateCertificateData
135196
);
136-
$this->certificate_data['path_certificate'] = $fileName;
197+
} catch (\Throwable $e) {
198+
// Do not break the constructor; log and keep going
199+
error_log('[CERT] general certificate upsert error: '.$e->getMessage());
200+
// As a last resort, populate file_content with a minimal HTML to avoid PDF fatal errors
201+
if (empty($this->certificate_data['file_content'])) {
202+
$this->certificate_data['file_content'] = '<html><body><p></p></body></html>';
203+
}
137204
}
138205
}
139206
}
@@ -809,6 +876,19 @@ public function generatePdfFromCustomCertificate(): void
809876
$page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
810877
$pdf = new PDF($page_format, $params['orientation'], $params);
811878

879+
if (empty($this->certificate_data['file_content'])) {
880+
try {
881+
$certRepo = Container::getGradeBookCertificateRepository();
882+
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
883+
$entity = $certRepo->getCertificateByUserId(0 === $categoryId ? null : $categoryId, $this->user_id);
884+
if ($entity && $entity->hasResourceNode()) {
885+
$this->certificate_data['file_content'] = $certRepo->getResourceFileContent($entity);
886+
}
887+
} catch (\Throwable $e) {
888+
error_log('[CERT::generatePdfFromCustomCertificate] fallback read error: '.$e->getMessage());
889+
}
890+
}
891+
812892
$pdf->content_to_pdf(
813893
$this->certificate_data['file_content'],
814894
null,

0 commit comments

Comments
 (0)