Passed
Pull Request — master (#6922)
by
unknown
08:21
created

Certificate::generateQRImage()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 1
nop 2
dl 0
loc 19
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\GradebookCategory;
6
use Chamilo\CoreBundle\Entity\PersonalFile;
7
use Chamilo\CoreBundle\Entity\ResourceFile;
8
use Chamilo\CoreBundle\Entity\ResourceNode;
9
use Chamilo\CoreBundle\Framework\Container;
10
use Endroid\QrCode\ErrorCorrectionLevel;
11
use Endroid\QrCode\QrCode;
12
use JetBrains\PhpStorm\NoReturn;
13
use Symfony\Component\HttpFoundation\File\UploadedFile;
14
15
/**
16
 * Certificate Class
17
 * Generate certificates based in the gradebook tool.
18
 */
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) ? $userId : api_get_user_id();
69
70
        if (!empty($certificate_id)) {
71
            $certificate = $this->get($certificate_id);
72
            if (!empty($certificate) && is_array($certificate)) {
73
                $this->certificate_data = $certificate;
74
                $this->user_id = $this->certificate_data['user_id'];
75
            }
76
        }
77
78
        if ($this->user_id) {
79
            // To force certification generation
80
            if ($this->force_certificate_generation) {
81
                $this->generate(['certificate_path' => ''], $sendNotification);
82
            }
83
            if (
84
                isset($this->certificate_data)
85
                && $this->certificate_data
86
                && empty($this->certificate_data['path_certificate'])
87
            ) {
88
                $this->generate(['certificate_path' => $pathToCertificate], $sendNotification);
89
            }
90
        }
91
92
        // Setting the qr and html variables
93
        if (isset($certificate_id) &&
94
            !empty($this->certification_user_path) &&
95
            isset($this->certificate_data['path_certificate'])
96
        ) {
97
            //$pathinfo = pathinfo($this->certificate_data['path_certificate']);
98
            $this->html_file = $this->certificate_data['path_certificate'];
99
            //$this->qr_file = $this->certification_user_path.$pathinfo['filename'].'_qr.png';
100
        } else {
101
            //$this->checkCertificatePath();
102
            if ('true' === api_get_setting('certificate.allow_general_certificate')) {
103
                // General certificate
104
                // 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
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) {
129
                    // Updating the path
130
                    self::updateUserCertificateInfo(
131
                        0,
132
                        $this->user_id,
133
                        $fileName,
134
                        $updateCertificateData
135
                    );
136
                    $this->certificate_data['path_certificate'] = $fileName;
137
                }
138
            }
139
        }
140
    }
141
142
    /**
143
     * Deletes the current certificate object. This is generally triggered by
144
     * the teacher from the gradebook tool to re-generate the certificate because
145
     * the original version wa flawed.
146
     *
147
     * @param bool $force_delete
148
     *
149
     * @return bool
150
     */
151
    public function deleteCertificate(): bool
152
    {
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);
162
163
            return true;
164
        } catch (\Throwable $e) {
165
            error_log('[CERTIFICATE::deleteCertificate] delete error: '.$e->getMessage());
166
            return false;
167
        }
168
    }
169
170
    /**
171
     * Generates (or updates) the user's certificate as a Resource.
172
     *
173
     * - Stores the HTML in a ResourceNode (resource_type = user_certificate or fallback handled at repository level).
174
     * - Fills $this->certificate_data['file_content'] with the HTML to avoid PDF errors.
175
     * - Keeps legacy DB info via registerUserInfoAboutCertificate() (no PersonalFile usage).
176
     *
177
     * @param array $params
178
     * @param bool  $sendNotification
179
     *
180
     * @return bool
181
     */
182
    public function generate($params = [], $sendNotification = false)
