Passed
Pull Request — master (#6079)
by
unknown
07:52
created

PDF::setBackground()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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

242
        self::/** @scrutinizer ignore-call */ 
243
              format_pdf($courseInfo, $complete_style);
Loading history...
243
244
        $counter = 1;
245
        foreach ($htmlFileArray as $file) {
246
            //Add a page break per file
247
            $pageBreak = '<pagebreak>';
248
            if ($counter == count($htmlFileArray)) {
249
                $pageBreak = '';
250
            }
251
252
            //if the array provided contained subarrays with 'title' entry,
253
            // then print the title in the PDF
254
            if (is_array($file) && isset($file['title'])) {
255
                $htmlTitle = $file['title'];
256
                $file = $file['path'];
257
            } else {
258
                //we suppose we've only been sent a file path
259
                $htmlTitle = basename($file);
260
            }
261
262
            $counter++;
263
264
            if (empty($file) && !empty($htmlTitle)) {
265
                // this is a chapter, print title & skip the rest
266
                if (2 === $counter && !empty($mainTitle)) {
267
                    $this->pdf->WriteHTML(
268
                        '<html><body><h2 style="text-align: center">'.$mainTitle.'</h2></body></html>'
269
                    );
270
                }
271
                if ($printTitle) {
272
                    $this->pdf->WriteHTML(
273
                        '<html><body><h3>'.$htmlTitle.'</h3></body></html>'.$pageBreak
274
                    );
275
                }
276
                continue;
277
            } else {
278
                if (2 === $counter && !empty($mainTitle)) {
279
                    $this->pdf->WriteHTML(
280
                        '<html><body><h2 style="text-align: center">'.$mainTitle.'</h2></body></html>'
281
                    );
282
                }
283
            }
284
285
            if (!file_exists($file)) {
286
                continue;
287
            }
288
289
            if ($addStyle) {
290
                $css_file = api_get_path(SYS_CSS_PATH).'/print.css';
291
                $css = file_exists($css_file) ? @file_get_contents($css_file) : '';
292
                $this->pdf->WriteHTML($css, 1);
293
            }
294
295
            //it's not a chapter but the file exists, print its title
296
            if ($printTitle) {
297
                $this->pdf->WriteHTML('<html><body><h3>'.$htmlTitle.'</h3></body></html>', 2);
298
            }
299
300
            $file_info = pathinfo($file);
301
            $extension = $file_info['extension'];
302
303
            if (in_array($extension, ['html', 'htm'])) {
304
                $dirName = $file_info['dirname'];
305
                $filename = $file_info['basename'];
306
                $filename = str_replace('_', ' ', $filename);
307
308
                if ('html' === $extension) {
309
                    $filename = basename($filename, '.html');
310
                } elseif ('htm' === $extension) {
311
                    $filename = basename($filename, '.htm');
312
                }
313
314
                $webPath = api_get_path(WEB_PATH);
315
316
                $documentHtml = @file_get_contents($file);
317
                $documentHtml = preg_replace($clean_search, '', $documentHtml);
318
319
                $crawler = new Crawler($documentHtml);
320
                $crawler
321
                    ->filter('link[rel="stylesheet"]')
322
                    ->each(function (Crawler $node) use ($webPath) {
323
                        $linkUrl = $node->link()->getUri();
324
                        if (!str_starts_with($linkUrl, $webPath)) {
325
                            $node->getNode(0)->parentNode->removeChild($node->getNode(0));
326
                        }
327
                    })
328
                ;
329
                $documentHtml = $crawler->outerHtml();
330
331
                //absolute path for frames.css //TODO: necessary?
332
                $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

332
                $absolute_css_path = api_get_path(WEB_CODE_PATH).'css/'./** @scrutinizer ignore-type */ api_get_setting('stylesheets.stylesheets').'/frames.css';
Loading history...
333
                $documentHtml = str_replace('href="./css/frames.css"', $absolute_css_path, $documentHtml);
334
                if (!empty($courseInfo['path'])) {
335
                    $documentHtml = str_replace('../', '', $documentHtml);
336
337
                    // Fix app/upload links convert web to system paths
338
                    $documentHtml = str_replace(
339
                        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...
340
                        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...
341
                        $documentHtml
342
                    );
343
                }
344
345
                //$documentHtml = self::fixImagesPaths($documentHtml, $courseInfo, $dirName);
346
                // The library mPDF expects UTF-8 encoded input data.
347
                api_set_encoding_html($documentHtml, 'UTF-8');
348
                // TODO: Maybe it is better idea the title to be passed through
349
                $title = api_get_title_html($documentHtml, 'UTF-8', 'UTF-8');
350
                // $_GET[] too, as it is done with file name.
351
                // At the moment the title is retrieved from the html document itself.
352
                if (empty($title)) {
353
                    $title = $filename; // Here file name is expected to contain ASCII symbols only.
354
                }
355
356
                if (!empty($documentHtml)) {
357
                    $this->pdf->WriteHTML($documentHtml.$pageBreak, 2);
358
                }
359
            } elseif (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
360
                // Images
361
                $image = Display::img($file);
362
                $this->pdf->WriteHTML('<html><body>'.$image.'</body></html>'.$pageBreak, 2);
363
            }
364
        }
365
366
        $outputFile = 'pdf_'.api_get_local_time().'.pdf';
367
        if (!empty($pdfName)) {
368
            $outputFile = $pdfName.'.pdf';
369
        }
370
371
        $outputFile = api_replace_dangerous_char($outputFile);
372
373
        // F to save the pdf in a file
374
        $this->pdf->Output($outputFile, Destination::DOWNLOAD);
375
        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...
376
    }
