| Total Complexity | 144 |
| Total Lines | 957 |
| Duplicated Lines | 0 % |
| Changes | 3 | ||
| Bugs | 1 | Features | 0 |
Complex classes like Certificate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Certificate, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 19 | class Certificate extends Model |
||
| 20 | { |
||
| 21 | public $table; |
||
| 22 | public $columns = [ |
||
| 23 | 'id', |
||
| 24 | 'cat_id', |
||
| 25 | 'score_certificate', |
||
| 26 | 'created_at', |
||
| 27 | 'path_certificate', |
||
| 28 | ]; |
||
| 29 | /** |
||
| 30 | * Certification data. |
||
| 31 | */ |
||
| 32 | public $certificate_data = []; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * Student's certification path. |
||
| 36 | */ |
||
| 37 | public $certification_user_path = null; |
||
| 38 | public $certification_web_user_path = null; |
||
| 39 | public $html_file = null; |
||
| 40 | public $qr_file = null; |
||
| 41 | public $user_id; |
||
| 42 | |||
| 43 | /** If true every time we enter to the certificate URL |
||
| 44 | * we would generate a new certificate (good thing because we can edit the |
||
| 45 | * certificate and all users will have the latest certificate bad because we. |
||
| 46 | * load the certificate every time */ |
||
| 47 | public $force_certificate_generation = true; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Constructor. |
||
| 51 | * |
||
| 52 | * @param int $certificate_id ID of the certificate |
||
| 53 | * @param int $userId |
||
| 54 | * @param bool $sendNotification send message to student |
||
| 55 | * @param bool $updateCertificateData |
||
| 56 | * @param string $pathToCertificate |
||
| 57 | * |
||
| 58 | * If no ID given, take user_id and try to generate one |
||
| 59 | */ |
||
| 60 | public function __construct( |
||
| 61 | $certificate_id = 0, |
||
| 62 | $userId = 0, |
||
| 63 | $sendNotification = false, |
||
| 64 | $updateCertificateData = true, |
||
| 65 | $pathToCertificate = '' |
||
| 66 | ) { |
||
| 67 | $this->table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE); |
||
| 68 | $this->user_id = !empty($userId) ? (int) $userId : api_get_user_id(); |
||
| 69 | |||
| 70 | // Load legacy row if an ID is provided |
||
| 71 | if (!empty($certificate_id)) { |
||
| 72 | $certificate = $this->get($certificate_id); |
||
| 73 | if (is_array($certificate) && !empty($certificate)) { |
||
| 74 | $this->certificate_data = $certificate; |
||
| 75 | $this->user_id = (int) $this->certificate_data['user_id']; |
||
| 76 | } |
||
| 77 | } |
||
| 78 | |||
| 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); |
||
| 96 | } |
||
| 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 { |
||
| 105 | $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()); |
||
| 133 | } |
||
| 134 | } |
||
| 135 | |||
| 136 | // Setting the qr and html variables |
||
| 137 | if ( |
||
| 138 | isset($certificate_id) && |
||
| 139 | !empty($this->certification_user_path) && |
||
| 140 | isset($this->certificate_data['path_certificate']) && |
||
| 141 | !empty($this->certificate_data['path_certificate']) |
||
| 142 | ) { |
||
| 143 | // Legacy: path points to a file name; we only keep it for BC. |
||
| 144 | $this->html_file = $this->certificate_data['path_certificate']; |
||
| 145 | } else { |
||
| 146 | 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 | |||
| 160 | // General certificate |
||
| 161 | // store as a Resource (resource_type = user_certificate) instead of PersonalFile |
||
| 162 | $cert = null; |
||
| 163 | 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 | |||
| 170 | // upsertCertificateResource(catId, userId, score, htmlContent, pdfBinary?, legacyFileName?) |
||
| 171 | $cert = $certRepo->upsertCertificateResource(0, $this->user_id, 100.0, $content, null, $fileName); |
||
| 172 | |||
| 173 | // Keep legacy compatibility fields in DB if required |
||
| 174 | if ($updateCertificateData) { |
||
| 175 | $certRepo->registerUserInfoAboutCertificate(0, $this->user_id, 100.0, $fileName); |
||
| 176 | } |
||
| 177 | |||
| 178 | // Update in-memory fields for downstream consumers (PDF) |
||
| 179 | $this->certification_user_path = 'resource://user_certificate'; |
||
| 180 | $this->certificate_data['path_certificate'] = $fileName; |
||
| 181 | |||
| 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 | } |
||
| 189 | |||
| 190 | // Optional: keep the legacy user-certificate metadata updated |
||
| 191 | $this->updateUserCertificateInfo( |
||
| 192 | 0, |
||
| 193 | $this->user_id, |
||
| 194 | $fileName, |
||
| 195 | $updateCertificateData |
||
| 196 | ); |
||
| 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 | } |
||
| 204 | } |
||
| 205 | } |
||
| 206 | } |
||
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * Deletes the current certificate object. This is generally triggered by |
||
| 211 | * the teacher from the gradebook tool to re-generate the certificate because |
||
| 212 | * the original version wa flawed. |
||
| 213 | * |
||
| 214 | * @param bool $force_delete |
||
| 215 | * |
||
| 216 | * @return bool |
||
| 217 | */ |
||
| 218 | public function deleteCertificate(): bool |
||
| 219 | { |
||
| 220 | if (empty($this->certificate_data)) { |
||
| 221 | return false; |
||
| 222 | } |
||
| 223 | |||
| 224 | $categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0; |
||
| 225 | $certRepo = Container::getGradeBookCertificateRepository(); |
||
| 226 | |||
| 227 | try { |
||
| 228 | $certRepo->deleteCertificateResource($this->certificate_data['user_id'], $categoryId); |
||
| 229 | |||
| 230 | return true; |
||
| 231 | } catch (\Throwable $e) { |
||
| 232 | error_log('[CERTIFICATE::deleteCertificate] delete error: '.$e->getMessage()); |
||
| 233 | return false; |
||
| 234 | } |
||
| 235 | } |
||
| 236 | |||
| 237 | /** |
||
| 238 | * Generates (or updates) the user's certificate as a Resource. |
||
| 239 | * |
||
| 240 | * - Stores the HTML in a ResourceNode (resource_type = user_certificate or fallback handled at repository level). |
||
| 241 | * - Fills $this->certificate_data['file_content'] with the HTML to avoid PDF errors. |
||
| 242 | * - Keeps legacy DB info via registerUserInfoAboutCertificate() (no PersonalFile usage). |
||
| 243 | * |
||
| 244 | * @param array $params |
||
| 245 | * @param bool $sendNotification |
||
| 246 | * |
||
| 247 | * @return bool |
||
| 248 | */ |
||
| 249 | public function generate($params = [], $sendNotification = false) |
||
| 250 | { |
||
| 251 | // Safe defaults |
||
| 252 | $params = is_array($params) ? $params : []; |
||
| 253 | $params['hide_print_button'] = isset($params['hide_print_button']) |
||
| 254 | ? (bool) $params['hide_print_button'] |
||
| 255 | : false; |
||
| 256 | |||
| 257 | $certRepo = Container::getGradeBookCertificateRepository(); |
||
| 258 | |||
| 259 | $categoryId = 0; |
||
| 260 | $category = null; |
||
| 261 | $isCertificateAvailableInCategory = false; |
||
| 262 | |||
| 263 | // If the certificate is linked to a Gradebook category, check availability |
||
| 264 | if (isset($this->certificate_data['cat_id'])) { |
||
| 265 | $categoryId = (int) $this->certificate_data['cat_id']; |
||
| 266 | |||
| 267 | // Category::load() returns an array |
||
| 268 | $myCategory = Category::load($categoryId); |
||
| 269 | |||
| 270 | $repo = Container::getGradeBookCategoryRepository(); |
||
| 271 | /** @var \Chamilo\CoreBundle\Entity\GradebookCategory|null $category */ |
||
| 272 | $category = $repo->find($categoryId); |
||
| 273 | |||
| 274 | if (!empty($categoryId) && !empty($myCategory) && isset($myCategory[0])) { |
||
| 275 | $isCertificateAvailableInCategory = $myCategory[0]->is_certificate_available($this->user_id); |
||
| 276 | } |
||
| 277 | } |
||
| 278 | |||
| 279 | // Path A: course/session-bound certificate |
||
| 280 | if ($isCertificateAvailableInCategory && null !== $category) { |
||
| 281 | // Course/session info |
||
| 282 | $course = $category->getCourse(); |
||
| 283 | $courseInfo = api_get_course_info($course->getCode()); |
||
| 284 | $courseId = $courseInfo['real_id']; |
||
| 285 | $sessionId = $category->getSession() ? (int) $category->getSession()->getId() : 0; |
||
| 286 | |||
| 287 | // Award related skill |
||
| 288 | $skill = new SkillModel(); |
||
| 289 | $skill->addSkillToUser( |
||
| 290 | $this->user_id, |
||
| 291 | $category, |
||
| 292 | $courseId, |
||
| 293 | $sessionId |
||
| 294 | ); |
||
| 295 | |||
| 296 | // Build certificate HTML and score |
||
| 297 | $gb = GradebookUtils::get_user_certificate_content( |
||
| 298 | $this->user_id, |
||
| 299 | $course->getId(), |
||
| 300 | $sessionId, |
||
| 301 | false, |
||
| 302 | $params['hide_print_button'] |
||
| 303 | ); |
||
| 304 | |||
| 305 | $html = ''; |
||
| 306 | $score = 100.0; |
||
| 307 | |||
| 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; |
||
| 314 | } |
||
| 315 | |||
| 316 | if ($html === '') { |
||
| 317 | error_log('[CERT::generate] Empty HTML content for category certificate (cat='.$categoryId.', user='.$this->user_id.').'); |
||
| 318 | return false; |
||
| 319 | } |
||
| 320 | |||
| 321 | try { |
||
| 322 | // Persist as Resource and register legacy info (no PersonalFile) |
||
| 323 | $entity = $certRepo->upsertCertificateResource($categoryId, $this->user_id, $score, $html); |
||
| 324 | $certRepo->registerUserInfoAboutCertificate($categoryId, $this->user_id, $score); |
||
| 325 | |||
| 326 | // 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 |
||
| 329 | |||
| 330 | // Send notification if required (we have course context here) |
||
| 331 | if ($sendNotification) { |
||
| 332 | $subject = get_lang('Certificate notification'); |
||
| 333 | $message = nl2br(get_lang('((user_first_name)),')); |
||
| 334 | $htmlUrl = $certRepo->getResourceFileUrl($entity); |
||
| 335 | |||
| 336 | self::sendNotification( |
||
| 337 | $subject, |
||
| 338 | $message, |
||
| 339 | api_get_user_info($this->user_id), |
||
| 340 | $courseInfo, |
||
| 341 | [ |
||
| 342 | 'score_certificate' => $score, |
||
| 343 | 'html_url' => $htmlUrl, |
||
| 344 | ] |
||
| 345 | ); |
||
| 346 | } |
||
| 347 | |||
| 348 | return true; |
||
| 349 | } catch (\Throwable $e) { |
||
| 350 | error_log('[CERT::generate] Upsert failed for category certificate (cat='.$categoryId.', user='.$this->user_id.'): '.$e->getMessage()); |
||
| 351 | return false; |
||
| 352 | } |
||
| 353 | } |
||
| 354 | |||
| 355 | // Path B: general (portal-wide) certificate |
||
| 356 | try { |
||
| 357 | $html = $this->generateCustomCertificate(''); |
||
| 358 | $score = 100.0; |
||
| 359 | |||
| 360 | if ($html === '') { |
||
| 361 | error_log('[CERT::generate] Empty HTML content for general certificate (user='.$this->user_id.').'); |
||
| 362 | return false; |
||
| 363 | } |
||
| 364 | |||
| 365 | $entity = $certRepo->upsertCertificateResource(0, $this->user_id, $score, $html); |
||
| 366 | $certRepo->registerUserInfoAboutCertificate(0, $this->user_id, $score); |
||
| 367 | |||
| 368 | // Ensure PDF flow has the HTML in memory |
||
| 369 | $this->certificate_data['file_content'] = $html; |
||
| 370 | $this->certificate_data['path_certificate'] = ''; // stored as resource |
||
| 371 | |||
| 372 | // No course context here, so we skip notification (sendNotification would fail its own checks) |
||
| 373 | return true; |
||
| 374 | } catch (\Throwable $e) { |
||
| 375 | error_log('[CERT::generate] General certificate upsert failed (user='.$this->user_id.'): '.$e->getMessage()); |
||
| 376 | return false; |
||
| 377 | } |
||
| 378 | } |
||
| 379 | |||
| 380 | /** |
||
| 381 | * @return array |
||
| 382 | */ |
||
| 383 | public static function notificationTags() |
||
| 384 | { |
||
| 385 | $tags = [ |
||
| 386 | '((course_title))', |
||
| 387 | '((user_first_name))', |
||
| 388 | '((user_last_name))', |
||
| 389 | '((author_first_name))', |
||
| 390 | '((author_last_name))', |
||
| 391 | '((score))', |
||
| 392 | '((portal_name))', |
||
| 393 | '((certificate_link))', |
||
| 394 | ]; |
||
| 395 | |||
| 396 | return $tags; |
||
| 397 | } |
||
| 398 | |||
| 399 | /** |
||
| 400 | * @param string $subject |
||
| 401 | * @param string $message |
||
| 402 | * @param array $userInfo |
||
| 403 | * @param array $courseInfo |
||
| 404 | * @param array $certificateInfo |
||
| 405 | * |
||
| 406 | * @return bool |
||
| 407 | */ |
||
| 408 | public static function sendNotification( |
||
| 409 | $subject, |
||
| 410 | $message, |
||
| 411 | $userInfo, |
||
| 412 | $courseInfo, |
||
| 413 | $certificateInfo |
||
| 414 | ) { |
||
| 415 | if (empty($userInfo) || empty($courseInfo)) { |
||
| 416 | return false; |
||
| 417 | } |
||
| 418 | |||
| 419 | $currentUserInfo = api_get_user_info(); |
||
| 420 | $url = ''; |
||
| 421 | |||
| 422 | // Prefer resource URL if present |
||
| 423 | if (!empty($certificateInfo['html_url'])) { |
||
| 424 | $url = $certificateInfo['html_url']; |
||
| 425 | } elseif (!empty($certificateInfo['path_certificate'])) { |
||
| 426 | $hash = pathinfo($certificateInfo['path_certificate'], PATHINFO_FILENAME); |
||
| 427 | $url = api_get_path(WEB_PATH) . 'certificates/' . $hash . '.html'; |
||
| 428 | } |
||
| 429 | $link = Display::url($url, $url); |
||
| 430 | |||
| 431 | $replace = [ |
||
| 432 | $courseInfo['title'], |
||
| 433 | $userInfo['firstname'], |
||
| 434 | $userInfo['lastname'], |
||
| 435 | $currentUserInfo['firstname'], |
||
| 436 | $currentUserInfo['lastname'], |
||
| 437 | $certificateInfo['score_certificate'], |
||
| 438 | api_get_setting('Institution'), |
||
| 439 | $link, |
||
| 440 | ]; |
||
| 441 | |||
| 442 | $message = str_replace(self::notificationTags(), $replace, $message); |
||
| 443 | MessageManager::send_message( |
||
| 444 | $userInfo['id'], |
||
| 445 | $subject, |
||
| 446 | $message, |
||
| 447 | [], |
||
| 448 | [], |
||
| 449 | 0, |
||
| 450 | 0, |
||
| 451 | 0, |
||
| 452 | 0, |
||
| 453 | $currentUserInfo['id'] |
||
| 454 | ); |
||
| 455 | } |
||
| 456 | |||
| 457 | /** |
||
| 458 | * Update user info about certificate. |
||
| 459 | * |
||
| 460 | * @param int $categoryId category id |
||
| 461 | * @param int $user_id user id |
||
| 462 | * @param string $path_certificate the path name of the certificate |
||
| 463 | * @param bool $updateCertificateData |
||
| 464 | */ |
||
| 465 | public function updateUserCertificateInfo( |
||
| 466 | $categoryId, |
||
| 467 | $user_id, |
||
| 468 | $path_certificate, |
||
| 469 | $updateCertificateData = true |
||
| 470 | ) { |
||
| 471 | if (!$updateCertificateData) { |
||
| 472 | return; |
||
| 473 | } |
||
| 474 | $certRepo = Container::getGradeBookCertificateRepository(); |
||
| 475 | |||
| 476 | $certRepo->registerUserInfoAboutCertificate( |
||
| 477 | (int)$categoryId, |
||
| 478 | (int)$user_id, |
||
| 479 | (float)($this->certificate_data['score_certificate'] ?? 100.0), |
||
| 480 | (string)$path_certificate |
||
| 481 | ); |
||
| 482 | } |
||
| 483 | |||
| 484 | /** |
||
| 485 | * Check if the file was generated. |
||
| 486 | * |
||
| 487 | * @return bool |
||
| 488 | */ |
||
| 489 | public function isHtmlFileGenerated() |
||
| 490 | { |
||
| 491 | if (empty($this->certification_user_path)) { |
||
| 492 | return false; |
||
| 493 | } |
||
| 494 | if (!empty($this->certificate_data) && |
||
| 495 | isset($this->certificate_data['path_certificate']) && |
||
| 496 | !empty($this->certificate_data['path_certificate']) |
||
| 497 | ) { |
||
| 498 | return true; |
||
| 499 | } |
||
| 500 | |||
| 501 | return false; |
||
| 502 | } |
||
| 503 | |||
| 504 | /** |
||
| 505 | * Generates a QR code for the certificate. The QR code embeds the text given. |
||
| 506 | * |
||
| 507 | * @param string $text Text to be added in the QR code |
||
| 508 | * @param string $path file path of the image |
||
| 509 | * |
||
| 510 | * @return bool |
||
| 511 | */ |
||
| 512 | public function generateQRImage($text, $path): bool |
||
| 513 | { |
||
| 514 | throw new \Exception('generateQRImage'); |
||
| 515 | if (!empty($text) && !empty($path)) { |
||
|
|
|||
| 516 | $qrCode = new QrCode($text); |
||
| 517 | //$qrCode->setEncoding('UTF-8'); |
||
| 518 | $qrCode->setSize(120); |
||
| 519 | $qrCode->setMargin(5); |
||
| 520 | /*$qrCode->setWriterByName('png'); |
||
| 521 | $qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::MEDIUM()); |
||
| 522 | $qrCode->setForegroundColor(['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]); |
||
| 523 | $qrCode->setBackgroundColor(['r' => 255, 'g' => 255, 'b' => 255, 'a' => 0]); |
||
| 524 | $qrCode->setValidateResult(false); |
||
| 525 | $qrCode->writeFile($path);*/ |
||
| 526 | |||
| 527 | return true; |
||
| 528 | } |
||
| 529 | |||
| 530 | return false; |
||
| 531 | } |
||
| 532 | |||
| 533 | /** |
||
| 534 | * Transforms certificate tags into text values. This function is very static |
||
| 535 | * (it doesn't allow for much flexibility in terms of what tags are printed). |
||
| 536 | * |
||
| 537 | * @param array $array Contains two array entries: first are the headers, |
||
| 538 | * second is an array of contents |
||
| 539 | * |
||
| 540 | * @return string The translated string |
||
| 541 | */ |
||
| 542 | public function parseCertificateVariables($array) |
||
| 543 | { |
||
| 544 | $headers = $array[0]; |
||
| 545 | $content = $array[1]; |
||
| 546 | $final_content = []; |
||
| 547 | |||
| 548 | if (!empty($content)) { |
||
| 549 | foreach ($content as $key => $value) { |
||
| 550 | $my_header = str_replace(['((', '))'], '', $headers[$key]); |
||
| 551 | $final_content[$my_header] = $value; |
||
| 552 | } |
||
| 553 | } |
||
| 554 | |||
| 555 | /* Certificate tags |
||
| 556 | * |
||
| 557 | 0 => string '((user_firstname))' (length=18) |
||
| 558 | 1 => string '((user_lastname))' (length=17) |
||
| 559 | 2 => string '((gradebook_institution))' (length=25) |
||
| 560 | 3 => string '((gradebook_sitename))' (length=22) |
||
| 561 | 4 => string '((teacher_firstname))' (length=21) |
||
| 562 | 5 => string '((teacher_lastname))' (length=20) |
||
| 563 | 6 => string '((official_code))' (length=17) |
||
| 564 | 7 => string '((date_certificate))' (length=20) |
||
| 565 | 8 => string '((course_code))' (length=15) |
||
| 566 | 9 => string '((course_title))' (length=16) |
||
| 567 | 10 => string '((gradebook_grade))' (length=19) |
||
| 568 | 11 => string '((certificate_link))' (length=20) |
||
| 569 | 12 => string '((certificate_link_html))' (length=25) |
||
| 570 | 13 => string '((certificate_barcode))' (length=23) |
||
| 571 | */ |
||
| 572 | |||
| 573 | $break_space = " \n\r "; |
||
| 574 | $text = |
||
| 575 | $final_content['gradebook_institution'].' - '. |
||
| 576 | $final_content['gradebook_sitename'].' - '. |
||
| 577 | get_lang('Certification').$break_space. |
||
| 578 | get_lang('Learner').': '.$final_content['user_firstname'].' '.$final_content['user_lastname'].$break_space. |
||
| 579 | get_lang('Trainer').': '.$final_content['teacher_firstname'].' '.$final_content['teacher_lastname'].$break_space. |
||
| 580 | get_lang('Date').': '.$final_content['date_certificate'].$break_space. |
||
| 581 | get_lang('Score').': '.$final_content['gradebook_grade'].$break_space. |
||
| 582 | 'URL'.': '.$final_content['certificate_link']; |
||
| 583 | |||
| 584 | return $text; |
||
| 585 | } |
||
| 586 | |||
| 587 | /** |
||
| 588 | * Check if the certificate is visible for the current user |
||
| 589 | * If the global setting allow_public_certificates is set to 'false', no certificate can be printed. |
||
| 590 | * If the global allow_public_certificates is set to 'true' and the course setting allow_public_certificates |
||
| 591 | * is set to 0, no certificate *in this course* can be printed (for anonymous users). |
||
| 592 | * Connected users can always print them. |
||
| 593 | * |
||
| 594 | * @return bool |
||
| 595 | */ |
||
| 596 | public function isVisible() |
||
| 597 | { |
||
| 598 | if (!api_is_anonymous()) { |
||
| 599 | return true; |
||
| 600 | } |
||
| 601 | |||
| 602 | if ('true' != api_get_setting('certificate.allow_public_certificates')) { |
||
| 603 | // The "non-public" setting is set, so do not print |
||
| 604 | return false; |
||
| 605 | } |
||
| 606 | |||
| 607 | if (!isset($this->certificate_data, $this->certificate_data['cat_id'])) { |
||
| 608 | return false; |
||
| 609 | } |
||
| 610 | |||
| 611 | $gradeBook = new Gradebook(); |
||
| 612 | $gradeBookInfo = $gradeBook->get($this->certificate_data['cat_id']); |
||
| 613 | |||
| 614 | if (empty($gradeBookInfo['course_code'])) { |
||
| 615 | return false; |
||
| 616 | } |
||
| 617 | |||
| 618 | $setting = api_get_course_setting( |
||
| 619 | 'allow_public_certificates', |
||
| 620 | api_get_course_info($gradeBookInfo['course_code']) |
||
| 621 | ); |
||
| 622 | |||
| 623 | if (0 == $setting) { |
||
| 624 | // Printing not allowed |
||
| 625 | return false; |
||
| 626 | } |
||
| 627 | |||
| 628 | return true; |
||
| 629 | } |
||
| 630 | |||
| 631 | /** |
||
| 632 | * Check if the certificate is available. |
||
| 633 | * |
||
| 634 | * @return bool |
||
| 635 | */ |
||
| 636 | public function isAvailable() |
||
| 637 | { |
||
| 638 | $certRepo = Container::getGradeBookCertificateRepository(); |
||
| 639 | |||
| 640 | $categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0; |
||
| 641 | |||
| 642 | try { |
||
| 643 | $entity = $certRepo->getCertificateByUserId(0 === $categoryId ? null : $categoryId, $this->user_id); |
||
| 644 | if (!$entity || !$entity->hasResourceNode()) { |
||
| 645 | return false; |
||
| 646 | } |
||
| 647 | |||
| 648 | $node = $entity->getResourceNode(); |
||
| 649 | return $node->hasResourceFile() && $node->getResourceFiles()->count() > 0; |
||
| 650 | } catch (\Throwable $e) { |
||
| 651 | error_log('[CERTIFICATE::isAvailable] check error: '.$e->getMessage()); |
||
| 652 | return false; |
||
| 653 | } |
||
| 654 | } |
||
| 655 | |||
| 656 | /** |
||
| 657 | * Shows the student's certificate (HTML file). |
||
| 658 | */ |
||
| 659 | public function show() |
||
| 701 | } |
||
| 702 | } |
||
| 703 | |||
| 704 | /** |
||
| 705 | * @return string |
||
| 706 | */ |
||
| 707 | public function generateCustomCertificate(string $fileName = ''): string |
||
| 708 | { |
||
| 709 | $certificateRepo = Container::getGradeBookCertificateRepository(); |
||
| 710 | $certificateRepo->registerUserInfoAboutCertificate(0, $this->user_id, 100, $fileName); |
||
| 711 | |||
| 712 | $userInfo = api_get_user_info($this->user_id); |
||
| 713 | $extraFieldValue = new ExtraFieldValue('user'); |
||
| 714 | $value = $extraFieldValue->get_values_by_handler_and_field_variable($this->user_id, 'legal_accept'); |
||
| 715 | $termsValidationDate = ''; |
||
| 716 | if (isset($value) && !empty($value['value'])) { |
||
| 717 | [$id, $id2, $termsValidationDate] = explode(':', $value['value']); |
||
| 718 | } |
||
| 719 | |||
| 720 | $sessions = SessionManager::get_sessions_by_user($this->user_id, false, true); |
||
| 721 | $totalTimeInLearningPaths = 0; |
||
| 722 | $sessionsApproved = []; |
||
| 723 | $coursesApproved = []; |
||
| 724 | $courseList = []; |
||
| 725 | |||
| 726 | $gradeBookRepo = Container::getGradeBookCategoryRepository(); |
||
| 727 | if ($sessions) { |
||
| 728 | foreach ($sessions as $session) { |
||
| 729 | $allCoursesApproved = []; |
||
| 730 | foreach ($session['courses'] as $course) { |
||
| 731 | $course = api_get_course_entity($course['real_id']); |
||
| 732 | $courseId = $course->getId(); |
||
| 733 | /* @var GradebookCategory $category */ |
||
| 734 | $category = $gradeBookRepo->findOneBy(['course' => $course, 'session' => $session['session_id']]); |
||
| 735 | |||
| 736 | if (null !== $category) { |
||
| 737 | $result = Category::userFinishedCourse( |
||
| 738 | $this->user_id, |
||
| 739 | $category, |
||
| 740 | true, |
||
| 741 | $courseId, |
||
| 742 | $session['session_id'] |
||
| 743 | ); |
||
| 744 | |||
| 745 | $lpList = new LearnpathList( |
||
| 746 | $this->user_id, |
||
| 747 | api_get_course_info_by_id($courseId), |
||
| 748 | $session['session_id'] |
||
| 749 | ); |
||
| 750 | $lpFlatList = $lpList->get_flat_list(); |
||
| 751 | |||
| 752 | // Find time spent in LP |
||
| 753 | $timeSpent = Tracking::get_time_spent_in_lp( |
||
| 754 | $this->user_id, |
||
| 755 | $course, |
||
| 756 | !empty($lpFlatList) ? array_keys($lpFlatList) : [], |
||
| 757 | $session['session_id'] |
||
| 758 | ); |
||
| 759 | |||
| 760 | if (!isset($courseList[$courseId])) { |
||
| 761 | $courseList[$courseId]['approved'] = false; |
||
| 762 | $courseList[$courseId]['time_spent'] = 0; |
||
| 763 | } |
||
| 764 | |||
| 765 | if ($result) { |
||
| 766 | $courseList[$courseId]['approved'] = true; |
||
| 767 | $coursesApproved[$courseId] = $course->getTitle(); |
||
| 768 | $allCoursesApproved[] = true; |
||
| 769 | } |
||
| 770 | $courseList[$courseId]['time_spent'] += $timeSpent; |
||
| 771 | } |
||
| 772 | } |
||
| 773 | |||
| 774 | if (count($allCoursesApproved) == count($session['courses'])) { |
||
| 775 | $sessionsApproved[] = $session; |
||
| 776 | } |
||
| 777 | } |
||
| 778 | } |
||
| 779 | |||
| 780 | $totalTimeInLearningPaths = 0; |
||
| 781 | foreach ($courseList as $courseId => $courseData) { |
||
| 782 | if (true === $courseData['approved']) { |
||
| 783 | $totalTimeInLearningPaths += $courseData['time_spent']; |
||
| 784 | } |
||
| 785 | } |
||
| 786 | |||
| 787 | $skill = new SkillModel(); |
||
| 788 | $skills = $skill->getStudentSkills($this->user_id, 2); |
||
| 789 | $allowAll = ('true' === api_get_setting('skill.allow_teacher_access_student_skills')); |
||
| 790 | $courseIdForSkills = $allowAll ? 0 : 0; |
||
| 791 | $sessionIdForSkills = $allowAll ? 0 : 0; |
||
| 792 | $skillsTable = $skill->getUserSkillsTable( |
||
| 793 | $this->user_id, |
||
| 794 | $courseIdForSkills, |
||
| 795 | $sessionIdForSkills, |
||
| 796 | false |
||
| 797 | ); |
||
| 798 | |||
| 799 | $timeInSeconds = Tracking::get_time_spent_on_the_platform( |
||
| 800 | $this->user_id, |
||
| 801 | 'ever' |
||
| 802 | ); |
||
| 803 | $time = api_time_to_hms($timeInSeconds); |
||
| 804 | |||
| 805 | $tplContent = new Template(null, false, false, false, false, false); |
||
| 806 | |||
| 807 | // variables for the default template |
||
| 808 | $tplContent->assign('complete_name', $userInfo['complete_name']); |
||
| 809 | $tplContent->assign('time_in_platform', $time); |
||
| 810 | $tplContent->assign('certificate_generated_date', isset($myCertificate['created_at']) ? api_get_local_time($myCertificate['created_at']) : ''); |
||
| 811 | if (!empty($termsValidationDate)) { |
||
| 812 | $termsValidationDate = api_get_local_time($termsValidationDate); |
||
| 813 | } |
||
| 814 | $tplContent->assign('terms_validation_date', $termsValidationDate); |
||
| 815 | |||
| 816 | if (empty($totalTimeInLearningPaths)) { |
||
| 817 | $totalTimeInLearningPaths = $timeInSeconds; |
||
| 818 | } |
||
| 819 | |||
| 820 | // Ofaj |
||
| 821 | $tplContent->assign('time_in_platform_in_hours', round($timeInSeconds/3600, 1)); |
||
| 822 | $tplContent->assign( |
||
| 823 | 'certificate_generated_date_no_time', |
||
| 824 | api_get_local_time( |
||
| 825 | $myCertificate['created_at'] ?? null, |
||
| 826 | null, |
||
| 827 | null, |
||
| 828 | false, |
||
| 829 | false, |
||
| 830 | false, |
||
| 831 | 'd-m-Y' |
||
| 832 | ) |
||
| 833 | ); |
||
| 834 | $tplContent->assign( |
||
| 835 | 'terms_validation_date_no_time', |
||
| 836 | api_get_local_time( |
||
| 837 | $termsValidationDate, |
||
| 838 | null, |
||
| 839 | null, |
||
| 840 | false, |
||
| 841 | false, |
||
| 842 | false, |
||
| 843 | 'd-m-Y' |
||
| 844 | ) |
||
| 845 | ); |
||
| 846 | $tplContent->assign('skills', $skills); |
||
| 847 | $tplContent->assign('skills_table_html', $skillsTable['table']); |
||
| 848 | $tplContent->assign('skills_rows', $skillsTable['skills']); |
||
| 849 | $tplContent->assign('sessions', $sessionsApproved); |
||
| 850 | $tplContent->assign('courses', $coursesApproved); |
||
| 851 | $tplContent->assign('time_spent_in_lps', api_time_to_hms($totalTimeInLearningPaths)); |
||
| 852 | $tplContent->assign('time_spent_in_lps_in_hours', round($totalTimeInLearningPaths/3600, 1)); |
||
| 853 | |||
| 854 | $layoutContent = $tplContent->get_template('gradebook/custom_certificate.html.twig'); |
||
| 855 | $content = $tplContent->fetch($layoutContent); |
||
| 856 | |||
| 857 | return $content; |
||
| 858 | } |
||
| 859 | |||
| 860 | /** |
||
| 861 | * Ofaj. |
||
| 862 | */ |
||
| 863 | public function generatePdfFromCustomCertificate(): void |
||
| 864 | { |
||
| 865 | $orientation = api_get_setting('certificate.certificate_pdf_orientation'); |
||
| 866 | |||
| 867 | $params['orientation'] = 'landscape'; |
||
| 868 | if (!empty($orientation)) { |
||
| 869 | $params['orientation'] = $orientation; |
||
| 870 | } |
||
| 871 | |||
| 872 | $params['left'] = 0; |
||
| 873 | $params['right'] = 0; |
||
| 874 | $params['top'] = 0; |
||
| 875 | $params['bottom'] = 0; |
||
| 876 | $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4'; |
||
| 877 | $pdf = new PDF($page_format, $params['orientation'], $params); |
||
| 878 | |||
| 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 | |||
| 892 | $pdf->content_to_pdf( |
||
| 893 | $this->certificate_data['file_content'], |
||
| 894 | null, |
||
| 895 | get_lang('Certificates'), |
||
| 896 | api_get_course_id(), |
||
| 897 | 'D', |
||
| 898 | false, |
||
| 899 | null, |
||
| 900 | false, |
||
| 901 | true, |
||
| 902 | true, |
||
| 903 | true, |
||
| 904 | true |
||
| 905 | ); |
||
| 906 | } |
||
| 907 | |||
| 908 | /** |
||
| 909 | * @param int $userId |
||
| 910 | * |
||
| 911 | * @return array |
||
| 912 | */ |
||
| 913 | public static function getCertificateByUser($userId) |
||
| 914 | { |
||
| 915 | $userId = (int) $userId; |
||
| 916 | if (empty($userId)) { |
||
| 917 | return []; |
||
| 918 | } |
||
| 919 | |||
| 920 | $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE); |
||
| 921 | $sql = "SELECT * FROM $table |
||
| 922 | WHERE user_id= $userId"; |
||
| 923 | $rs = Database::query($sql); |
||
| 924 | |||
| 925 | return Database::store_result($rs, 'ASSOC'); |
||
| 926 | } |
||
| 927 | |||
| 928 | /** |
||
| 929 | * @param int $userId |
||
| 930 | */ |
||
| 931 | public static function generateUserSkills($userId) |
||
| 976 | } |
||
| 977 | } |
||
| 978 | } |
||
| 979 | } |
||
| 980 | } |
||
| 981 | } |
||
| 982 | } |
||
| 983 | } |
||
| 984 | } |
||
| 985 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.