183
    {
184
        // Safe defaults
185
        $params = is_array($params) ? $params : [];
186
        $params['hide_print_button'] = isset($params['hide_print_button'])
187
            ? (bool) $params['hide_print_button']
188
            : false;
189
190
        $certRepo = Container::getGradeBookCertificateRepository();
191
192
        $categoryId = 0;
193
        $category   = null;
194
        $isCertificateAvailableInCategory = false;
195
196
        // If the certificate is linked to a Gradebook category, check availability
197
        if (isset($this->certificate_data['cat_id'])) {
198
            $categoryId = (int) $this->certificate_data['cat_id'];
199
200
            // Category::load() returns an array
201
            $myCategory = Category::load($categoryId);
202
203
            $repo = Container::getGradeBookCategoryRepository();
204
            /** @var \Chamilo\CoreBundle\Entity\GradebookCategory|null $category */
205
            $category = $repo->find($categoryId);
206
207
            if (!empty($categoryId) && !empty($myCategory) && isset($myCategory[0])) {
208
                $isCertificateAvailableInCategory = $myCategory[0]->is_certificate_available($this->user_id);
209
            }
210
        }
211
212
        // Path A: course/session-bound certificate
213
        if ($isCertificateAvailableInCategory && null !== $category) {
214
            // Course/session info
215
            $course     = $category->getCourse();
216
            $courseInfo = api_get_course_info($course->getCode());
217
            $courseId   = $courseInfo['real_id'];
218
            $sessionId  = $category->getSession() ? (int) $category->getSession()->getId() : 0;
219
220
            // Award related skill
221
            $skill = new SkillModel();
222
            $skill->addSkillToUser(
223
                $this->user_id,
224
                $category,
225
                $courseId,
226
                $sessionId
227
            );
228
229
            // Build certificate HTML and score
230
            $gb = GradebookUtils::get_user_certificate_content(
231
                $this->user_id,
232
                $course->getId(),
233
                $sessionId,
234
                false,
235
                $params['hide_print_button']
236
            );
237
238
            $html  = '';
239
            $score = 100.0;
240
241
            if (is_array($gb)) {
242
                $html  = isset($gb['content']) ? (string) $gb['content'] : '';
243
                $score = isset($gb['score']) ? (float) $gb['score'] : 100.0;
244
            } elseif (is_string($gb) && $gb !== '') {
245
                // Some custom implementations might return a raw string
246
                $html = $gb;
247
            }
248
249
            if ($html === '') {
250
                error_log('[CERT::generate] Empty HTML content for category certificate (cat='.$categoryId.', user='.$this->user_id.').');
251
                return false;
252
            }
253
254
            try {
255
                // Persist as Resource and register legacy info (no PersonalFile)
256
                $entity = $certRepo->upsertCertificateResource($categoryId, $this->user_id, $score, $html);
257
                $certRepo->registerUserInfoAboutCertificate($categoryId, $this->user_id, $score);
258
259
                // Ensure PDF flow has the HTML in memory
260
                $this->certificate_data['file_content']   = $html;
261
                $this->certificate_data['path_certificate'] = ''; // stored as resource, no legacy file path
262
263
                // Send notification if required (we have course context here)
264
                if ($sendNotification) {
265
                    $subject = get_lang('Certificate notification');
266
                    $message = nl2br(get_lang('((user_first_name)),'));
267
                    $htmlUrl = $certRepo->getResourceFileUrl($entity);
268
269
                    self::sendNotification(
270
                        $subject,
271
                        $message,
272
                        api_get_user_info($this->user_id),
273
                        $courseInfo,
274
                        [
275
                            'score_certificate' => $score,
276
                            'html_url'          => $htmlUrl,
277
                        ]
278
                    );
279
                }
280
281
                return true;
282
            } catch (\Throwable $e) {
283
                error_log('[CERT::generate] Upsert failed for category certificate (cat='.$categoryId.', user='.$this->user_id.'): '.$e->getMessage());
284
                return false;
285
            }
286
        }
287
288
        // Path B: general (portal-wide) certificate
289
        try {
290
            $html   = $this->generateCustomCertificate('');
291
            $score  = 100.0;
292
293
            if ($html === '') {
294
                error_log('[CERT::generate] Empty HTML content for general certificate (user='.$this->user_id.').');
295
                return false;
296
            }
297
298
            $entity = $certRepo->upsertCertificateResource(0, $this->user_id, $score, $html);
299
            $certRepo->registerUserInfoAboutCertificate(0, $this->user_id, $score);
300
301
            // Ensure PDF flow has the HTML in memory
302
            $this->certificate_data['file_content']     = $html;
303
            $this->certificate_data['path_certificate'] = ''; // stored as resource
304
305
            // No course context here, so we skip notification (sendNotification would fail its own checks)
306
            return true;
307
        } catch (\Throwable $e) {
308
            error_log('[CERT::generate] General certificate upsert failed (user='.$this->user_id.'): '.$e->getMessage());
309
            return false;
310
        }
311
    }