377
378
    /**
379
     * Converts an html string to PDF.
380
     *
381
     * @param string $document_html  valid html
382
     * @param string $css            CSS content of a CSS file
383
     * @param string $pdf_name       pdf name
384
     * @param string $courseCode     course code
385
     *                               (if you are using html that are located in the document tool you must provide this)
386
     * @param string $outputMode     the MPDF output mode can be:
387
     * @param bool   $saveInFile
388
     * @param string $fileToSave
389
     * @param bool   $returnHtml
390
     * @param bool   $addDefaultCss
391
     * @param bool   $completeHeader
392
     *
393
     * 'I' (print on standard output),
394
     * 'D' (download file) (this is the default value),
395
     * 'F' (save to local file) or
396
     * 'S' (return as a string)
397
     *
398
     * @return string Web path
399
     */
400
    public function content_to_pdf(
401
        $document_html,
402
        ?string $css = null,
403
        $pdf_name = '',
404
        $courseCode = null,
405
        $outputMode = 'D',
406
        $saveInFile = false,
407
        $fileToSave = null,
408
        $returnHtml = false,
409
        $addDefaultCss = false,
410
        $completeHeader = true,
411
        $disableFooter = false,
412
        $disablePagination = false
413
    ) {
414
        $urlAppend = '';
415
416
        if (empty($document_html)) {
417
            return false;
418
        }
419
420
        // clean styles and javascript document
421
        $clean_search = [
422
            '@<script[^>]*?>.*?</script>@si',
423
            '@<style[^>]*?>.*?</style>@siU',
424
        ];
425
426
        // Formatting the pdf
427
        $courseInfo = api_get_course_info($courseCode);
428
        $this->format_pdf($courseInfo, $completeHeader, $disablePagination);
429
        $document_html = preg_replace($clean_search, '', $document_html);
430
431
        $document_html = str_replace('../../', '', $document_html);
432
        $document_html = str_replace('../', '', $document_html);
433
        $document_html = str_replace(
434
            (empty($urlAppend) ? '' : $urlAppend.'/').'courses/'.$courseCode.'/document/',
435
            '',
436
            $document_html
437
        );
438
439
        $basicStyles = [];
440
441
        $doc = new DOMDocument();
442
        @$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

442
        /** @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...
443
444
        $linksToRemove = [];
445
446
        foreach ($doc->getElementsByTagName('link') as $link) {
447
            if ($link->getAttribute('href') === './css/frames.css') {
448
                $linksToRemove[] = $link;
449
            }
450
        }
451
452
        foreach ($linksToRemove as $link) {
453
            $link->parentNode->removeChild($link);
454
        }
455
456
        $basicStyles[] = Container::getThemeHelper()->getAssetContents('frames.css');
457
458
        $document_html = $doc->saveHTML();
459
460
        if (!empty($courseInfo['path'])) {
461
            //Fixing only images @todo do the same thing with other elements
462
            $elements = $doc->getElementsByTagName('img');
463
            $protocol = api_get_protocol();
464
            $replaced = [];
465
            if (!empty($elements)) {
466
                foreach ($elements as $item) {
467
                    $old_src = $item->getAttribute('src');
468
469
                    if (in_array($old_src, $replaced)) {
470
                        continue;
471
                    }
472
                }
473
            }
474
        }
475
476
        // Use sys path to correct export images
477
        $document_html = str_replace(
478
            api_get_path(WEB_CODE_PATH).'img/',
479
            api_get_path(SYS_CODE_PATH).'img/',
480
            $document_html
481
        );
482
        $document_html = str_replace(api_get_path(WEB_ARCHIVE_PATH), api_get_path(SYS_ARCHIVE_PATH), $document_html);
483
        $document_html = str_replace('<?xml encoding="UTF-8">', '', $document_html);
484
        // The library mPDF expects UTF-8 encoded input data.
485
        api_set_encoding_html($document_html, 'UTF-8');
486
        // At the moment the title is retrieved from the html document itself.
487
        if ($returnHtml) {
488
            return "<style>$css</style>".$document_html;
489
        }
490
491
        $css .= "
492
            table {
493
                width: 100%;
494
                border-collapse: collapse;
495
            }
496
            th, td {
497
                font-size: 12px;
498
                text-align: left;
499
                padding: 2px;
500
                border: 1px solid #ccc;
501
            }
502
        ";
503
504
        if (!empty($css)) {
505
            $this->pdf->WriteHTML($css, HTMLParserMode::HEADER_CSS);
506
        }
507
508
        if ($addDefaultCss) {
509
            $basicStyles[] = Container::getThemeHelper()->getAssetContents('default.css');
510
        }
511
512
        foreach ($basicStyles as $cssContent) {
513
            if ($cssContent) {
514
                @$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

514
                /** @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...
515
            }
516
        }
517
518
        @$this->pdf->WriteHTML($document_html);
519
        if ($disableFooter) {
520
            $this->pdf->SetHTMLFooter('');
521
        }
522
523
        if (empty($pdf_name)) {
524
            $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
525
        } else {
526
            $pdf_name = api_replace_dangerous_char($pdf_name);
527
            $output_file = $pdf_name.'.pdf';
528
        }
529
530
        if ('F' === $outputMode) {
531
            $output_file = api_get_path(SYS_ARCHIVE_PATH).$output_file;
532
        }
533
534
        if ($saveInFile) {
535
            $fileToSave = !empty($fileToSave) ? $fileToSave : api_get_path(SYS_ARCHIVE_PATH).uniqid();
536
537
            $this->pdf->Output(
538
                $fileToSave,
539
                $outputMode
540
            ); // F to save the pdf in a file
541
        } else {
542
            $this->pdf->Output(
543
                $output_file,
544
                $outputMode
545
            );
546
        }
547
548
        if ('F' !== $outputMode) {
549
            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...
550
        }
551
552
        return $output_file;
553
    }
554
555
    /**
556
     * Gets the watermark from the platform or a course.
557
     *
558
     * @param   string  course code (optional)
559
     * @param   mixed   web path of the watermark image, false if there is nothing to return
560
     *
561
     * @return string
562
     */
563
    public static function get_watermark($courseCode = null)
564
    {
565
        $web_path = false;
566
        $urlId = api_get_current_access_url_id();
567
        if (!empty($courseCode) && 'true' == api_get_setting('document.pdf_export_watermark_by_course')) {
568
            $course_info = api_get_course_info($courseCode);
569
            // course path
570
            $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...
571
            if (file_exists($store_path)) {
572
                $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
573
            }
574
        } else {
575
            // course path
576
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
577
            if (file_exists($store_path)) {
578
                $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
579
            }
580
        }
581
582
        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...
583
    }
584
585
    /**
586
     * Deletes the watermark from the Platform or Course.
587
     *
588
     * @param string $courseCode course code (optional)
589
     * @param   mixed   web path of the watermark image, false if there is nothing to return
590
     *
591
     * @return bool
592
     */
593
    public static function delete_watermark($courseCode = null)
594
    {
595
        $urlId = api_get_current_access_url_id();
596
        if (!empty($courseCode) && 'true' === api_get_setting('document.pdf_export_watermark_by_course')) {
597
            $course_info = api_get_course_info($courseCode);
598
            // course path
599
            $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...
600
        } else {
601
            // course path
602
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
603
        }
604
        if (file_exists($store_path)) {
605
            unlink($store_path);
606
607
            return true;
608
        }
609
610
        return false;
611
    }
612
613
    /**
614
     * Uploads the pdf watermark in the main/default_course_document directory or in the course directory.
615
     *
616
     * @param string $filename    filename
617
     * @param string $source_file path of the file
618
     * @param string $courseCode
619
     *
620
     * @return mixed web path of the file if sucess, false otherwise
621
     */
622
    public static function upload_watermark($filename, $source_file, $courseCode = null)
623
    {
624
        $urlId = api_get_current_access_url_id();
625
        if (!empty($courseCode) && 'true' === api_get_setting('document.pdf_export_watermark_by_course')) {
626
            $course_info = api_get_course_info($courseCode);
627
            $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...
628
            $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/pdf_watermark.png';
629
        } else {
630
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images'; // course path
631
            $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
632
        }
633
        $course_image = $store_path.'/'.$urlId.'_pdf_watermark.png';
634
635
        if (file_exists($course_image)) {
636
            @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

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

814
                    self::/** @scrutinizer ignore-call */ 
815
                          set_footer();
Loading history...
815
                } else {
816
                    $this->pdf->SetHTMLFooter($this->custom_footer);
817
                }
818
            }
819
        }
