PDF::upload_watermark()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

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

174
        self::/** @scrutinizer ignore-call */ 
175
              content_to_pdf(
Loading history...
175
            $html,
176
            $css,
177
            $this->params['filename'],
178
            $this->params['course_code'],
179
            $outputMode,
180
            $saveToFile,
181
            $fileToSave,
182
            $returnHtml,
183
            $addDefaultCss
184
        );
185
    }
186
187
    /**
188
     * Converts HTML files to PDF.
189
     *
190
     * @param mixed  $html_file_array could be a html file path or an array
191
     *                                with paths example:
192
     *                                /var/www/myfile.html or array('/myfile.html','myotherfile.html') or
193
     *                                even an indexed array with both 'title' and 'path' indexes
194
     *                                for each element like
195
     *                                array(
196
     *                                0 => array('title'=>'Hello','path'=>'file.html'),
197
     *                                1 => array('title'=>'Bye','path'=>'file2.html')
198
     *                                );
199
     * @param string $pdf_name        pdf name
200
     * @param null   $course_code     (if you are using html that are located
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $course_code is correct as it would always require null to be passed?
Loading history...
201
     *                                in the document tool you must provide this)
202
     * @param bool   $print_title     add title
203
     * @param bool   $complete_style  show header and footer if true
204
     * @param bool   $addStyle
205
     * @param string $mainTitle
206
     * @param bool   $generateToFile  Optional. When it is TRUE, then the output file is move to app/cache
207
     *
208
     * @throws MpdfException
209
     *
210
     * @return false|null
211
     */