312
313
    /**
314
     * @return array
315
     */
316
    public static function notificationTags()
317
    {
318
        $tags = [
319
            '((course_title))',
320
            '((user_first_name))',
321
            '((user_last_name))',
322
            '((author_first_name))',
323
            '((author_last_name))',
324
            '((score))',
325
            '((portal_name))',
326
            '((certificate_link))',
327
        ];
328
329
        return $tags;
330
    }
331
332
    /**
333
     * @param string $subject
334
     * @param string $message
335
     * @param array  $userInfo
336
     * @param array  $courseInfo
337
     * @param array  $certificateInfo
338
     *
339
     * @return bool
340
     */
341
    public static function sendNotification(
342
        $subject,
343
        $message,
344
        $userInfo,
345
        $courseInfo,
346
        $certificateInfo
347
    ) {
348
        if (empty($userInfo) || empty($courseInfo)) {
349
            return false;
350
        }
351
352
        $currentUserInfo = api_get_user_info();
353
        $url = '';
354
355
        // Prefer resource URL if present
356
        if (!empty($certificateInfo['html_url'])) {
357
            $url = $certificateInfo['html_url'];
358
        } elseif (!empty($certificateInfo['path_certificate'])) {
359
            $hash = pathinfo($certificateInfo['path_certificate'], PATHINFO_FILENAME);
360
            $url = api_get_path(WEB_PATH) . 'certificates/' . $hash . '.html';
361
        }
362
        $link = Display::url($url, $url);
363
364
        $replace = [
365
            $courseInfo['title'],
366
            $userInfo['firstname'],
367
            $userInfo['lastname'],
368
            $currentUserInfo['firstname'],
369
            $currentUserInfo['lastname'],
370
            $certificateInfo['score_certificate'],
371
            api_get_setting('Institution'),
372
            $link,
373
        ];
374
375
        $message = str_replace(self::notificationTags(), $replace, $message);
376
        MessageManager::send_message(
377
            $userInfo['id'],
378
            $subject,
379
            $message,
380
            [],
381
            [],
382
            0,
383
            0,
384
            0,
385
            0,
386
            $currentUserInfo['id']
387
        );
388
    }
389
390
    /**
391
     * Update user info about certificate.
392
     *
393
     * @param int    $categoryId            category id
394
     * @param int    $user_id               user id
395
     * @param string $path_certificate      the path name of the certificate
396
     * @param bool   $updateCertificateData
397
     */
398
    public function updateUserCertificateInfo(
399
        $categoryId,
400
        $user_id,
401
        $path_certificate,
402
        $updateCertificateData = true
403
    ) {
404
        if (!$updateCertificateData) {
405
            return;
406
        }
407
        $certRepo = Container::getGradeBookCertificateRepository();
408
409
        $certRepo->registerUserInfoAboutCertificate(
410
            (int)$categoryId,
411
            (int)$user_id,
412
            (float)($this->certificate_data['score_certificate'] ?? 100.0),
413
            (string)$path_certificate
414
        );
415
    }
416
417
    /**
418
     * Check if the file was generated.
419
     *
420
     * @return bool
421
     */
422
    public function isHtmlFileGenerated()
423
    {
424
        if (empty($this->certification_user_path)) {
425
            return false;
426
        }
427
        if (!empty($this->certificate_data) &&
428
            isset($this->certificate_data['path_certificate']) &&
429
            !empty($this->certificate_data['path_certificate'])
430
        ) {
431
            return true;
432
        }
433
434
        return false;
435
    }
436
437
    /**
438
     * Generates a QR code for the certificate. The QR code embeds the text given.
439
     *
440
     * @param string $text Text to be added in the QR code
441
     * @param string $path file path of the image
442
     *
443
     * @return bool
444
     */
