Passed
Push — master ( ac7b68...ce62aa )
by
unknown
16:57 queued 08:46
created

PDF::replaceIconsWithImages()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

774
                        /** @scrutinizer ignore-call */ 
775
                        strcode2utf($watermark_text),
Loading history...
775
                        0.1
776
                    );
777
                    $this->pdf->showWatermarkText = true;
778
                }
779
            }
780
781
            if ($disablePagination) {
782
                $this->pdf->SetHTMLHeader('');
783
                $this->pdf->SetHTMLFooter('');
784
            } else {
785
                if (empty($this->custom_header)) {
786
                    $this->set_header($courseInfo);
787
                } else {
788
                    $this->pdf->SetHTMLHeader($this->custom_header, 'E');
789
                    $this->pdf->SetHTMLHeader($this->custom_header, 'O');
790
                }
791
792
                if (empty($this->custom_footer)) {
793
                    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

793
                    self::/** @scrutinizer ignore-call */ 
794
                          set_footer();
Loading history...
794
                } else {
795
                    $this->pdf->SetHTMLFooter($this->custom_footer);
796
                }
797
            }
798
        }
799
    }
800
801
    /**
802
     * Generate a PDF file from $html in SYS_APP_PATH.
803
     *
804
     * @param string $html     PDF content
805
     * @param string $fileName File name
806
     * @param string $dest     Optional. Directory to move file
807
     *
808
     * @return string The PDF path
809
     */
810
    public function exportFromHtmlToFile($html, $fileName)
811
    {
812
        $this->template = $this->template ?: new Template('', false, false, false, false, false, false);
813
814
        $css = Container::getThemeHelper()->getAssetContents('print.css');
815
816
        $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

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