Skip to content

Commit 5127ba0

Browse files
Certificate: Migrate to Resource (‘user_certificate’) and replace PersonalFile - refs #5074
1 parent 6f63e07 commit 5127ba0

File tree

7 files changed

+908
-291
lines changed

7 files changed

+908
-291
lines changed

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

Lines changed: 123 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,31 @@ public function __construct(
101101
//$this->checkCertificatePath();
102102
if ('true' === api_get_setting('certificate.allow_general_certificate')) {
103103
// General certificate
104+
// store as a Resource (resource_type = user_certificate) instead of PersonalFile
105+
$repo = Container::getGradeBookCertificateRepository();
106+
$content = $this->generateCustomCertificate();
104107
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
105-
$name = hash('sha256', $this->user_id . $categoryId);
106-
$fileName = $name . '.html';
107-
$content = $this->generateCustomCertificate();
108-
$gradebookCertificateRepo = Container::getGradeBookCertificateRepository();
109-
$personalFile = $gradebookCertificateRepo->generateCertificatePersonalFile($this->user_id, $fileName, $content);
108+
$hash = hash('sha256', $this->user_id.$categoryId);
109+
$fileName = $hash.'.html';
110110

111-
if (null !== $personalFile) {
111+
try {
112+
// upsertCertificateResource(catId, userId, score, htmlContent, pdfBinary?, legacyFileName?)
113+
$cert = $repo->upsertCertificateResource(0, $this->user_id, 100.0, $content, null, $fileName);
114+
115+
// Keep legacy compatibility fields
116+
if ($updateCertificateData) {
117+
$repo->registerUserInfoAboutCertificate(0, $this->user_id, 100.0, $fileName);
118+
}
119+
120+
$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+
}
126+
127+
128+
if (null !== $cert) {
112129
// Updating the path
113130
self::updateUserCertificateInfo(
114131
0,
@@ -133,15 +150,21 @@ public function __construct(
133150
*/
134151
public function deleteCertificate(): bool
135152
{
136-
if (!empty($this->certificate_data)) {
137-
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
138-
$gradebookCertificateRepo = Container::getGradeBookCertificateRepository();
139-
$gradebookCertificateRepo->deleteCertificateAndRelatedFiles($this->certificate_data['user_id'], $categoryId);
153+
if (empty($this->certificate_data)) {
154+
return false;
155+
}
156+
157+
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
158+
$certRepo = Container::getGradeBookCertificateRepository();
159+
160+
try {
161+
$certRepo->deleteCertificateResource($this->certificate_data['user_id'], $categoryId);
140162

141163
return true;
164+
} catch (\Throwable $e) {
165+
error_log('[CERTIFICATE::deleteCertificate] delete error: '.$e->getMessage());
166+
return false;
142167
}
143-
144-
return false;
145168
}
146169

147170
/**
@@ -167,13 +190,13 @@ public function generate($params = [], $sendNotification = false)
167190
$category = $repo->find($categoryId);
168191
$isCertificateAvailableInCategory = !empty($categoryId) && $myCategory[0]->is_certificate_available($this->user_id);
169192
}
170-
$container = Container::getResourceNodeRepository();
171-
$filesystem = $container->getFileSystem();
193+
194+
$certRepo = Container::getGradeBookCertificateRepository();
172195

173196
if ($isCertificateAvailableInCategory && null !== $category) {
174197
$courseInfo = api_get_course_info($category->getCourse()->getCode());
175-
$courseId = $courseInfo['real_id'];
176-
$sessionId = $category->getSession() ? $category->getSession()->getId() : 0;
198+
$courseId = $courseInfo['real_id'];
199+
$sessionId = $category->getSession() ? $category->getSession()->getId() : 0;
177200

178201
$skill = new SkillModel();
179202
$skill->addSkillToUser(
@@ -184,77 +207,59 @@ public function generate($params = [], $sendNotification = false)
184207
);
185208

186209
if (!empty($this->certificate_data)) {
187-
$newContentHtml = GradebookUtils::get_user_certificate_content(
210+
$gb = GradebookUtils::get_user_certificate_content(
188211
$this->user_id,
189212
$category->getCourse()->getId(),
190213
$category->getSession() ? $category->getSession()->getId() : 0,
191214
false,
192215
$params['hide_print_button']
193216
);
194217

195-
if ($category->getId() == $categoryId) {
196-
$myPathCertificate = $this->certificate_data['path_certificate'] ?? '';
197-
198-
if ($filesystem->fileExists($myPathCertificate) &&
199-
!$this->force_certificate_generation
200-
) {
201-
// Seems that the file was already generated
202-
return true;
203-
} else {
204-
// Creating new name
205-
$name = hash('sha256', $this->user_id . $categoryId);
206-
$fileName = $name . '.html';
207-
$gradebookCertificateRepo = Container::getGradeBookCertificateRepository();
208-
$personalFile = $gradebookCertificateRepo->generateCertificatePersonalFile($this->user_id, $fileName, $newContentHtml['content']);
209-
210-
if (null !== $personalFile) {
211-
$result = true;
212-
// Updating the path
213-
$this->updateUserCertificateInfo(
214-
$this->certificate_data['cat_id'],
215-
$this->user_id,
216-
$fileName
217-
);
218-
$this->certification_user_path = $fileName;
219-
$this->certificate_data['path_certificate'] = $fileName;
220-
221-
if ($this->isHtmlFileGenerated()) {
222-
if ($sendNotification) {
223-
$subject = get_lang('Certificate notification');
224-
$message = nl2br(get_lang('((user_first_name)),'));
225-
$score = $this->certificate_data['score_certificate'];
226-
self::sendNotification(
227-
$subject,
228-
$message,
229-
api_get_user_info($this->user_id),
230-
$courseInfo,
231-
[
232-
'score_certificate' => $score,
233-
]
234-
);
235-
}
236-
}
237-
}
238-
239-
return $result;
218+
$html = is_array($gb) && isset($gb['content']) ? $gb['content'] : '';
219+
$score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
220+
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+
);
240244
}
245+
246+
return true;
247+
} catch (\Throwable $e) {
248+
error_log('[CERTIFICATE::generate] upsert error: '.$e->getMessage());
249+
return false;
241250
}
242251
}
243252
} else {
244-
$name = hash('sha256', $this->user_id . $categoryId);
245-
$fileName = $name . '.html';
246-
$certificateContent = $this->generateCustomCertificate($fileName);
247-
248-
$gradebookCertificateRepo = Container::getGradeBookCertificateRepository();
249-
$personalFile = $gradebookCertificateRepo->generateCertificatePersonalFile($this->user_id, $fileName, $certificateContent);
250-
251-
if ($personalFile !== null) {
252-
$personalRepo = Container::getPersonalFileRepository();
253-
$this->certificate_data['file_content'] = $personalRepo->getResourceFileContent($personalFile);
254-
$this->certificate_data['path_certificate'] = $fileName;
253+
$gbHtml = $this->generateCustomCertificate();
254+
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'] = '';
258+
return true;
259+
} catch (\Throwable $e) {
260+
error_log('[CERTIFICATE::generate] general certificate upsert error: '.$e->getMessage());
261+
return false;
255262
}
256-
257-
return true;
258263
}
259264

260265
return false;
@@ -301,7 +306,11 @@ public static function sendNotification(
301306

302307
$currentUserInfo = api_get_user_info();
303308
$url = '';
304-
if (!empty($certificateInfo['path_certificate'])) {
309+
310+
// Prefer resource URL if present
311+
if (!empty($certificateInfo['html_url'])) {
312+
$url = $certificateInfo['html_url'];
313+
} elseif (!empty($certificateInfo['path_certificate'])) {
305314
$hash = pathinfo($certificateInfo['path_certificate'], PATHINFO_FILENAME);
306315
$url = api_get_path(WEB_PATH) . 'certificates/' . $hash . '.html';
307316
}
@@ -347,25 +356,17 @@ public function updateUserCertificateInfo(
347356
$path_certificate,
348357
$updateCertificateData = true
349358
) {
350-
$categoryId = (int) $categoryId;
351-
$user_id = (int) $user_id;
352-
353-
$categoryCondition = 'cat_id = "'.$categoryId.'"';
354-
if ($categoryId == 0) {
355-
$categoryCondition = 'cat_id IS NULL';
359+
if (!$updateCertificateData) {
360+
return;
356361
}
362+
$certRepo = Container::getGradeBookCertificateRepository();
357363

358-
if ($updateCertificateData &&
359-
!UserManager::is_user_certified($categoryId, $user_id)
360-
) {
361-
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
362-
$now = api_get_utc_datetime();
363-
$sql = 'UPDATE '.$table.' SET
364-
path_certificate="'.Database::escape_string($path_certificate).'",
365-
created_at = "'.$now.'"
366-
WHERE '.$categoryCondition.' AND user_id="'.$user_id.'" ';
367-
Database::query($sql);
368-
}
364+
$certRepo->registerUserInfoAboutCertificate(
365+
(int)$categoryId,
366+
(int)$user_id,
367+
(float)($this->certificate_data['score_certificate'] ?? 100.0),
368+
(string)$path_certificate
369+
);
369370
}
370371

371372
/**
@@ -522,38 +523,44 @@ public function isVisible()
522523
*/
523524
public function isAvailable()
524525
{
525-
if (empty($this->certificate_data['path_certificate'])) {
526-
return false;
527-
}
526+
$certRepo = Container::getGradeBookCertificateRepository();
528527

529-
$container = Container::getResourceNodeRepository();
530-
$filesystem = $container->getFileSystem();
531-
if (!$filesystem->fileExists($this->certificate_data['path_certificate'])) {
528+
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
529+
530+
try {
531+
$entity = $certRepo->getCertificateByUserId(0 === $categoryId ? null : $categoryId, $this->user_id);
532+
if (!$entity || !$entity->hasResourceNode()) {
533+
return false;
534+
}
535+
536+
$node = $entity->getResourceNode();
537+
return $node->hasResourceFile() && $node->getResourceFiles()->count() > 0;
538+
} catch (\Throwable $e) {
539+
error_log('[CERTIFICATE::isAvailable] check error: '.$e->getMessage());
532540
return false;
533541
}
534-
535-
return true;
536542
}
537543

538544
/**
539545
* Shows the student's certificate (HTML file).
540546
*/
541547
public function show()
542548
{
543-
$container = Container::getResourceNodeRepository();
544-
$filesystem = $container->getFileSystem();
545-
if ($filesystem->fileExists($this->certificate_data['path_certificate'])) {
546-
// Needed in order to browsers don't add custom CSS
547-
$certificateContent = '<!DOCTYPE html>';
548-
$certificateContent .= $filesystem->read($this->certificate_data['path_certificate']);
549+
$certRepo = Container::getGradeBookCertificateRepository();
550+
$categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
549551

550-
// Remove media=screen to be available when printing a document
551-
$certificateContent = str_replace(
552-
' media="screen"',
553-
'',
554-
$certificateContent
555-
);
552+
try {
553+
$entity = $certRepo->getCertificateByUserId(0 === $categoryId ? null : $categoryId, $this->user_id);
554+
if (!$entity || !$entity->hasResourceNode()) {
555+
api_not_allowed(true);
556+
}
556557

558+
// Read HTML content from the Resource layer
559+
$certificateContent = '<!DOCTYPE html>';
560+
$certificateContent .= $certRepo->getResourceFileContent($entity);
561+
$certificateContent = str_replace(' media="screen"', '', $certificateContent);
562+
563+
// Track “downloaded_at” (legacy extra fields)
557564
if ($this->user_id == api_get_user_id() &&
558565
!empty($this->certificate_data) &&
559566
isset($this->certificate_data['id'])
@@ -575,10 +582,11 @@ public function show()
575582

576583
header('Content-Type: text/html; charset='.api_get_system_encoding());
577584
echo $certificateContent;
578-
579585
return;
586+
} catch (\Throwable $e) {
587+
error_log('[CERTIFICATE::show] read error: '.$e->getMessage());
588+
api_not_allowed(true);
580589
}
581-
api_not_allowed(true);
582590
}
583591

584592
/**

public/main/lp/lp_final_item.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,25 +140,32 @@ function safeGenerateCertificateForCategory(GradebookCategory $category, int $us
140140
$sessId = $session ? $session->getId() : 0;
141141
$catId = (int) $category->getId();
142142

143-
$gb = GradebookUtils::get_user_certificate_content($userId, $courseId, $sessId);
144-
$html = (is_array($gb) && isset($gb['content'])) ? $gb['content'] : '';
143+
// Build certificate content & score
144+
$gb = GradebookUtils::get_user_certificate_content($userId, $courseId, $sessId);
145+
$html = (is_array($gb) && isset($gb['content'])) ? $gb['content'] : '';
145146
$score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
146-
$fileName = ltrim(hash('sha256', $userId.$catId).'.html', '/');
147147

148148
$certRepo = Container::getGradeBookCertificateRepository();
149-
$pf = $certRepo->generateCertificatePersonalFile($userId, $fileName, $html);
149+
150+
$htmlUrl = '';
151+
$pdfUrl = '';
152+
150153
try {
151-
$certRepo->registerUserInfoAboutCertificate($catId, $userId, $score, $fileName);
154+
// Store/refresh as Resource (controlled access; not shown in "My personal files")
155+
$cert = $certRepo->upsertCertificateResource($catId, $userId, $score, $html);
156+
157+
// (Optional) keep metadata (created_at/score). Filename is not required anymore.
158+
$certRepo->registerUserInfoAboutCertificate($catId, $userId, $score);
159+
160+
// Build URLs from the Resource layer
161+
// View URL (first resource file assigned to the node – here the HTML we just uploaded)
162+
$htmlUrl = $certRepo->getResourceFileUrl($cert);
152163
} catch (\Throwable $e) {
153164
error_log('[LP_FINAL] register cert error: '.$e->getMessage());
154165
}
155166

156-
$hash = pathinfo($fileName, PATHINFO_FILENAME);
157-
$htmlUrl = api_get_path(WEB_PATH).'certificates/'.$hash.'.html';
158-
$pdfUrl = api_get_path(WEB_PATH).'certificates/'.$hash.'.pdf';
159-
160167
return [
161-
'path_certificate' => $fileName,
168+
'path_certificate' => (string) ($cert->getPathCertificate() ?? ''),
162169
'html_url' => $htmlUrl,
163170
'pdf_url' => $pdfUrl,
164171
];

0 commit comments

Comments
 (0)