445
    public function generateQRImage($text, $path): bool
446
    {
447
        throw new \Exception('generateQRImage');
448
        if (!empty($text) && !empty($path)) {
0 ignored issues
show
Unused Code introduced by
IfNode is not reachable.

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, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
449
            $qrCode = new QrCode($text);
450
            //$qrCode->setEncoding('UTF-8');
451
            $qrCode->setSize(120);
452
            $qrCode->setMargin(5);
453
            /*$qrCode->setWriterByName('png');
454
            $qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::MEDIUM());
455
            $qrCode->setForegroundColor(['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]);
456
            $qrCode->setBackgroundColor(['r' => 255, 'g' => 255, 'b' => 255, 'a' => 0]);
457
            $qrCode->setValidateResult(false);
458
            $qrCode->writeFile($path);*/
459
460
            return true;
461
        }
462
463
        return false;
464
    }
465
466
    /**
467
     * Transforms certificate tags into text values. This function is very static
468
     * (it doesn't allow for much flexibility in terms of what tags are printed).
469
     *
470
     * @param array $array Contains two array entries: first are the headers,
471
     *                     second is an array of contents
472
     *
473
     * @return string The translated string
474
     */
475
    public function parseCertificateVariables($array)
476
    {
477
        $headers = $array[0];
478
        $content = $array[1];
479
        $final_content = [];
480
481
        if (!empty($content)) {
482
            foreach ($content as $key => $value) {
483
                $my_header = str_replace(['((', '))'], '', $headers[$key]);
484
                $final_content[$my_header] = $value;
485
            }
486
        }
487
488
        /* Certificate tags
489
         *
490
          0 => string '((user_firstname))' (length=18)
491
          1 => string '((user_lastname))' (length=17)
492
          2 => string '((gradebook_institution))' (length=25)
493
          3 => string '((gradebook_sitename))' (length=22)
494
          4 => string '((teacher_firstname))' (length=21)
495
          5 => string '((teacher_lastname))' (length=20)
496
          6 => string '((official_code))' (length=17)
497
          7 => string '((date_certificate))' (length=20)
498
          8 => string '((course_code))' (length=15)
499
          9 => string '((course_title))' (length=16)
500
          10 => string '((gradebook_grade))' (length=19)
501
          11 => string '((certificate_link))' (length=20)
502
          12 => string '((certificate_link_html))' (length=25)
503
          13 => string '((certificate_barcode))' (length=23)
504
         */
505
506
        $break_space = " \n\r ";
507
        $text =
508
            $final_content['gradebook_institution'].' - '.
509
            $final_content['gradebook_sitename'].' - '.
510
            get_lang('Certification').$break_space.
511
            get_lang('Learner').': '.$final_content['user_firstname'].' '.$final_content['user_lastname'].$break_space.
512
            get_lang('Trainer').': '.$final_content['teacher_firstname'].' '.$final_content['teacher_lastname'].$break_space.
513
            get_lang('Date').': '.$final_content['date_certificate'].$break_space.
514
            get_lang('Score').': '.$final_content['gradebook_grade'].$break_space.
515
            'URL'.': '.$final_content['certificate_link'];
516
517
        return $text;
518
    }
519
520
    /**
521
     * Check if the certificate is visible for the current user
522
     * If the global setting allow_public_certificates is set to 'false', no certificate can be printed.
523
     * If the global allow_public_certificates is set to 'true' and the course setting allow_public_certificates
524
     * is set to 0, no certificate *in this course* can be printed (for anonymous users).
525
     * Connected users can always print them.
526
     *
527
     * @return bool
528
     */
529
    public function isVisible()
530
    {
531
        if (!api_is_anonymous()) {
532
            return true;
533
        }
534
535
        if ('true' != api_get_setting('certificate.allow_public_certificates')) {
536
            // The "non-public" setting is set, so do not print
537
            return false;
538
        }
539
540
        if (!isset($this->certificate_data, $this->certificate_data['cat_id'])) {
541
            return false;
542
        }
543
544
        $gradeBook = new Gradebook();
545
        $gradeBookInfo = $gradeBook->get($this->certificate_data['cat_id']);
546
547
        if (empty($gradeBookInfo['course_code'])) {
548
            return false;
549
        }
550
551
        $setting = api_get_course_setting(
552
            'allow_public_certificates',
553
            api_get_course_info($gradeBookInfo['course_code'])
554
        );
555
556
        if (0 == $setting) {
557
            // Printing not allowed
558
            return false;
559
        }
560
561
        return true;
562
    }
