@@ -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 /**
0 commit comments