212
    public function html_to_pdf(
213
        $html_file_array,
214
        $pdf_name = '',
215
        $course_code = null,
216
        $print_title = false,
217
        $complete_style = true,
218
        $addStyle = true,
219
        $mainTitle = '',
220
        $generateToFile = false
221
    ) {
222
        if (empty($html_file_array)) {
223
            return false;
224
        }
225
226
        if (is_array($html_file_array)) {
227
            if (count($html_file_array) == 0) {
228
                return false;
229
            }
230
        } else {
231
            if (!file_exists($html_file_array)) {
232
                return false;
233
            }
234
            // Converting the string into an array
235
            $html_file_array = [$html_file_array];
236
        }
237
238
        if (!empty($course_code)) {
239
            $course_data = api_get_course_info($course_code);
240
        } else {
241
            $course_data = api_get_course_info();
242
        }
243
244
        // Clean styles and javascript document
245
        $clean_search = [
246
            '@<script[^>]*?>.*?</script>@si',
247
            '@<style[^>]*?>.*?</style>@si',
248
        ];
249
250
        // Formatting the pdf
251
        self::format_pdf($course_data, $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

251
        self::/** @scrutinizer ignore-call */ 
252
              format_pdf($course_data, $complete_style);
Loading history...
252
253
        $counter = 1;
254
        foreach ($html_file_array as $file) {
255
            // Add a page break per file
256
            $page_break = '<pagebreak>';
257
            if ($counter == count($html_file_array)) {
258
                $page_break = '';
259
            }
260
261
            // if the array provided contained subarrays with 'title' entry,
262
            // then print the title in the PDF
263
            if (is_array($file) && isset($file['title'])) {
264
                $html_title = $file['title'];
265
                $file = $file['path'];
266
            } else {
267
                //we suppose we've only been sent a file path
268
                $html_title = basename($file);
269
            }
270
271
            $counter++;
272
273
            if (empty($file) && !empty($html_title)) {
274
                // this is a chapter, print title & skip the rest
275
                if ($counter === 2 && !empty($mainTitle)) {
276
                    $this->pdf->WriteHTML(
277
                        '<html><body><h2 style="text-align: center">'.$mainTitle.'</h2></body></html>'
278
                    );
279
                }
280
                if ($print_title) {
281
                    $this->pdf->WriteHTML(
282
                        '<html><body><h3>'.$html_title.'</h3></body></html>'.$page_break
283
                    );
284
                }
285
                continue;
286
            } else {
287
                if ($counter === 2 && !empty($mainTitle)) {
288
                    $this->pdf->WriteHTML(
289
                        '<html><body><h2 style="text-align: center">'.$mainTitle.'</h2></body></html>'
290
                    );
291
                }
292
            }
293
294
            if (!file_exists($file)) {
295
                continue;
296
            }
297
298
            if ($addStyle) {
299
                $basicStyles = [
300
                    api_get_path(SYS_PATH).'web/assets/bootstrap/dist/css/bootstrap.min.css',
301
                    api_get_path(SYS_PATH).'web/css/base.css',
302
                    api_get_path(SYS_PATH).'web/css/themes/'.api_get_visual_theme().'/default.css',
303
                    api_get_print_css(false),
304
                ];
305
                foreach ($basicStyles as $style) {
306
                    if (file_exists($style)) {
307
                        $cssContent = file_get_contents($style);
308
                        try {
309
                            @$this->pdf->WriteHTML($cssContent, 1);
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

309
                            /** @scrutinizer ignore-unhandled */ @$this->pdf->WriteHTML($cssContent, 1);

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...
310
                        } catch (MpdfException $e) {
311
                            error_log($e);
312
                        }
313
                    }
314
                }
315
            }
316
317
            // it's not a chapter but the file exists, print its title
318
            if ($print_title) {
319
                @$this->pdf->WriteHTML(
320
                    '<html><body><h3>'.$html_title.'</h3></body></html>'
321
                );
322
            }
323
324
            $file_info = pathinfo($file);
325
            $extension = $file_info['extension'];
326
327
            if (in_array($extension, ['html', 'htm'])) {
328
                $dirName = $file_info['dirname'];
329
                $filename = $file_info['basename'];
330
                $filename = str_replace('_', ' ', $filename);
331
332
                if ($extension === 'html') {
333
                    $filename = basename($filename, '.html');
334
                } elseif ($extension === 'htm') {
335
                    $filename = basename($filename, '.htm');
336
                }
337
338
                $webPath = api_get_path(WEB_PATH);
339
340
                $document_html = @file_get_contents($file);
341
                $document_html = preg_replace($clean_search, '', $document_html);
342
343
                $crawler = new Crawler($document_html);
344
                $crawler
345
                    ->filter('link[rel="stylesheet"]')
346
                    ->each(function (Crawler $node) use ($webPath) {
347
                        $linkUrl = $node->link()->getUri();
348
349
                        if (!str_starts_with($linkUrl, $webPath)) {
350
                            $node->getNode(0)->parentNode->removeChild($node->getNode(0));
351
                        }
352
                    })
353
                ;
354
                $document_html = $crawler->outerHtml();
355
356
                //absolute path for frames.css //TODO: necessary?
357
                $absolute_css_path = api_get_path(WEB_CODE_PATH).'css/'.api_get_setting('stylesheets').'/frames.css';
358
                $document_html = str_replace('href="./css/frames.css"', $absolute_css_path, $document_html);
359
360
                if (!empty($course_data['path'])) {
361
                    $document_html = str_replace('../', '', $document_html);
362
363
                    // Fix app/upload links convert web to system paths
364
                    $document_html = str_replace(
365
                        api_get_path(WEB_UPLOAD_PATH),
366
                        api_get_path(SYS_UPLOAD_PATH),
367
                        $document_html
368
                    );
369
                }
370
371
                $document_html = self::fixImagesPaths($document_html, $course_data, $dirName);
372
373
                // The library Mpdf expects UTF-8 encoded input data.
374
                api_set_encoding_html($document_html, 'UTF-8');
375
                // TODO: Maybe it is better idea the title to be passed through
376
                $title = api_get_title_html($document_html, 'UTF-8', 'UTF-8');
377
                // $_GET[] too, as it is done with file name.
378
                // At the moment the title is retrieved from the html document itself.
379
                if (empty($title)) {
380
                    $title = $filename; // Here file name is expected to contain ASCII symbols only.
381
                }
382
                if (!empty($document_html)) {
383
                    @$this->pdf->WriteHTML($document_html.$page_break);
384
                }
385
            } elseif (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
386
                // Images
387
                $image = Display::img($file);
388
                @$this->pdf->WriteHTML('<html><body>'.$image.'</body></html>'.$page_break);
389
            }
390
        }
391
        if (empty($pdf_name)) {
392
            $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
393
        } else {
394
            $pdf_name = api_replace_dangerous_char($pdf_name);
395
            $output_file = $pdf_name.'.pdf';
396
        }
397
        // F to save the pdf in a file
398
        if ($generateToFile) {
399
            @$this->pdf->Output(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for Output(). 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

399
            /** @scrutinizer ignore-unhandled */ @$this->pdf->Output(

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...
400
                api_get_path(SYS_ARCHIVE_PATH).$output_file,
401
                'F'
402
            );
403
        } else {
404
            @$this->pdf->Output($output_file, 'D');
405
        }
406
407
        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...
408
    }
409
410
    /**
411
     * Converts a html string to PDF.
412
     *
413
     * @param string $document_html  valid html
414
     * @param string $css            CSS content of a CSS file
415
     * @param string $pdf_name       pdf name
416
     * @param string $course_code    course code
417
     *                               (if you are using html that are located in the document tool you must provide this)
418
     * @param string $outputMode     the MPDF output mode can be:
419
     * @param bool   $saveInFile
420
     * @param string $fileToSave
421
     * @param bool   $returnHtml
422
     * @param bool   $addDefaultCss
423
     * @param bool   $completeHeader
424
     *
425
     * @throws MpdfException
426
     *
427
     * 'I' (print on standard output),
428
     * 'D' (download file) (this is the default value),
429
     * 'F' (save to local file) or
430
     * 'S' (return as a string)
431
     *
432
     * @return string Web path
433
     */
434
    public function content_to_pdf(
435
        $document_html,
436
        $css = '',
437
        $pdf_name = '',
438
        $course_code = null,
439
        $outputMode = 'D',
440
        $saveInFile = false,
441
        $fileToSave = null,
442
        $returnHtml = false,
443
        $addDefaultCss = false,
444
        $completeHeader = true
445
    ) {
446
        $urlAppend = api_get_configuration_value('url_append');
447
448
        if (empty($document_html)) {
449
            return false;
450
        }
451
452
        // clean styles and javascript document
453
        $clean_search = [
454
            '@<script[^>]*?>.*?</script>@si',
455
            '@<style[^>]*?>.*?</style>@siU',
456
        ];
457
458
        // Formatting the pdf
459
        $course_data = api_get_course_info($course_code);
460
        self::format_pdf($course_data, $completeHeader);
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

460
        self::/** @scrutinizer ignore-call */ 
461
              format_pdf($course_data, $completeHeader);
Loading history...
461
        $document_html = preg_replace($clean_search, '', $document_html);
462
463
        //absolute path for frames.css //TODO: necessary?
464
        $absolute_css_path = api_get_path(WEB_CSS_PATH).api_get_setting('stylesheets').'/frames.css';
465
        $document_html = str_replace('href="./css/frames.css"', 'href="'.$absolute_css_path.'"', $document_html);
466
        $document_html = str_replace('../../', '', $document_html);
467
        $document_html = str_replace('../', '', $document_html);
468
        $document_html = str_replace(
469
            (empty($urlAppend) ? '' : $urlAppend.'/').'courses/'.$course_code.'/document/',
470
            '',
471
            $document_html
472
        );
473
474
        if (!empty($course_data['path'])) {
475
            $document_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document/';
476
477
            $doc = new DOMDocument();
478
            @$doc->loadHTML($document_html);
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

478
            /** @scrutinizer ignore-unhandled */ @$doc->loadHTML($document_html);

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...
479
480
            //Fixing only images @todo do the same thing with other elements
481
            $elements = $doc->getElementsByTagName('img');
482
            $protocol = api_get_protocol();
483
            $replaced = [];
484
            if (!empty($elements)) {
485
                foreach ($elements as $item) {
486
                    $old_src = $item->getAttribute('src');
487
488
                    if (in_array($old_src, $replaced)) {
489
                        continue;
490
                    }
491
492
                    if (strpos($old_src, $protocol) === false) {
493
                        if (strpos($old_src, '/main/default_course_document') === false) {
494
                            if (strpos($old_src, '/main/inc/lib/') === false &&
495
                                strpos($old_src, '/app/upload/') === false
496
                            ) {
497
                                $old_src_fixed = str_replace(
498
                                    api_get_path(REL_COURSE_PATH).$course_data['path'].'/document/',
499
                                    '',
500
                                    $old_src
501
                                );
502
                                $old_src_fixed = str_replace(
503
                                    'courses/'.$course_data['path'].'/document/',
504
                                    '',
505
                                    $old_src_fixed
506
                                );
507
                                $new_path = $document_path.$old_src_fixed;
508
                                $document_html = str_replace($old_src, $new_path, $document_html);
509
                                $replaced[] = $old_src;
510
                            }
511
                        }
512
                    }
513
                }
514
            }
515
        }
516
517
        // Use sys path to correct export images
518
        $document_html = str_replace(
519
            api_get_path(WEB_CODE_PATH).'img/',
520
            api_get_path(SYS_CODE_PATH).'img/',
521
            $document_html
522
        );
523
524
        $theme = api_get_visual_theme();
525
        $document_html = str_replace(
526
            api_get_path(WEB_CSS_PATH).'themes/'.$theme,
527
            api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme,
528
            $document_html
529
        );
530
531
        $document_html = str_replace(api_get_path(WEB_UPLOAD_PATH), api_get_path(SYS_UPLOAD_PATH), $document_html);
532
        $document_html = str_replace(api_get_path(WEB_ARCHIVE_PATH), api_get_path(SYS_ARCHIVE_PATH), $document_html);
533
534
        // The library Mpdf expects UTF-8 encoded input data.
535
        api_set_encoding_html($document_html, 'UTF-8');
536
        // At the moment the title is retrieved from the html document itself.
537
        if ($returnHtml) {
538
            return "<style>$css</style>".$document_html;
539
        }
540
541
        if (!empty($css)) {
542
            try {
543
                @$this->pdf->WriteHTML($css, 1);
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

543
                /** @scrutinizer ignore-unhandled */ @$this->pdf->WriteHTML($css, 1);

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...
544
            } catch (MpdfException $e) {
545
                error_log($e);
546
            }
547
        }
548
549
        $cssBootstrap = file_get_contents(api_get_path(SYS_PATH).'web/assets/bootstrap/dist/css/bootstrap.min.css');
550
        if ($addDefaultCss) {
551
            $cssContent = api_get_print_css();
552
            try {
553
                @$this->pdf->WriteHTML($cssBootstrap, 1);
554
                @$this->pdf->WriteHTML($cssContent, 1);
555
            } catch (MpdfException $e) {
556
                error_log($e);
557
            }
558
        }
559
560
        try {
561
            @$this->pdf->WriteHTML($document_html);
562
        } catch (MpdfException $e) {
563
            error_log($e);
564
        }
565
566
        if (empty($pdf_name)) {
567
            $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
568
        } else {
569
            $pdf_name = api_replace_dangerous_char($pdf_name);
570
            $output_file = $pdf_name.'.pdf';
571
        }
572
573
        if ($outputMode === 'F') {
574
            $output_file = api_get_path(SYS_ARCHIVE_PATH).$output_file;
575
        }
576
577
        if ($saveInFile) {
578
            $fileToSave = !empty($fileToSave) ? $fileToSave : api_get_path(SYS_ARCHIVE_PATH).uniqid();
579
            try {
580
                @$this->pdf->Output(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for Output(). 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

580
                /** @scrutinizer ignore-unhandled */ @$this->pdf->Output(

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...
581
                    $fileToSave,
582
                    $outputMode
583
                ); // F to save the pdf in a file
584
            } catch (MpdfException $e) {
585
                error_log($e);
586
            }
587
        } else {
588
            try {
589
                @$this->pdf->Output(
590
                    $output_file,
591
                    $outputMode
592
                ); // F to save the pdf in a file
593
            } catch (MpdfException $e) {
594
                error_log($e);
595
            }
596
        }
597
598
        if ($outputMode != 'F') {
599
            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...
600
        }
601
602
        return $output_file;
603
    }
604
605
    /**
606
     * Gets the watermark from the platform or a course.
607
     *
608
     * @param   string  course code (optional)
609
     * @param   mixed   web path of the watermark image, false if there is nothing to return
610
     *
611
     * @return string
612
     */
613
    public static function get_watermark($course_code = null)
614
    {
615
        $web_path = false;
616
        $urlId = api_get_current_access_url_id();
617
        if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') === 'true') {
618
            $course_info = api_get_course_info($course_code);
619
            // course path
620
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
621
            if (file_exists($store_path)) {
622
                $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
623
            }
624
        } else {
625
            // course path
626
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
627
            if (file_exists($store_path)) {
628
                $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
629
            }
630
        }
631
632
        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...
633
    }
634
635
    /**
636
     * Deletes the watermark from the Platform or Course.
637
     *
638
     * @param string $course_code course code (optional)
639
     * @param   mixed   web path of the watermark image, false if there is nothing to return
640
     *
641
     * @return bool
642
     */
643
    public static function delete_watermark($course_code = null)
644
    {
645
        $urlId = api_get_current_access_url_id();
646
        if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') == 'true') {
647
            $course_info = api_get_course_info($course_code);
648
            // course path
649
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
650
        } else {
651
            // course path
652
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
653
        }
654
        if (file_exists($store_path)) {
655
            unlink($store_path);
656
657
            return true;
658
        }
659
660
        return false;
661
    }
662
663
    /**
664
     * Uploads the pdf watermark in the main/default_course_document directory or in the course directory.
665
     *
666
     * @param string $filename    filename
667
     * @param string $source_file path of the file
668
     * @param string $course_code
669
     *
670
     * @return mixed web path of the file if sucess, false otherwise
671
     */
672
    public static function upload_watermark($filename, $source_file, $course_code = null)
673
    {
674
        $urlId = api_get_current_access_url_id();
675
        if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') == 'true') {
676
            $course_info = api_get_course_info($course_code);
677
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path']; // course path
678
            $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/pdf_watermark.png';
679
        } else {
680
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images'; // course path
681
            $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
682
        }
683
        $course_image = $store_path.'/'.$urlId.'_pdf_watermark.png';
684
685
        if (file_exists($course_image)) {
686
            @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

686
            /** @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...
687
        }
688
        $my_image = new Image($source_file);
689
        $result = $my_image->send_image($course_image, -1, 'png');
690
        if ($result) {
691
            $result = $web_path;
692
        }
693
694
        return $result;
695
    }
696
697
    /**
698
     * Returns the default header.
699
     */
700
    public function get_header($course_code = null)
701
    {
702
        /*$header = api_get_setting('pdf_export_watermark_text');
703
    	if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') == 'true') {
704
            $header = api_get_course_setting('pdf_export_watermark_text');
705
        }
706
        return $header;*/
707
    }
708
709
    /**
710
     * Sets the PDF footer.
711
     */
712
    public function set_footer()
713
    {
714
        $this->pdf->defaultfooterfontsize = 12; // in pts
715
        $this->pdf->defaultfooterfontstyle = 'B'; // blank, B, I, or BI
716
        $this->pdf->defaultfooterline = 1; // 1 to include line below header/above footer
717
718
        $view = new Template('', false, false, false, true, false, false);
719
        $template = $view->get_template('export/pdf_footer.tpl');
720
        $footerHTML = $view->fetch($template);
721
722
        $this->pdf->SetHTMLFooter($footerHTML, 'E'); //Even pages
723
        $this->pdf->SetHTMLFooter($footerHTML, 'O'); //Odd pages
724
    }
725
726
    public function setCertificateFooter()
727
    {
728
        $this->pdf->defaultfooterfontsize = 12; // in pts
729
        $this->pdf->defaultfooterfontstyle = 'B'; // blank, B, I, or BI
730
        $this->pdf->defaultfooterline = 1; // 1 to include line below header/above footer
731
732
        $view = new Template('', false, false, false, true, false, false);
733
        $template = $view->get_template('export/pdf_certificate_footer.tpl');
734
        $footerHTML = $view->fetch($template);
735
736
        $this->pdf->SetHTMLFooter($footerHTML, 'E'); //Even pages
737
        $this->pdf->SetHTMLFooter($footerHTML, 'O'); //Odd pages
738
    }
739
740
    /**
741
     * Sets the PDF header.
742
     *
743
     * @param array $courseInfo
744
     */
745
    public function set_header($courseInfo)
746
    {
747
        $this->pdf->defaultheaderfontsize = 10; // in pts
748
        $this->pdf->defaultheaderfontstyle = 'BI'; // blank, B, I, or BI
749
        $this->pdf->defaultheaderline = 1; // 1 to include line below header/above footer
750
751
        $userId = api_get_user_id();
752
        if (!empty($courseInfo['code'])) {
753
            $teacher_list = CourseManager::get_teacher_list_from_course_code($courseInfo['code']);
754
755
            $teachers = '';
756
            if (!empty($teacher_list)) {
757
                foreach ($teacher_list as $teacher) {
758
                    if ($teacher['user_id'] != $userId) {
759
                        continue;
760
                    }
761
762
                    // Do not show the teacher list see BT#4080 only the current teacher name
763
                    $teachers = api_get_person_name($teacher['firstname'], $teacher['lastname']);
764
                }
765
            }
766
767
            $organization = null;
768
769
            // try getting the course logo
770
            if (api_get_configuration_value('mail_header_from_custom_course_logo') == true) {
771
                $organization = CourseManager::getCourseEmailPicture($courseInfo, []);
772
            }
773
            // only show platform logo in mail if no course photo available
774
            if (empty($organization)) {
775
                $organization = ChamiloApi::getPlatformLogo('', [], false, true);
776
            }
777
778
            // Use custom logo image.
779
            $pdfLogo = api_get_setting('pdf_logo_header');
780
            if ($pdfLogo === 'true') {
781
                $visualTheme = api_get_visual_theme();
782
                $img = api_get_path(SYS_CSS_PATH).'themes/'.$visualTheme.'/images/pdf_logo_header.png';
783
                if (file_exists($img)) {
784
                    $organization = "<img src='$img'>";
785
                }
786
            }
787
788
            $view = new Template('', false, false, false, true, false, false);
789
            $view->assign('teacher_name', $teachers);
790
            $view->assign('organization', $organization);
791
            $template = $view->get_template('export/pdf_header.tpl');
792
            $headerHTML = $view->fetch($template);
793
794
            $this->pdf->SetHTMLHeader($headerHTML, 'E');
795
            $this->pdf->SetHTMLHeader($headerHTML, 'O');
796
        }
797
    }
798
799
    /**
800
     * @param string $header html content
801
     */
802
    public function set_custom_header($header)
803
    {
804
        $this->custom_header = $header;
805
    }
806
807
    /**
808
     * @param array $footer html content
809
     */
810
    public function set_custom_footer($footer)
811
    {
812
        $this->custom_footer = $footer;
813
    }
814
815
    /**
816
     * Pre-formats a PDF to the right size and, if not stated otherwise, with
817
     * header, footer and watermark (if any).
818
     *
819
     * @param array $courseInfo General course information (to fill headers)
820
     * @param bool  $complete   Whether we want headers, footers and watermark or not
821
     */
822
    public function format_pdf($courseInfo, $complete = true)
823
    {
824
        $courseCode = null;
825
        if (!empty($courseInfo)) {
826
            $courseCode = $courseInfo['code'];
827
        }
828
829
        /*$pdf->SetAuthor('Documents Chamilo');
830
        $pdf->SetTitle('title');
831
        $pdf->SetSubject('Exported from Chamilo Documents');
832
        $pdf->SetKeywords('Chamilo Documents');
833
        */
834
        // TODO: To be read from the html document.
835
        $this->pdf->directionality = api_get_text_direction();
836
        // Use different Odd/Even headers and footers and mirror margins
837
        $this->pdf->mirrorMargins = 1;
838
839
        // Add decoration only if not stated otherwise
840
        if ($complete) {
841
            // Adding watermark
842
            if (api_get_setting('pdf_export_watermark_enable') === 'true') {
843
                $watermark_file = self::get_watermark($courseCode);
844
                if ($watermark_file) {
845
                    //http://mpdf1.com/manual/index.php?tid=269&searchstring=watermark
846
                    $this->pdf->SetWatermarkImage($watermark_file);
847
                    $this->pdf->showWatermarkImage = true;
848
                } else {
849
                    $watermark_file = self::get_watermark();
850
851
                    if ($watermark_file) {
852
                        $this->pdf->SetWatermarkImage($watermark_file);
853
                        $this->pdf->showWatermarkImage = true;
854
                    }
855
                }
856
857
                $watermark_text = api_get_setting('pdf_export_watermark_text');
858
                if ($courseCode && 'true' === api_get_setting('pdf_export_watermark_by_course')) {
859
                    $courseWaterMark = api_get_course_setting('pdf_export_watermark_text');
860
                    if (!empty($courseWaterMark) && -1 != $courseWaterMark) {
861
                        $watermark_text = $courseWaterMark;
862
                    }
863
                }
864
865
                if (!empty($watermark_text)) {
866
                    $this->pdf->SetWatermarkText(
867
                        UtfString::strcode2utf($watermark_text),
868
                        0.1
869
                    );
870
                    $this->pdf->showWatermarkText = true;
871
                }
872
            }
873
874
            if (empty($this->custom_header)) {
875
                self::set_header($courseInfo);
0 ignored issues
show
Bug Best Practice introduced by
The method PDF::set_header() 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

875
                self::/** @scrutinizer ignore-call */ 
876
                      set_header($courseInfo);
Loading history...
876
            } else {
877
                $this->pdf->SetHTMLHeader($this->custom_header, 'E');
878
                $this->pdf->SetHTMLHeader($this->custom_header, 'O');
879
            }
880
881
            if (empty($this->custom_footer)) {
882
                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

882
                self::/** @scrutinizer ignore-call */ 
883
                      set_footer();
Loading history...
883
            } else {
884
                $this->pdf->SetHTMLFooter($this->custom_footer);
885
            }
886
        }
887
    }
888
889
    /**
890
     * Generate a PDF file from $html in SYS_APP_PATH.
891
     *
892
     * @param string $html     PDF content
893
     * @param string $fileName File name
894
     * @param string $dest     Optional. Directory to move file
895
     *
896
     * @return string The PDF path
897
     */
898
    public function exportFromHtmlToFile($html, $fileName, $dest = null)
899
    {
900
        $this->template = $this->template ?: new Template('', false, false, false, false, false, false);
901
902
        $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

902
        /** @scrutinizer ignore-call */ 
903
        $pdfPath = self::content_to_pdf(
Loading history...
903
            $html,
904
            api_get_print_css(),
905
            $fileName,
906
            $this->params['course_code'],
907
            'F'
908
        );
909
910
        if (!$dest) {
911
            return $pdfPath;
912
        }
913
914
        move($pdfPath, $dest);
915
916
        return $dest.basename($pdfPath);
917
    }
918
919
    /**
920
     * Create a PDF and save it into the documents area.
921
     *
922
     * @param string $htmlContent HTML Content
923
     * @param string $fileName    The file name
924
     * @param int    $courseId    The course ID
925
     * @param int    $sessionId   Optional. The session ID
926
     */
927
    public function exportFromHtmlToDocumentsArea(
928
        $htmlContent,
929
        $fileName,
930
        $courseId,
931
        $sessionId = 0
932
    ) {
933
        $userId = api_get_user_id();
934
        $courseInfo = api_get_course_info_by_id($courseId);
935
        $courseDirectory = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document/';
936
937
        $docPath = $this->exportFromHtmlToFile(
938
            $htmlContent,
939
            $fileName,
940
            $courseDirectory
941
        );
942
943
        $docId = add_document(
944
            $courseInfo,
945
            str_replace($courseDirectory, '/', $docPath),
946
            'file',
947
            filesize($docPath),
948
            $fileName,
949
            null,
950
            false,
951
            true,
952
            null,
953
            $sessionId,
954
            $userId
955
        );
956
957
        api_item_property_update(
958
            $courseInfo,
959
            TOOL_DOCUMENT,
960
            $docId,
961
            'DocumentAdded',
962
            $userId
963
        );
964
965
        Display::addFlash(Display::return_message(get_lang('ItemAdded')));
966
    }
967
968
    /**
969
     * @param string $theme
970
     * @param bool   $fullPage
971
     *
972
     * @throws MpdfException
973
     */
974
    public function setBackground($theme, $fullPage = false)
975
    {
976
        $themeName = empty($theme) ? api_get_visual_theme() : $theme;
977
        $themeDir = Template::getThemeDir($themeName);
978
        $customLetterhead = $themeDir.'images/letterhead.png';
979
        $urlPathLetterhead = api_get_path(SYS_CSS_PATH).$customLetterhead;
980
981
        if (file_exists($urlPathLetterhead)) {
982
            $urlWebLetterhead = 'url('.api_get_path(WEB_CSS_PATH).$customLetterhead.')';
983
        } else {
984
            $urlWebLetterhead = 'url('.api_get_path(WEB_CSS_PATH).'themes/chamilo/images/letterhead.png)';
985
        }
986
987
        if ($fullPage) {
988
            $this->pdf->SetDisplayMode('fullpage');
989
            $this->pdf->SetDefaultBodyCSS('background', $urlWebLetterhead);
990
            $this->pdf->SetDefaultBodyCSS('background-image-resize', '6');
991
        }
992
    }
993
994
    /**
995
     * Fix images source paths to allow export to pdf.
996
     */
997
    public static function fixImagesPaths(string $documentHtml, array $courseInfo, string $dirName = ''): string
998
    {
999
        $documentHtml = '<?xml encoding="utf-8" ?>'.$documentHtml;
1000
        $doc = new DOMDocument();
1001
        @$doc->loadHTML($documentHtml);
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

1001
        /** @scrutinizer ignore-unhandled */ @$doc->loadHTML($documentHtml);

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...
1002
1003
        $elements = $doc->getElementsByTagName('img');
1004
1005
        if (empty($elements)) {
1006
            return $doc->saveHTML();
1007
        }
1008
1009
        $protocol = api_get_protocol();
1010
        $sysCodePath = api_get_path(SYS_CODE_PATH);
1011
        $sysCoursePath = api_get_path(SYS_COURSE_PATH);
1012
        $sysUploadPath = api_get_path(SYS_UPLOAD_PATH);
1013
        $sysPath = api_get_path(SYS_PATH);
1014
1015
        $documentPath = $courseInfo ? $sysCoursePath.$courseInfo['path'].'/document/' : '';
1016
1017
        $notFoundImagePath = Display::return_icon(
1018
            'closed-circle.png',
1019
            get_lang('FileNotFound'),
1020
            [],
1021
            ICON_SIZE_TINY,
1022
            false,
1023
            true
1024
        );
1025
1026
        /** @var DOMElement $element */
1027
        foreach ($elements as $element) {
1028
            $src = $element->getAttribute('src');
1029
            $src = trim($src);
1030
1031
            if (api_filename_has_blacklisted_stream_wrapper($src)) {
1032
                $element->setAttribute('src', $notFoundImagePath);
1033
                continue;
1034
            }
1035
1036
            if (strpos($src, $protocol) !== false) {
1037
                continue;
1038
            }
1039
1040
            // It's a reference to a file in the system, do not change it
1041
            if (file_exists($src)) {
1042
                continue;
1043
            }
1044
1045
            if (strpos($src, '/main/default_course_document') === 0) {
1046
                $element->setAttribute(
1047
                    'src',
1048
                    str_replace('/main/default_course_document', $sysCodePath.'default_course_document', $src)
1049
                );
1050
                continue;
1051
            }
1052
1053
            if (strpos($src, '/main/img') === 0) {
1054
                $element->setAttribute(
1055
                    'src',
1056
                    str_replace('/main/img/', $sysCodePath.'img/', $src)
1057
                );
1058
                continue;
1059
            }
1060
1061
            if (strpos($src, '/app/upload/') === 0) {
1062
                $element->setAttribute(
1063
                    'src',
1064
                    str_replace('/app/upload/', $sysUploadPath, $src)
1065
                );
1066
                continue;
1067
            }
1068
1069
            if (strpos($src, '/web/css/themes/') === 0) {
1070
                $element->setAttribute(
1071
                    'src',
1072
                    str_replace('/web/css/themes/', $sysPath.'web/css/themes/', $src)
1073
                );
1074
1075
                continue;
1076
            }
1077
1078
            if (empty($courseInfo)) {
1079
                continue;
1080
            }
1081
1082
            if (api_get_path(REL_PATH) != '/') {
1083
                $oldSrcFixed = str_replace(
1084
                    api_get_path(REL_PATH).'courses/'.$courseInfo['path'].'/document/',
1085
                    '',
1086
                    $src
1087
                );
1088
1089
                // Try with the dirname if exists
1090
                if ($oldSrcFixed == $src) {
1091
                    if (file_exists($dirName.'/'.$src)) {
1092
                        $documentPath = '';
1093
                        $oldSrcFixed = $dirName.'/'.$src;
1094
                    }
1095
                }
1096
            } else {
1097
                if (strpos($src, 'courses/'.$courseInfo['path'].'/document/') !== false) {
1098
                    $oldSrcFixed = str_replace('courses/'.$courseInfo['path'].'/document/', '', $src);
1099
                } else {
1100
                    // Try with the dirname if exists
1101
                    $documentPath = '';
1102
                    if (file_exists($dirName.'/'.$src)) {
1103
                        $oldSrcFixed = $dirName.'/'.$src;
1104
                    } else {
1105
                        $oldSrcFixed = $src;
1106
                    }
1107
                }
1108
            }
1109
1110
            $element->setAttribute('src', $documentPath.$oldSrcFixed);
1111
        }
1112
1113
        return $doc->saveHTML();
1114
    }
1115
}
1116