563
564
    /**
565
     * Check if the certificate is available.
566
     *
567
     * @return bool
568
     */
569
    public function isAvailable()
570
    {
571
        $certRepo = Container::getGradeBookCertificateRepository();
572
573
        $categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
574
575
        try {
576
            $entity = $certRepo->getCertificateByUserId(0 === $categoryId ? null : $categoryId, $this->user_id);
577
            if (!$entity || !$entity->hasResourceNode()) {
578
                return false;
579
            }
580
581
            $node = $entity->getResourceNode();
582
            return $node->hasResourceFile() && $node->getResourceFiles()->count() > 0;
583
        } catch (\Throwable $e) {
584
            error_log('[CERTIFICATE::isAvailable] check error: '.$e->getMessage());
585
            return false;
586
        }
587
    }
588
589
    /**
590
     * Shows the student's certificate (HTML file).
591
     */
592
    public function show()
593
    {
594
        $certRepo = Container::getGradeBookCertificateRepository();
595
        $categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0;
596
597
        try {
598
            $entity = $certRepo->getCertificateByUserId(0 === $categoryId ? null : $categoryId, $this->user_id);
599
            if (!$entity || !$entity->hasResourceNode()) {
600
                api_not_allowed(true);
601
            }
602
603
            // Read HTML content from the Resource layer
604
            $certificateContent = '<!DOCTYPE html>';
605
            $certificateContent .= $certRepo->getResourceFileContent($entity);
606
            $certificateContent = str_replace(' media="screen"', '', $certificateContent);
607
608
            // Track “downloaded_at” (legacy extra fields)
609
            if ($this->user_id == api_get_user_id() &&
610
                !empty($this->certificate_data) &&
611
                isset($this->certificate_data['id'])
612
            ) {
613
                $certificateId = $this->certificate_data['id'];
614
                $extraFieldValue = new ExtraFieldValue('user_certificate');
615
                $value = $extraFieldValue->get_values_by_handler_and_field_variable(
616
                    $certificateId,
617
                    'downloaded_at'
618
                );
619
                if (empty($value)) {
620
                    $params = [
621
                        'item_id' => $this->certificate_data['id'],
622
                        'extra_downloaded_at' => api_get_utc_datetime(),
623
                    ];
624
                    $extraFieldValue->saveFieldValues($params);
625
                }
626
            }
627
628
            header('Content-Type: text/html; charset='.api_get_system_encoding());
629
            echo $certificateContent;
630
            return;
631
        } catch (\Throwable $e) {
632
            error_log('[CERTIFICATE::show] read error: '.$e->getMessage());
633
            api_not_allowed(true);
634
        }
635
    }
636
637
    /**
638
     * @return string
639
     */
640
    public function generateCustomCertificate(string $fileName = ''): string
