PDF   F
last analyzed

Complexity

Total Complexity 125

Size/Duplication

Total Lines 1025
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 125
eloc 492
dl 0
loc 1025
rs 2
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A setBackground() 0 18 4
A delete_watermark() 0 18 4
A upload_watermark() 0 23 5
A setCertificateFooter() 0 12 1
A get_header() 0 2 1
A get_watermark() 0 20 5
A set_footer() 0 12 1
A set_custom_header() 0 3 1
B set_header() 0 39 7
A exportFromHtmlToFile() 0 15 2
A exportFromHtmlToDocumentsArea() 0 33 1
A set_custom_footer() 0 3 1
B html_to_pdf_with_template() 0 80 9
A __construct() 0 52 3
A replaceIconsWithImages() 0 22 2
C format_pdf() 0 57 14
F content_to_pdf() 0 153 21
F html_to_pdf() 0 151 27
C fixImagesPaths() 0 107 16

How to fix   Complexity   

Complex Class

Complex classes like PDF 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 PDF, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* See license terms in /license.txt */
4
5
use Chamilo\CoreBundle\Framework\Container;
6
use Masterminds\HTML5;
7
use Mpdf\HTMLParserMode;
8
use Mpdf\Mpdf;
9
use Mpdf\MpdfException;
10
use Mpdf\Output\Destination;
11
use Mpdf\Utils\UtfString;
12
use Symfony\Component\DomCrawler\Crawler;
13
14
/**
15
 * Class PDF.
16
 */