820
    }
821
822
    /**
823
     * Generate a PDF file from $html in SYS_APP_PATH.
824
     *
825
     * @param string $html     PDF content
826
     * @param string $fileName File name
827
     * @param string $dest     Optional. Directory to move file
828
     *
829
     * @return string The PDF path
830
     */
831
    public function exportFromHtmlToFile($html, $fileName)
832
    {
833
        $this->template = $this->template ?: new Template('', false, false, false, false, false, false);
834
835
        $css = Container::getThemeHelper()->getAssetContents('print.css');
836
837
        $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

837
        /** @scrutinizer ignore-call */ 
838
        $pdfPath = self::content_to_pdf(
Loading history...
838
            $html,
839
            $css,
840
            $fileName,
841
            $this->params['course_code'],
842
            'F'
843
        );
844
845
        return $pdfPath;
846
    }
847
848
    /**
849
     * Create a PDF and save it into the documents area.
850
     *
851
     * @param string $htmlContent HTML Content
852
     * @param string $fileName    The file name
853
     * @param int    $courseId    The course ID
854
     * @param int    $sessionId   Optional. The session ID
855
     */
856
    public function exportFromHtmlToDocumentsArea(
857
        $htmlContent,
858
        $fileName,
859
        $courseId,
860
        $sessionId = 0
861
    ) {
862
        $userId = api_get_user_id();
863
        $courseInfo = api_get_course_info_by_id($courseId);
864
865
        $docPath = $this->exportFromHtmlToFile(
866
            $htmlContent,
867
            $fileName
868
        );
869
870
        DocumentManager::addDocument(
871
            $courseInfo,
872
            '',
873
            'file',
874
            filesize($docPath),
875
            $fileName,
876
            null,
877
            false,
878
            true,
879
            null,
880
            $sessionId,
881
            $userId,
882
            false,
883
            '',
884
            null,
885
            $docPath
886
        );
887
888
        Display::addFlash(Display::return_message(get_lang('Item added')));
889
    }