641
    {
642
        $certificateRepo = Container::getGradeBookCertificateRepository();
643
        $certificateRepo->registerUserInfoAboutCertificate(0, $this->user_id, 100, $fileName);
644
645
        $userInfo = api_get_user_info($this->user_id);
646
        $extraFieldValue = new ExtraFieldValue('user');
647
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($this->user_id, 'legal_accept');
648
        $termsValidationDate = '';
649
        if (isset($value) && !empty($value['value'])) {
650
            [$id, $id2, $termsValidationDate] = explode(':', $value['value']);
651
        }
652
653
        $sessions = SessionManager::get_sessions_by_user($this->user_id, false, true);
654
        $totalTimeInLearningPaths = 0;
655
        $sessionsApproved = [];
656
        $coursesApproved = [];
657
        $courseList = [];
658
659
        $gradeBookRepo = Container::getGradeBookCategoryRepository();
660
        if ($sessions) {
661
            foreach ($sessions as $session) {
662
                $allCoursesApproved = [];
663
                foreach ($session['courses'] as $course) {
664
                    $course = api_get_course_entity($course['real_id']);
665
                    $courseId = $course->getId();
666
                    /* @var GradebookCategory $category */
667
                    $category = $gradeBookRepo->findOneBy(['course' => $course, 'session' => $session['session_id']]);
668
669
                    if (null !== $category) {
670
                        $result = Category::userFinishedCourse(
671
                            $this->user_id,
672
                            $category,
673
                            true,
674
                            $courseId,
675
                            $session['session_id']
676
                        );
677
678
                        $lpList = new LearnpathList(
679
                            $this->user_id,
680
                            api_get_course_info_by_id($courseId),
681
                            $session['session_id']
682
                        );
683
                        $lpFlatList = $lpList->get_flat_list();
684
685
                        // Find time spent in LP
686
                        $timeSpent = Tracking::get_time_spent_in_lp(
687
                            $this->user_id,
688
                            $course,
689
                            !empty($lpFlatList) ? array_keys($lpFlatList) : [],
690
                            $session['session_id']
691
                        );
692
693
                        if (!isset($courseList[$courseId])) {
694
                            $courseList[$courseId]['approved'] = false;
695
                            $courseList[$courseId]['time_spent'] = 0;
696
                        }
697
698
                        if ($result) {
699
                            $courseList[$courseId]['approved'] = true;
700
                            $coursesApproved[$courseId] = $course->getTitle();
701
                            $allCoursesApproved[] = true;
702
                        }
703
                        $courseList[$courseId]['time_spent'] += $timeSpent;
704
                    }
705
                }
706
707
                if (count($allCoursesApproved) == count($session['courses'])) {
708
                    $sessionsApproved[] = $session;
709
                }
710
            }
711
        }
712
713
        $totalTimeInLearningPaths = 0;
714
        foreach ($courseList as $courseId => $courseData) {
715
            if (true === $courseData['approved']) {
716
                $totalTimeInLearningPaths += $courseData['time_spent'];
717
            }
718
        }
719
720
        $skill = new SkillModel();
721
        $skills = $skill->getStudentSkills($this->user_id, 2);
722
        $allowAll = ('true' === api_get_setting('skill.allow_teacher_access_student_skills'));
723
        $courseIdForSkills  = $allowAll ? 0 : 0;
724
        $sessionIdForSkills = $allowAll ? 0 : 0;
725
        $skillsTable = $skill->getUserSkillsTable(
726
            $this->user_id,
727
            $courseIdForSkills,
728
            $sessionIdForSkills,
729
            false
730
        );
731
732
        $timeInSeconds = Tracking::get_time_spent_on_the_platform(
733
            $this->user_id,
734
            'ever'
735
        );
736
        $time = api_time_to_hms($timeInSeconds);
737
738
        $tplContent = new Template(null, false, false, false, false, false);
739
740
        // variables for the default template
741
        $tplContent->assign('complete_name', $userInfo['complete_name']);
742
        $tplContent->assign('time_in_platform', $time);
743
        $tplContent->assign('certificate_generated_date', isset($myCertificate['created_at']) ? api_get_local_time($myCertificate['created_at']) : '');
744
        if (!empty($termsValidationDate)) {
745
            $termsValidationDate = api_get_local_time($termsValidationDate);
746
        }
747
        $tplContent->assign('terms_validation_date', $termsValidationDate);
748
749
        if (empty($totalTimeInLearningPaths)) {
750
            $totalTimeInLearningPaths = $timeInSeconds;
751
        }
752
753
        // Ofaj
754
        $tplContent->assign('time_in_platform_in_hours', round($timeInSeconds/3600, 1));
755
        $tplContent->assign(
756
            'certificate_generated_date_no_time',
757
            api_get_local_time(
758
                $myCertificate['created_at'] ?? null,
759
                null,
760
                null,
761
                false,
762
                false,
763
                false,
764
                'd-m-Y'
765
            )
766
        );
767
        $tplContent->assign(
768
            'terms_validation_date_no_time',
769
            api_get_local_time(
770
                $termsValidationDate,
771
                null,
772
                null,
773
                false,
774
                false,
775
                false,
776
                'd-m-Y'
777
            )
778
        );
779
        $tplContent->assign('skills', $skills);
780
        $tplContent->assign('skills_table_html', $skillsTable['table']);
781
        $tplContent->assign('skills_rows', $skillsTable['skills']);
782
        $tplContent->assign('sessions', $sessionsApproved);
783
        $tplContent->assign('courses', $coursesApproved);
784
        $tplContent->assign('time_spent_in_lps', api_time_to_hms($totalTimeInLearningPaths));
785
        $tplContent->assign('time_spent_in_lps_in_hours', round($totalTimeInLearningPaths/3600, 1));
786
787
        $layoutContent = $tplContent->get_template('gradebook/custom_certificate.html.twig');
788
        $content = $tplContent->fetch($layoutContent);
789
790
        return $content;
791
    }