17
class PDF
18
{
19
    /** @var Mpdf */
20
    public $pdf;
21
    public $custom_header = [];
22
    public $custom_footer = [];
23
    public $params = [];
24
    public $template;
25
26
    /**
27
     * Creates the mPDF object.
28
     *
29
     * @param string   $pageFormat  format A4 A4-L see
30
     *                              http://mpdf1.com/manual/index.php?tid=184&searchstring=format
31
     * @param string   $orientation orientation "P" = Portrait "L" = Landscape
32
     * @param array    $params
33
     * @param Template $template
34
     *
35
     * @throws MpdfException
36
     */
37
    public function __construct(
38
        $pageFormat = 'A4',
39
        $orientation = 'P',
40
        $params = [],
41
        $template = null
42
    ) {
43
        $this->template = $template;
44
        /* More info @ http://mpdf1.com/manual/index.php?tid=184&searchstring=Mpdf */
45
        if (!in_array($orientation, ['P', 'L'])) {
46
            $orientation = 'P';
47
        }
48
        //left, right, top, bottom, margin_header, margin footer
49
50
        $params['left'] = $params['left'] ?? 15;
51
        $params['right'] = $params['right'] ?? 15;
52
        $params['top'] = $params['top'] ?? 30;
53
        $params['bottom'] = $params['bottom'] ?? 30;
54
        $params['margin_footer'] = $params['margin_footer'] ?? 8;
55
56
        $this->params['filename'] = $params['filename'] ?? api_get_local_time();
57
        $this->params['pdf_title'] = $params['pdf_title'] ?? '';
58
        $this->params['course_info'] = $params['course_info'] ?? api_get_course_info();
59
        $this->params['session_info'] = $params['session_info'] ?? api_get_session_info(api_get_session_id());
60
        $this->params['course_code'] = $params['course_code'] ?? api_get_course_id();
61
        $this->params['add_signatures'] = $params['add_signatures'] ?? [];
62
        $this->params['show_real_course_teachers'] = $params['show_real_course_teachers'] ?? false;
63
        $this->params['student_info'] = $params['student_info'] ?? false;
64
        $this->params['show_grade_generated_date'] = $params['show_grade_generated_date'] ?? false;
65
        $this->params['show_teacher_as_myself'] = $params['show_teacher_as_myself'] ?? true;
66
        $localTime = api_get_local_time();
67
        $this->params['pdf_date'] = $params['pdf_date'] ?? api_format_date($localTime, DATE_TIME_FORMAT_LONG);
68
        $this->params['pdf_date_only'] = $params['pdf_date'] ?? api_format_date($localTime, DATE_FORMAT_LONG);
69
70
        $params = [
71
            'tempDir' => Container::getCacheDir(),
72
            'mode' => 'utf-8',
73
            'format' => $pageFormat,
74
            'orientation' => $orientation,
75
            'margin_left' => $params['left'],
76
            'margin_right' => $params['right'],
77
            'margin_top' => $params['top'],
78
            'margin_bottom' => $params['bottom'],
79
            'margin_header' => 8,
80
            'margin_footer' => 8,
81
        ];
82
83
        // Default value is 96 set in the mpdf library file config.php
84
        $value = (int) api_get_setting('platform.pdf_img_dpi');
85
        if ($value) {
86
            $params['img_dpi'] = $value;
87
        }
88
        $this->pdf = new Mpdf($params);
89
    }
90
91
    /**
92
     * Export the given HTML to PDF, using a global template.
93
     *
94
     * @uses \export/table_pdf.tpl
95
     *
96
     * @param string     $content
97
     * @param bool|false $saveToFile
98
     * @param bool|false $returnHtml
99
     * @param bool       $addDefaultCss (bootstrap/default/base.css)
100
     * @param array
101
     *
102
     * @return string
103
     */
104
    public function html_to_pdf_with_template(
105
        $content,
106
        $saveToFile = false,
107
        $returnHtml = false,
108
        $addDefaultCss = false,
109
        $extraRows = [],
110
        $outputMode = 'D',
111
        $fileToSave = null
112
    ) {
113
        if (empty($this->template)) {
114
            $tpl = new Template('', false, false, false, false, true, false);
115
        } else {
116
            $tpl = $this->template;
117
        }
118
119
        // Assignments
120
        $tpl->assign('pdf_content', $content);
121
122
        // Showing only the current teacher/admin instead the all teacher list name see BT#4080
123
        $teacher_list = null;
124
        if (isset($this->params['show_real_course_teachers']) &&
125
            $this->params['show_real_course_teachers']
126
        ) {
127
            if (isset($this->params['session_info']) &&
128
                !empty($this->params['session_info'])
129
            ) {
130
                $teacher_list = SessionManager::getCoachesByCourseSessionToString(
131
                    $this->params['session_info']['id'],
132
                    $this->params['course_info']['real_id']
133
                );
134
            } else {
135
                $teacher_list = CourseManager::getTeacherListFromCourseCodeToString(
136
                    $this->params['course_code']
137
                );
138
            }
139
        } else {
140
            $user_info = api_get_user_info();
141
            if ($this->params['show_teacher_as_myself']) {
142
                $teacher_list = $user_info['complete_name'];
143
            }
144
        }
145
146
        if (!empty($this->params['session_info']['title'])) {
147
            $this->params['session_info']['title'] = preg_replace('/[\x{2600}-\x{26FF}]/u', '', $this->params['session_info']['title']);
148
        }
149
150
        $tpl->assign('pdf_course', $this->params['course_code']);
151
        $tpl->assign('pdf_course_info', $this->params['course_info']);
152
        $tpl->assign('pdf_session_info', $this->params['session_info']);
153
        $tpl->assign('pdf_date', $this->params['pdf_date']);
154
        $tpl->assign('pdf_date_only', $this->params['pdf_date_only']);
155
        $tpl->assign('pdf_teachers', $teacher_list);
156
        $tpl->assign('pdf_title', $this->params['pdf_title']);
157
        $tpl->assign('pdf_student_info', $this->params['student_info']);
158
        $tpl->assign('show_grade_generated_date', $this->params['show_grade_generated_date']);
159
        $tpl->assign('add_signatures', $this->params['add_signatures']);
160
        $tpl->assign('extra_rows', $extraRows);
161
162
        // Getting template
163
        $tableTemplate = $tpl->get_template('export/table_pdf.tpl');
164
        $html = $tpl->fetch($tableTemplate);
165
        $html = api_utf8_encode($html);
166
        $html = $this->replaceIconsWithImages($html);
167
168
        if ($returnHtml) {
169
            return $html;
170
        }
171
172
        $css = Container::getThemeHelper()->getAssetContents('print.css');
173
174
        self::content_to_pdf(
0 ignored issues
show
Bug Best Practice introduced by
The method PDF::content_to_pdf() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
        self::/** @scrutinizer ignore-call */ 
175
              content_to_pdf(
Loading history...
175
            $html,
176
            $css,
177
            $this->params['filename'],
178
            $this->params['course_code'],
179
            $outputMode,
180
            $saveToFile,
181
            $fileToSave,
182
            $returnHtml,
183
            $addDefaultCss
184
        );
185
    }
186
187
    /**
188
     * Converts HTML files to PDF.
189
     *
190
     * @param mixed  $htmlFileArray  could be an html file path or an array
191
     *                               with paths example:
192
     *                               /var/www/myfile.html or array('/myfile.html','myotherfile.html') or
193
     *                               even an indexed array with both 'title' and 'path' indexes
194
     *                               for each element like
195
     *                               array(
196
     *                               0 => array('title'=>'Hello','path'=>'file.html'),
197
     *                               1 => array('title'=>'Bye','path'=>'file2.html')
198
     *                               );
199
     * @param string $pdfName        pdf name
200
     * @param string $courseCode     (if you are using html that are located
201
     *                               in the document tool you must provide this)
202
     * @param bool   $complete_style show header and footer if true
203
     * @param bool   $addStyle
204
     * @param string $mainTitle
205
     *
206
     * @return false|null
207
     */
208
    public function html_to_pdf(
209
        $htmlFileArray,
210
        $pdfName = '',
211
        $courseCode = null,
212
        $printTitle = false,
213
        $complete_style = true,
214
        $addStyle = true,
215
        $mainTitle = ''
216
    ) {
217
        if (empty($htmlFileArray)) {
218
            return false;
219
        }
220
221
        if (!is_array($htmlFileArray)) {
222
            if (!file_exists($htmlFileArray)) {
223
                return false;
224
            }
225
            // Converting the string into an array
226
            $htmlFileArray = [$htmlFileArray];
227
        }
228
229
        $courseInfo = api_get_course_info();
230
        if (!empty($courseCode)) {
231
            $courseInfo = api_get_course_info($courseCode);
232
        }
233
234
        // Clean styles and javascript document
235
        $clean_search = [
236
            '@<script[^>]*?>.*?</script>@si',
237
            '@<style[^>]*?>.*?</style>@si',
238
        ];
239
240
        // Formatting the pdf
241
        self::format_pdf($courseInfo, $complete_style);
0 ignored issues
show
Bug Best Practice introduced by
The method PDF::format_pdf() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

241
        self::/** @scrutinizer ignore-call */ 
242
              format_pdf($courseInfo, $complete_style);
Loading history...
242
243
        $counter = 1;
244
        foreach ($htmlFileArray as $file) {
245
            $pageBreak = ($counter === count($htmlFileArray)) ? '' : '<pagebreak>';
246
            $htmlTitle = '';
247
            $filePath = null;
248
            $content = null;
249
250
            if (is_array($file)) {
251
                $htmlTitle = $file['title'] ?? '';
252
                $content = $file['content'] ?? null;
253
                $filePath = $file['path'] ?? null;
254
            } else {
255
                $filePath = $file;
256
                $htmlTitle = basename($file);
257
            }
258
259
            if ($counter === 1 && !empty($mainTitle)) {
260
                $this->pdf->WriteHTML('<html><body><h2 style="text-align: center">'.$mainTitle.'</h2></body></html>');
261
            }
262
263
            // New support for direct HTML content
264
            if (!empty($content)) {
265
                if ($printTitle && !empty($htmlTitle)) {
266
                    $this->pdf->WriteHTML('<html><body><h3>'.$htmlTitle.'</h3></body></html>', 2);
267
                }
268
                $this->pdf->WriteHTML($content.$pageBreak, 2);
269
                $counter++;
270
                continue;
271
            }
272
273
            // Original logic for physical files
274
            if (empty($filePath)) {
275
                if ($printTitle && !empty($htmlTitle)) {
276
                    $this->pdf->WriteHTML('<html><body><h3>'.$htmlTitle.'</h3></body></html>'.$pageBreak);
277
                }
278
                $counter++;
279
                continue;
280
            }
281
282
            if (!file_exists($filePath)) {
283
                $counter++;
284
                continue;
285
            }
286
287
            if ($addStyle) {
288
                $css_file = api_get_path(SYS_CSS_PATH).'/print.css';
289
                $css = file_exists($css_file) ? @file_get_contents($css_file) : '';
290
                $this->pdf->WriteHTML($css, 1);
291
            }
292
293
            if ($printTitle && !empty($htmlTitle)) {
294
                $this->pdf->WriteHTML('<html><body><h3>'.$htmlTitle.'</h3></body></html>', 2);
295
            }
296
297
            $file_info = pathinfo($filePath);
298
            $extension = $file_info['extension'];
299
300
            if (in_array($extension, ['html', 'htm'])) {
301
                $dirName = $file_info['dirname'];
302
                $filename = str_replace('_', ' ', $file_info['basename']);
303
                $filename = basename($filename, '.'.$extension);
304
305
                $webPath = api_get_path(WEB_PATH);
306
307
                $documentHtml = @file_get_contents($filePath);
308
                $documentHtml = preg_replace($clean_search, '', $documentHtml);
309
310
                $crawler = new Crawler($documentHtml);
311
                $crawler
312
                    ->filter('link[rel="stylesheet"]')
313
                    ->each(function (Crawler $node) use ($webPath) {
314
                        $linkUrl = $node->link()->getUri();
315
                        if (!str_starts_with($linkUrl, $webPath)) {
316
                            $node->getNode(0)->parentNode->removeChild($node->getNode(0));
317
                        }
318
                    })
319
                ;
320
                $documentHtml = $crawler->outerHtml();
321
322
                //absolute path for frames.css //TODO: necessary?
323
                $absolute_css_path = api_get_path(WEB_CODE_PATH).'css/'.api_get_setting('stylesheets.stylesheets').'/frames.css';
0 ignored issues
show
Bug introduced by
Are you sure api_get_setting('stylesheets.stylesheets') of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

323
                $absolute_css_path = api_get_path(WEB_CODE_PATH).'css/'./** @scrutinizer ignore-type */ api_get_setting('stylesheets.stylesheets').'/frames.css';
Loading history...
324
                $documentHtml = str_replace('href="./css/frames.css"', $absolute_css_path, $documentHtml);
325
                if (!empty($courseInfo['path'])) {
326
                    $documentHtml = str_replace('../', '', $documentHtml);
327
328
                    // Fix app/upload links convert web to system paths
329
                    $documentHtml = str_replace(
330
                        api_get_path(WEB_UPLOAD_PATH),
0 ignored issues
show
Bug introduced by
The constant WEB_UPLOAD_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
331
                        api_get_path(SYS_UPLOAD_PATH),
0 ignored issues
show
Bug introduced by
The constant SYS_UPLOAD_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
332
                        $documentHtml
333
                    );
334
                }
335
336
                api_set_encoding_html($documentHtml, 'UTF-8');
337
338
                if (!empty($documentHtml)) {
339
                    $this->pdf->WriteHTML($documentHtml.$pageBreak, 2);
340
                }
341
            } elseif (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
342
                $image = Display::img($filePath);
343
                $this->pdf->WriteHTML('<html><body>'.$image.'</body></html>'.$pageBreak, 2);
344
            }
345
346
            $counter++;
347
        }
348
349
        $outputFile = 'pdf_'.api_get_local_time().'.pdf';
350
        if (!empty($pdfName)) {
351
            $outputFile = $pdfName.'.pdf';
352
        }
353
354
        $outputFile = api_replace_dangerous_char($outputFile);
355
356
        // F to save the pdf in a file
357
        $this->pdf->Output($outputFile, Destination::DOWNLOAD);
358
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
359
    }
360
361
    /**
362
     * Converts an html string to PDF.
363
     *
364
     * @param string $document_html  valid html
365
     * @param string $css            CSS content of a CSS file
366
     * @param string $pdf_name       pdf name
367
     * @param string $courseCode     course code
368
     *                               (if you are using html that are located in the document tool you must provide this)
369
     * @param string $outputMode     the MPDF output mode can be:
370
     * @param bool   $saveInFile
371
     * @param string $fileToSave
372
     * @param bool   $returnHtml
373
     * @param bool   $addDefaultCss
374
     * @param bool   $completeHeader
375
     *
376
     * 'I' (print on standard output),
377
     * 'D' (download file) (this is the default value),
378
     * 'F' (save to local file) or
379
     * 'S' (return as a string)
380
     *
381
     * @return string Web path
382
     */
383
    public function content_to_pdf(
384
        $document_html,
385
        ?string $css = null,
386
        $pdf_name = '',
387
        $courseCode = null,
388
        $outputMode = 'D',
389
        $saveInFile = false,
390
        $fileToSave = null,
391
        $returnHtml = false,
392
        $addDefaultCss = false,
393
        $completeHeader = true,
394
        $disableFooter = false,
395
        $disablePagination = false
396
    ) {
397
        $urlAppend = '';
398
399
        if (empty($document_html)) {
400
            return false;
401
        }
402
403
        // clean styles and javascript document
404
        $clean_search = [
405
            '@<script[^>]*?>.*?</script>@si',
406
            '@<style[^>]*?>.*?</style>@siU',
407
        ];
408
409
        // Formatting the pdf
410
        $courseInfo = api_get_course_info($courseCode);
411
        $this->format_pdf($courseInfo, $completeHeader, $disablePagination);
412
        $document_html = preg_replace($clean_search, '', $document_html);
413
414
        $document_html = str_replace('../../', '', $document_html);
415
        $document_html = str_replace('../', '', $document_html);
416
        $document_html = str_replace(
417
            (empty($urlAppend) ? '' : $urlAppend.'/').'courses/'.$courseCode.'/document/',
418
            '',
419
            $document_html
420
        );
421
422
        $basicStyles = [];
423
424
        $doc = new DOMDocument();
425
        @$doc->loadHTML('<?xml encoding="UTF-8">' . $document_html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for loadHTML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

425
        /** @scrutinizer ignore-unhandled */ @$doc->loadHTML('<?xml encoding="UTF-8">' . $document_html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
426
427
        $linksToRemove = [];
428
429
        foreach ($doc->getElementsByTagName('link') as $link) {
430
            if ($link->getAttribute('href') === './css/frames.css') {
431
                $linksToRemove[] = $link;
432
            }
433
        }
434
435
        foreach ($linksToRemove as $link) {
436
            $link->parentNode->removeChild($link);
437
        }
438
439
        $basicStyles[] = Container::getThemeHelper()->getAssetContents('frames.css');
440
441
        $document_html = $doc->saveHTML();
442
443
        if (!empty($courseInfo['path'])) {
444
            //Fixing only images @todo do the same thing with other elements
445
            $elements = $doc->getElementsByTagName('img');
446
            $protocol = api_get_protocol();
447
            $replaced = [];
448
            if (!empty($elements)) {
449
                foreach ($elements as $item) {
450
                    $old_src = $item->getAttribute('src');
451
452
                    if (in_array($old_src, $replaced)) {
453
                        continue;
454
                    }
455
                }
456
            }
457
        }
458
459
        // Use sys path to correct export images
460
        $document_html = str_replace(
461
            api_get_path(WEB_CODE_PATH).'img/',
462
            api_get_path(SYS_CODE_PATH).'img/',
463
            $document_html
464
        );
465
        $document_html = str_replace(api_get_path(WEB_ARCHIVE_PATH), api_get_path(SYS_ARCHIVE_PATH), $document_html);
466
        $document_html = str_replace('<?xml encoding="UTF-8">', '', $document_html);
467
        // The library mPDF expects UTF-8 encoded input data.
468
        api_set_encoding_html($document_html, 'UTF-8');
469
        // At the moment the title is retrieved from the html document itself.
470
        if ($returnHtml) {
471
            return "<style>$css</style>".$document_html;
472
        }
473
474
        $css .= "
475
            table {
476
                width: 100%;
477
                border-collapse: collapse;
478
            }
479
            th, td {
480
                font-size: 12px;
481
                text-align: left;
482
                padding: 2px;
483
                border: 1px solid #ccc;
484
            }
485
        ";
486
487
        if (!empty($css)) {
488
            $this->pdf->WriteHTML($css, HTMLParserMode::HEADER_CSS);
489
        }
490
491
        if ($addDefaultCss) {
492
            $basicStyles[] = Container::getThemeHelper()->getAssetContents('default.css');
493
        }
494
495
        foreach ($basicStyles as $cssContent) {
496
            if ($cssContent) {
497
                @$this->pdf->WriteHTML($cssContent, HTMLParserMode::HEADER_CSS);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for WriteHTML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

497
                /** @scrutinizer ignore-unhandled */ @$this->pdf->WriteHTML($cssContent, HTMLParserMode::HEADER_CSS);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
498
            }
499
        }
500
501
        @$this->pdf->WriteHTML($document_html);
502
        if ($disableFooter) {
503
            $this->pdf->SetHTMLFooter('');
504
        }
505
506
        if (empty($pdf_name)) {
507
            $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
508
        } else {
509
            $pdf_name = api_replace_dangerous_char($pdf_name);
510
            $output_file = $pdf_name.'.pdf';
511
        }
512
513
        if ('F' === $outputMode) {
514
            $output_file = api_get_path(SYS_ARCHIVE_PATH).$output_file;
515
        }
516
517
        if ($saveInFile) {
518
            $fileToSave = !empty($fileToSave) ? $fileToSave : api_get_path(SYS_ARCHIVE_PATH).uniqid();
519
520
            $this->pdf->Output(
521
                $fileToSave,
522
                $outputMode
523
            ); // F to save the pdf in a file
524
        } else {
525
            $this->pdf->Output(
526
                $output_file,
527
                $outputMode
528
            );
529
        }
530
531
        if ('F' !== $outputMode) {
532
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
533
        }
534
535
        return $output_file;
536
    }
537
538
    /**
539
     * Gets the watermark from the platform or a course.
540
     *
541
     * @param   string  course code (optional)
542
     * @param   mixed   web path of the watermark image, false if there is nothing to return
543
     *
544
     * @return string
545
     */
546
    public static function get_watermark($courseCode = null)
547
    {
548
        $web_path = false;
549
        $urlId = api_get_current_access_url_id();
550
        if (!empty($courseCode) && 'true' == api_get_setting('document.pdf_export_watermark_by_course')) {
551
            $course_info = api_get_course_info($courseCode);
552
            // course path
553
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
554
            if (file_exists($store_path)) {
555
                $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
556
            }
557
        } else {
558
            // course path
559
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
560
            if (file_exists($store_path)) {
561
                $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
562
            }
563
        }
564
565
        return $web_path;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $web_path could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
566
    }
567
568
    /**
569
     * Deletes the watermark from the Platform or Course.
570
     *
571
     * @param string $courseCode course code (optional)
572
     * @param   mixed   web path of the watermark image, false if there is nothing to return
573
     *
574
     * @return bool
575
     */
576
    public static function delete_watermark($courseCode = null)
577
    {
578
        $urlId = api_get_current_access_url_id();
579
        if (!empty($courseCode) && 'true' === api_get_setting('document.pdf_export_watermark_by_course')) {
580
            $course_info = api_get_course_info($courseCode);
581
            // course path
582
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
583
        } else {
584
            // course path
585
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
586
        }
587
        if (file_exists($store_path)) {
588
            unlink($store_path);
589
590
            return true;
591
        }
592
593
        return false;
594
    }
595
596
    /**
597
     * Uploads the pdf watermark in the main/default_course_document directory or in the course directory.
598
     *
599
     * @param string $filename    filename
600
     * @param string $source_file path of the file
601
     * @param string $courseCode
602
     *
603
     * @return mixed web path of the file if sucess, false otherwise
604
     */
605
    public static function upload_watermark($filename, $source_file, $courseCode = null)
606
    {
607
        $urlId = api_get_current_access_url_id();
608
        if (!empty($courseCode) && 'true' === api_get_setting('document.pdf_export_watermark_by_course')) {
609
            $course_info = api_get_course_info($courseCode);
610
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path']; // course path
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
611
            $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/pdf_watermark.png';
612
        } else {
613
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images'; // course path
614
            $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
615
        }
616
        $course_image = $store_path.'/'.$urlId.'_pdf_watermark.png';
617
618
        if (file_exists($course_image)) {
619
            @unlink($course_image);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

619
            /** @scrutinizer ignore-unhandled */ @unlink($course_image);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
620
        }
621
        $my_image = new Image($source_file);
622
        $result = $my_image->send_image($course_image, -1, 'png');
623
        if ($result) {
624
            $result = $web_path;
625
        }
626
627
        return $result;
628
    }
629
630
    /**
631
     * Returns the default header.
632
     */
633
    public function get_header($courseCode = null)
634
    {
635
        /*$header = api_get_setting('pdf_export_watermark_text');
636
    	if (!empty($courseCode) && api_get_setting('pdf_export_watermark_by_course') == 'true') {
637
            $header = api_get_course_setting('pdf_export_watermark_text');
638
        }
639
        return $header;*/
640
    }
641
642
    /**
643
     * Sets the PDF footer.
644
     */
645
    public function set_footer()
646
    {
647
        $this->pdf->defaultfooterfontsize = 12; // in pts
648
        $this->pdf->defaultfooterfontstyle = 'B'; // blank, B, I, or BI
649
        $this->pdf->defaultfooterline = 1; // 1 to include line below header/above footer
650
651
        $view = new Template('', false, false, false, true, false, false);
652
        $template = $view->get_template('export/pdf_footer.tpl');
653
        $footerHTML = $view->fetch($template);
654
655
        $this->pdf->SetHTMLFooter($footerHTML, 'E'); //Even pages
656
        $this->pdf->SetHTMLFooter($footerHTML, 'O'); //Odd pages
657
    }
658
659
    public function setCertificateFooter()
660
    {
661
        $this->pdf->defaultfooterfontsize = 12; // in pts
662
        $this->pdf->defaultfooterfontstyle = 'B'; // blank, B, I, or BI
663
        $this->pdf->defaultfooterline = 1; // 1 to include line below header/above footer
664
665
        $view = new Template('', false, false, false, true, false, false);
666
        $template = $view->get_template('export/pdf_certificate_footer.tpl');
667
        $footerHTML = $view->fetch($template);
668
669
        $this->pdf->SetHTMLFooter($footerHTML, 'E'); //Even pages
670
        $this->pdf->SetHTMLFooter($footerHTML, 'O'); //Odd pages
671
    }
672
673
    /**
674
     * Sets the PDF header.
675
     *
676
     * @param array $courseInfo
677
     */
678
    public function set_header($courseInfo)
679
    {
680
        $this->pdf->defaultheaderfontsize = 10; // in pts
681
        $this->pdf->defaultheaderfontstyle = 'BI'; // blank, B, I, or BI
682
        $this->pdf->defaultheaderline = 1; // 1 to include line below header/above footer
683
684
        $userId = api_get_user_id();
685
        if (!empty($courseInfo['code'])) {
686
            $teacher_list = CourseManager::get_teacher_list_from_course_code($courseInfo['code']);
687
688
            $teachers = '';
689
            if (!empty($teacher_list)) {
690
                foreach ($teacher_list as $teacher) {
691
                    if ($teacher['user_id'] != $userId) {
692
                        continue;
693
                    }
694
695
                    // Do not show the teacher list see BT#4080 only the current teacher name
696
                    $teachers = api_get_person_name($teacher['firstname'], $teacher['lastname']);
697
                }
698
            }
699
700
            $logoSrc = Container::getThemeHelper()->getAssetBase64Encoded('images/header-logo.png');
701
            // Use custom logo image.
702
            $pdfLogo = api_get_setting('display.pdf_logo_header');
703
            if ('true' === $pdfLogo) {
704
                $logoSrc = Container::getThemeHelper()->getAssetBase64Encoded('images/pdf_logo_header.png') ?: $logoSrc;
705
            }
706
707
            $organization = "<img src='$logoSrc'>";
708
709
            $view = new Template('', false, false, false, true, false, false);
710
            $view->assign('teacher_name', $teachers);
711
            $view->assign('organization', $organization);
712
            $template = $view->get_template('export/pdf_header.tpl');
713
            $headerHTML = $view->fetch($template);
714
715
            $this->pdf->SetHTMLHeader($headerHTML, 'E');
716
            $this->pdf->SetHTMLHeader($headerHTML, 'O');
717
        }
718
    }
719
720
    /**
721
     * @param string $header html content
722
     */
723
    public function set_custom_header($header)
724
    {
725
        $this->custom_header = $header;
726
    }
727
728
    /**
729
     * @param array $footer html content
730
     */
731
    public function set_custom_footer($footer)
732
    {
733
        $this->custom_footer = $footer;
734
    }
735
736
    /**
737
     * Pre-formats a PDF to the right size and, if not stated otherwise, with
738
     * header, footer and watermark (if any).
739
     *
740
     * @param array $courseInfo General course information (to fill headers)
741
     * @param bool  $complete   Whether we want headers, footers and watermark or not
742
     */
743
    public function format_pdf($courseInfo, $complete = true, $disablePagination = false)
744
    {
745
        $courseCode = null;
746
        if (!empty($courseInfo)) {
747
            $courseCode = $courseInfo['code'];
748
        }
749
750
        $this->pdf->directionality = api_get_text_direction();
751
        $this->pdf->onlyCoreFonts = true;
752
        $this->pdf->mirrorMargins = 1;
753
754
        // Add decoration only if not stated otherwise
755
        if ($complete) {
756
            // Adding watermark
757
            if ('true' == api_get_setting('document.pdf_export_watermark_enable')) {
758
                $watermark_file = self::get_watermark($courseCode);
759
                if ($watermark_file) {
760
                    $this->pdf->SetWatermarkImage($watermark_file);
761
                    $this->pdf->showWatermarkImage = true;
762
                } else {
763
                    $watermark_file = self::get_watermark(null);
764
                    if ($watermark_file) {
765
                        $this->pdf->SetWatermarkImage($watermark_file);
766
                        $this->pdf->showWatermarkImage = true;
767
                    }
768
                }
769
                $watermark_text = api_get_setting('document.pdf_export_watermark_text');
770
                if ($courseCode && 'true' === api_get_setting('document.pdf_export_watermark_by_course')) {
771
                    $courseWaterMark = api_get_course_setting('document.pdf_export_watermark_text');
772
                    if (!empty($courseWaterMark) && -1 != $courseWaterMark) {
773
                        $watermark_text = $courseWaterMark;
774
                    }
775
                }
776
                if (!empty($watermark_text)) {
777
                    $this->pdf->SetWatermarkText(
778
                        UtfString::strcode2utf($watermark_text),
779
                        0.1
780
                    );
781
                    $this->pdf->showWatermarkText = true;
782
                }
783
            }
784
785
            if ($disablePagination) {
786
                $this->pdf->SetHTMLHeader('');
787
                $this->pdf->SetHTMLFooter('');
788
            } else {
789
                if (empty($this->custom_header)) {
790
                    $this->set_header($courseInfo);
791
                } else {
792
                    $this->pdf->SetHTMLHeader($this->custom_header, 'E');
793
                    $this->pdf->SetHTMLHeader($this->custom_header, 'O');
794
                }
795
796
                if (empty($this->custom_footer)) {
797
                    self::set_footer();
0 ignored issues
show
Bug Best Practice introduced by
The method PDF::set_footer() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

797
                    self::/** @scrutinizer ignore-call */ 
798
                          set_footer();
Loading history...
798
                } else {
799
                    $this->pdf->SetHTMLFooter($this->custom_footer);
800
                }
801
            }
802
        }
803
    }
804
805
    /**
806
     * Generate a PDF file from $html in SYS_APP_PATH.
807
     *
808
     * @param string $html     PDF content
809
     * @param string $fileName File name
810
     * @param string $dest     Optional. Directory to move file
811
     *
812
     * @return string The PDF path
813
     */
814
    public function exportFromHtmlToFile($html, $fileName)
815
    {
816
        $this->template = $this->template ?: new Template('', false, false, false, false, false, false);
817
818
        $css = Container::getThemeHelper()->getAssetContents('print.css');
819
820
        $pdfPath = self::content_to_pdf(
0 ignored issues
show
Bug Best Practice introduced by
The method PDF::content_to_pdf() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

820
        /** @scrutinizer ignore-call */ 
821
        $pdfPath = self::content_to_pdf(
Loading history...
821
            $html,
822
            $css,
823
            $fileName,
824
            $this->params['course_code'],
825
            'F'
826
        );
827
828
        return $pdfPath;
829
    }
830
831
    /**
832
     * Create a PDF and save it into the documents area.
833
     *
834
     * @param string $htmlContent HTML Content
835
     * @param string $fileName    The file name
836
     * @param int    $courseId    The course ID
837
     * @param int    $sessionId   Optional. The session ID
838
     */
839
    public function exportFromHtmlToDocumentsArea(
840
        $htmlContent,
841
        $fileName,
842
        $courseId,
843
        $sessionId = 0
844
    ) {
845
        $userId = api_get_user_id();
846
        $courseInfo = api_get_course_info_by_id($courseId);
847
848
        $docPath = $this->exportFromHtmlToFile(
849
            $htmlContent,
850
            $fileName
851
        );
852
853
        DocumentManager::addDocument(
854
            $courseInfo,
855
            '',
856
            'file',
857
            filesize($docPath),
858
            $fileName,
859
            null,
860
            false,
861
            true,
862
            null,
863
            $sessionId,
864
            $userId,
865
            false,
866
            '',
867
            null,
868
            $docPath
869
        );
870
871
        Display::addFlash(Display::return_message(get_lang('Item added')));
872
    }
873
874
    /**
875
     * @param string $theme
876
     *
877
     * @throws MpdfException
878
     */
879
    public function setBackground($theme)
880
    {
881
        $themeName = empty($theme) ? api_get_visual_theme() : $theme;
882
        $themeDir = \Template::getThemeDir($themeName);
883
        $customLetterhead = $themeDir.'images/letterhead.png';
884
        $urlPathLetterhead = api_get_path(SYS_CSS_PATH).$customLetterhead;
885
886
        $urlWebLetterhead = '#FFFFFF';
887
        $fullPage = false;
888
        if (file_exists($urlPathLetterhead)) {
889
            $fullPage = true;
890
            $urlWebLetterhead = 'url('.api_get_path(WEB_CSS_PATH).$customLetterhead.')';
891
        }
892
893
        if ($fullPage) {
894
            $this->pdf->SetDisplayMode('fullpage');
895
            $this->pdf->SetDefaultBodyCSS('background', $urlWebLetterhead);
896
            $this->pdf->SetDefaultBodyCSS('background-image-resize', '6');
897
        }
898
    }
899
900
    /**
901
     * Fix images source paths to allow export to pdf.
902
     *
903
     * @param string $documentHtml
904
     * @param string $dirName
905
     *
906
     * @return string
907
     */
908
    private static function fixImagesPaths($documentHtml, array $courseInfo = null, $dirName = '')
0 ignored issues
show
Unused Code introduced by
The method fixImagesPaths() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
909
    {
910
        $html = new HTML5();
911
        $doc = $html->loadHTML($documentHtml);
912
913
        $elements = $doc->getElementsByTagName('img');
914
915
        if (empty($elements)) {
916
            return $doc->saveHTML();
917
        }
918
919
        $protocol = api_get_protocol();
920
        $sysCodePath = api_get_path(SYS_CODE_PATH);
921
        $sysCoursePath = api_get_path(SYS_PATH).'../app/courses/';
922
        $sysUploadPath = api_get_path(SYS_PATH).'../app/upload/';
923
924
        $documentPath = $courseInfo ? $sysCoursePath.$courseInfo['path'].'/document/' : '';
925
926
        $notFoundImagePath = Display::return_icon(
927
            'closed-circle.png',
928
            get_lang('The file was not found'),
929
            [],
930
            ICON_SIZE_TINY,
931
            false,
932
            true
933
        );
934
935
        /** @var \DOMElement $element */
936
        foreach ($elements as $element) {
937
            $src = $element->getAttribute('src');
938
            $src = trim($src);
939
940
            if (api_filename_has_blacklisted_stream_wrapper($src)) {
941
                $element->setAttribute('src', $notFoundImagePath);
942
                continue;
943
            }
944
945
            if (false !== strpos($src, $protocol)) {
946
                continue;
947
            }
948
949
            // It's a reference to a file in the system, do not change it
950
            if (file_exists($src)) {
951
                continue;
952
            }
953
954
            if (0 === strpos($src, '/main/default_course_document')) {
955
                $element->setAttribute(
956
                    'src',
957
                    str_replace('/main/default_course_document', $sysCodePath.'default_course_document', $src)
958
                );
959
                continue;
960
            }
961
962
            if (0 === strpos($src, '/main/img')) {
963
                $element->setAttribute(
964
                    'src',
965
                    str_replace('/main/img/', $sysCodePath.'img/', $src)
966
                );
967
                continue;
968
            }
969
970
            if (0 === strpos($src, '/app/upload/')) {
971
                $element->setAttribute(
972
                    'src',
973
                    str_replace('/app/upload/', $sysUploadPath, $src)
974
                );
975
                continue;
976
            }
977
978
            if (empty($courseInfo)) {
979
                continue;
980
            }
981
982
            if ('/' != api_get_path(REL_PATH)) {
983
                $oldSrcFixed = str_replace(
984
                    api_get_path(REL_PATH).'courses/'.$courseInfo['path'].'/document/',
985
                    '',
986
                    $src
987
                );
988
989
                // Try with the dirname if exists
990
                if ($oldSrcFixed == $src) {
991
                    if (file_exists($dirName.'/'.$src)) {
992
                        $documentPath = '';
993
                        $oldSrcFixed = $dirName.'/'.$src;
994
                    }
995
                }
996
            } else {
997
                if (false !== strpos($src, 'courses/'.$courseInfo['path'].'/document/')) {
998
                    $oldSrcFixed = str_replace('courses/'.$courseInfo['path'].'/document/', '', $src);
999
                } else {
1000
                    // Try with the dirname if exists
1001
                    if (file_exists($dirName.'/'.$src)) {
1002
                        $documentPath = '';
1003
                        $oldSrcFixed = $dirName.'/'.$src;
1004
                    } else {
1005
                        $documentPath = '';
1006
                        $oldSrcFixed = $src;
1007
                    }
1008
                }
1009
            }
1010
1011
            $element->setAttribute('src', $documentPath.$oldSrcFixed);
1012
        }
1013
1014
        return $doc->saveHTML();
1015
    }
1016
1017
    /**
1018
     * Replaces icon tags in the HTML content with corresponding image paths.
1019
     */
1020
    public function replaceIconsWithImages(string $content): string
1021
    {
1022
        // Load icon images
1023
        $checkboxOn = Display::return_icon('checkbox_on.png', null, null, ICON_SIZE_TINY);
1024
        $checkboxOff = Display::return_icon('checkbox_off.png', null, null, ICON_SIZE_TINY);
1025
        $radioOn = Display::return_icon('radio_on.png', null, null, ICON_SIZE_TINY);
1026
        $radioOff = Display::return_icon('radio_off.png', null, null, ICON_SIZE_TINY);
1027
1028
        // Define replacements
1029
        $replacements = [
1030
            '/<i[^>]*class="[^"]*checkbox-marked-outline[^"]*"[^>]*><\/i>/i' => $checkboxOn,
1031
            '/<i[^>]*class="[^"]*checkbox-blank-outline[^"]*"[^>]*><\/i>/i' => $checkboxOff,
1032
            '/<i[^>]*class="[^"]*radiobox-marked[^"]*"[^>]*><\/i>/i' => $radioOn,
1033
            '/<i[^>]*class="[^"]*radiobox-blank[^"]*"[^>]*><\/i>/i' => $radioOff,
1034
        ];
1035
1036
        // Perform replacements
1037
        foreach ($replacements as $pattern => $replacement) {
1038
            $content = preg_replace($pattern, $replacement, $content);
1039
        }
1040
1041
        return $content;
1042
    }
1043
}
1044