890
891
    /**
892
     * @param string $theme
893
     *
894
     * @throws MpdfException
895
     */
896
    public function setBackground($theme)
897
    {
898
        $themeName = empty($theme) ? api_get_visual_theme() : $theme;
899
        $themeDir = \Template::getThemeDir($themeName);
900
        $customLetterhead = $themeDir.'images/letterhead.png';
901
        $urlPathLetterhead = api_get_path(SYS_CSS_PATH).$customLetterhead;
902
903
        $urlWebLetterhead = '#FFFFFF';
904
        $fullPage = false;
905
        if (file_exists($urlPathLetterhead)) {
906
            $fullPage = true;
907
            $urlWebLetterhead = 'url('.api_get_path(WEB_CSS_PATH).$customLetterhead.')';
908
        }
909
910
        if ($fullPage) {
911
            $this->pdf->SetDisplayMode('fullpage');
912
            $this->pdf->SetDefaultBodyCSS('background', $urlWebLetterhead);
913
            $this->pdf->SetDefaultBodyCSS('background-image-resize', '6');
914
        }
915
    }
916
917
    /**
918
     * Fix images source paths to allow export to pdf.
919
     *
920
     * @param string $documentHtml
921
     * @param string $dirName
922
     *
923
     * @return string
924
     */
925
    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...
