@@ -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