792
793
    /**
794
     * Ofaj.
795
     */
796
    public function generatePdfFromCustomCertificate(): void
797
    {
798
        $orientation = api_get_setting('certificate.certificate_pdf_orientation');
799
800
        $params['orientation'] = 'landscape';
801
        if (!empty($orientation)) {
802
            $params['orientation'] = $orientation;
803
        }
804
805
        $params['left'] = 0;
806
        $params['right'] = 0;
807
        $params['top'] = 0;
808
        $params['bottom'] = 0;
809
        $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
810
        $pdf = new PDF($page_format, $params['orientation'], $params);
811
812
        $pdf->content_to_pdf(
813
            $this->certificate_data['file_content'],
814
            null,
815
            get_lang('Certificates'),
816
            api_get_course_id(),
817
            'D',
818
            false,
819
            null,
820
            false,
821
            true,
822
            true,
823
            true,
824
            true
825
        );
826
    }
827
828
    /**
829
     * @param int $userId
830
     *
831
     * @return array
832
     */
833
    public static function getCertificateByUser($userId)
834
    {
835
        $userId = (int) $userId;
836
        if (empty($userId)) {
837
            return [];
838
        }
839
840
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
841
        $sql = "SELECT * FROM $table
842
                WHERE user_id= $userId";
843
        $rs = Database::query($sql);
844
845
        return Database::store_result($rs, 'ASSOC');
846
    }
847
848
    /**
849
     * @param int $userId
850
     */
851
    public static function generateUserSkills($userId)
852
    {
853
        $controller = new IndexManager(get_lang('My courses'));
854
        $courseAndSessions = $controller->returnCoursesAndSessions($userId, true, null, true, false);
855
        $repo = Container::getGradeBookCategoryRepository();
856
        if (isset($courseAndSessions['courses']) && !empty($courseAndSessions['courses'])) {
857
            foreach ($courseAndSessions['courses'] as $course) {
858
                $category = $repo->findOneBy(['course' => $course['real_id']]);
859
                /*$cats = Category::load(
860
                    null,
861
                    null,
862
                    $course['code'],
863
                    null,
864
                    null,
865
                    null,
866
                    false
867
                );*/
868
                if (null !== $category) {
869
                    Category::generateUserCertificate($category, $userId);
870
                }
871
            }
872
        }
873
874
        if (isset($courseAndSessions['sessions']) && !empty($courseAndSessions['sessions'])) {
875
            foreach ($courseAndSessions['sessions'] as $sessionCategory) {
876
                if (isset($sessionCategory['sessions'])) {
877
                    foreach ($sessionCategory['sessions'] as $sessionData) {
878
                        if (!empty($sessionData['courses'])) {
879
                            $sessionId = $sessionData['session_id'];
880
                            foreach ($sessionData['courses'] as $courseData) {
881
                                /*$cats = Category:: load(
882
                                    null,
883
                                    null,
884
                                    $courseData['course_code'],
885
                                    null,
886
                                    null,
887
                                    $sessionId,
888
                                    false
889
                                );*/
890
891
                                $category = $repo->findOneBy(
892
                                    ['course' => $courseData['real_id'], 'session' => $sessionId]
893
                                );
894
                                if (null !== $category) {
895
                                    Category::generateUserCertificate($category, $userId);
896
                                }
897
                            }
898
                        }
899
                    }
900
                }
901
            }
902
        }
903
    }
904
}
905