926
    {
927
        $html = new HTML5();
928
        $doc = $html->loadHTML($documentHtml);
929
930
        $elements = $doc->getElementsByTagName('img');
931
932
        if (empty($elements)) {
933
            return $doc->saveHTML();
934
        }
935
936
        $protocol = api_get_protocol();
937
        $sysCodePath = api_get_path(SYS_CODE_PATH);
938
        $sysCoursePath = api_get_path(SYS_PATH).'../app/courses/';
939
        $sysUploadPath = api_get_path(SYS_PATH).'../app/upload/';
940
941
        $documentPath = $courseInfo ? $sysCoursePath.$courseInfo['path'].'/document/' : '';
942
943
        $notFoundImagePath = Display::return_icon(
944
            'closed-circle.png',
945
            get_lang('FileNotFound'),
946
            [],
947
            ICON_SIZE_TINY,
948
            false,
949
            true
950
        );
951
952
        /** @var \DOMElement $element */
953
        foreach ($elements as $element) {
954
            $src = $element->getAttribute('src');
955
            $src = trim($src);
956
957
            if (api_filename_has_blacklisted_stream_wrapper($src)) {
958
                $element->setAttribute('src', $notFoundImagePath);
959
                continue;
960
            }
961
962
            if (false !== strpos($src, $protocol)) {
963
                continue;
964
            }
965
966
            // It's a reference to a file in the system, do not change it
967
            if (file_exists($src)) {
968
                continue;
969
            }
970
971
            if (0 === strpos($src, '/main/default_course_document')) {
972
                $element->setAttribute(
973
                    'src',
974
                    str_replace('/main/default_course_document', $sysCodePath.'default_course_document', $src)
975
                );
976
                continue;
977
            }
978
979
            if (0 === strpos($src, '/main/img')) {
980
                $element->setAttribute(
981
                    'src',
982
                    str_replace('/main/img/', $sysCodePath.'img/', $src)
983
                );
984
                continue;
985
            }
986
987
            if (0 === strpos($src, '/app/upload/')) {
988
                $element->setAttribute(
989
                    'src',
990
                    str_replace('/app/upload/', $sysUploadPath, $src)
991
                );
992
                continue;
993
            }
994
995
            if (empty($courseInfo)) {
996
                continue;
997
            }
998
999
            if ('/' != api_get_path(REL_PATH)) {
1000
                $oldSrcFixed = str_replace(
1001
                    api_get_path(REL_PATH).'courses/'.$courseInfo['path'].'/document/',
1002
                    '',
1003
                    $src
1004
                );
1005
1006
                // Try with the dirname if exists
1007
                if ($oldSrcFixed == $src) {
1008
                    if (file_exists($dirName.'/'.$src)) {
1009
                        $documentPath = '';
1010
                        $oldSrcFixed = $dirName.'/'.$src;
1011
                    }
1012
                }
1013
            } else {
1014
                if (false !== strpos($src, 'courses/'.$courseInfo['path'].'/document/')) {
1015
                    $oldSrcFixed = str_replace('courses/'.$courseInfo['path'].'/document/', '', $src);
1016
                } else {
1017
                    // Try with the dirname if exists
1018
                    if (file_exists($dirName.'/'.$src)) {
1019
                        $documentPath = '';
1020
                        $oldSrcFixed = $dirName.'/'.$src;
1021
                    } else {
1022
                        $documentPath = '';
1023
                        $oldSrcFixed = $src;
1024
                    }
1025
                }
1026
            }
1027
1028
            $element->setAttribute('src', $documentPath.$oldSrcFixed);
1029
        }
1030
1031
        return $doc->saveHTML();
1032
    }
1033
1034
    /**
1035
     * Replaces icon tags in the HTML content with corresponding image paths.
1036
     */
1037
    public function replaceIconsWithImages(string $content): string
1038
    {
1039
        // Load icon images
1040
        $checkboxOn = Display::return_icon('checkbox_on.png', null, null, ICON_SIZE_TINY);
1041
        $checkboxOff = Display::return_icon('checkbox_off.png', null, null, ICON_SIZE_TINY);
1042
        $radioOn = Display::return_icon('radio_on.png', null, null, ICON_SIZE_TINY);
1043
        $radioOff = Display::return_icon('radio_off.png', null, null, ICON_SIZE_TINY);
1044
1045
        // Define replacements
1046
        $replacements = [
1047
            '/<i[^>]*class="[^"]*checkbox-marked-outline[^"]*"[^>]*><\/i>/i' => $checkboxOn,
1048
            '/<i[^>]*class="[^"]*checkbox-blank-outline[^"]*"[^>]*><\/i>/i' => $checkboxOff,
1049
            '/<i[^>]*class="[^"]*radiobox-marked[^"]*"[^>]*><\/i>/i' => $radioOn,
1050
            '/<i[^>]*class="[^"]*radiobox-blank[^"]*"[^>]*><\/i>/i' => $radioOff,
1051
        ];
1052
1053
        // Perform replacements
1054
        foreach ($replacements as $pattern => $replacement) {
1055
            $content = preg_replace($pattern, $replacement, $content);
1056
        }
1057
1058
        return $content;
1059
    }
1060
}
1061