Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

PDF   F

Complexity

Total Complexity 144

Size/Duplication

Total Lines 1040
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 505
c 0
b 0
f 0
dl 0
loc 1040
rs 2
wmc 144

18 Methods

Rating   Name   Duplication   Size   Complexity  
F __construct() 0 53 20
B html_to_pdf_with_template() 0 73 8
A set_custom_header() 0 3 1
A delete_watermark() 0 18 4
A setBackground() 0 17 4
A upload_watermark() 0 23 5
B set_header() 0 41 7
A exportFromHtmlToFile() 0 19 3
C format_pdf() 0 64 13
A setCertificateFooter() 0 12 1
A get_header() 0 2 1
A exportFromHtmlToDocumentsArea() 0 39 1
A get_watermark() 0 20 5
A set_footer() 0 12 1
A set_custom_footer() 0 3 1
F html_to_pdf() 0 181 32
C fixImagesPaths() 0 94 15
F content_to_pdf() 0 161 22

How to fix   Complexity   

Complex Class

Complex classes like PDF often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PDF, and based on these observations, apply Extract Interface, too.

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

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

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

294
                            /** @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...
295
                        } catch (MpdfException $e) {
296
                            error_log($e);
297
                        }
298
                    }
299
                }
300
            }
301
302
            // it's not a chapter but the file exists, print its title
303
            if ($print_title) {
304
                @$this->pdf->WriteHTML(
305
                    '<html><body><h3>'.$html_title.'</h3></body></html>'
306
                );
307
            }
308
309
            $file_info = pathinfo($file);
310
            $extension = $file_info['extension'];
311
312
            if (in_array($extension, ['html', 'htm'])) {
313
                $dirName = $file_info['dirname'];
314
                $filename = $file_info['basename'];
315
                $filename = str_replace('_', ' ', $filename);
316
317
                if ($extension === 'html') {
318
                    $filename = basename($filename, '.html');
319
                } elseif ($extension === 'htm') {
320
                    $filename = basename($filename, '.htm');
321
                }
322
323
                $document_html = @file_get_contents($file);
324
                $document_html = preg_replace($clean_search, '', $document_html);
325
326
                //absolute path for frames.css //TODO: necessary?
327
                $absolute_css_path = api_get_path(WEB_CODE_PATH).'css/'.api_get_setting('stylesheets').'/frames.css';
328
                $document_html = str_replace('href="./css/frames.css"', $absolute_css_path, $document_html);
329
330
                if (!empty($course_data['path'])) {
331
                    $document_html = str_replace('../', '', $document_html);
332
333
                    // Fix app/upload links convert web to system paths
334
                    $document_html = str_replace(
335
                        api_get_path(WEB_UPLOAD_PATH),
336
                        api_get_path(SYS_UPLOAD_PATH),
337
                        $document_html
338
                    );
339
                }
340
341
                $document_html = self::fixImagesPaths($document_html, $course_data, $dirName);
342
343
                // The library mPDF expects UTF-8 encoded input data.
344
                api_set_encoding_html($document_html, 'UTF-8');
345
                // TODO: Maybe it is better idea the title to be passed through
346
                $title = api_get_title_html($document_html, 'UTF-8', 'UTF-8');
347
                // $_GET[] too, as it is done with file name.
348
                // At the moment the title is retrieved from the html document itself.
349
                if (empty($title)) {
350
                    $title = $filename; // Here file name is expected to contain ASCII symbols only.
351
                }
352
                if (!empty($document_html)) {
353
                    @$this->pdf->WriteHTML($document_html.$page_break);
354
                }
355
            } elseif (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
356
                // Images
357
                $image = Display::img($file);
358
                @$this->pdf->WriteHTML('<html><body>'.$image.'</body></html>'.$page_break);
359
            }
360
        }
361
        if (empty($pdf_name)) {
362
            $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
363
        } else {
364
            $pdf_name = api_replace_dangerous_char($pdf_name);
365
            $output_file = $pdf_name.'.pdf';
366
        }
367
        // F to save the pdf in a file
368
        if ($generateToFile) {
369
            @$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

369
            /** @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...
370
                api_get_path(SYS_ARCHIVE_PATH).$output_file,
371
                'F'
372
            );
373
        } else {
374
            @$this->pdf->Output($output_file, 'D');
375
        }
376
377
        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...
378
    }
379
380
    /**
381
     * Converts an html string to PDF.
382
     *
383
     * @param string $document_html  valid html
384
     * @param string $css            CSS content of a CSS file
385
     * @param string $pdf_name       pdf name
386
     * @param string $course_code    course code
387
     *                               (if you are using html that are located in the document tool you must provide this)
388
     * @param string $outputMode     the MPDF output mode can be:
389
     * @param bool   $saveInFile
390
     * @param string $fileToSave
391
     * @param bool   $returnHtml
392
     * @param bool   $addDefaultCss
393
     * @param bool   $completeHeader
394
     *
395
     * 'I' (print on standard output),
396
     * 'D' (download file) (this is the default value),
397
     * 'F' (save to local file) or
398
     * 'S' (return as a string)
399
     *
400
     * @throws MpdfException
401
     *
402
     * @return string Web path
403
     */
404
    public function content_to_pdf(
405
        $document_html,
406
        $css = '',
407
        $pdf_name = '',
408
        $course_code = null,
409
        $outputMode = 'D',
410
        $saveInFile = false,
411
        $fileToSave = null,
412
        $returnHtml = false,
413
        $addDefaultCss = false,
414
        $completeHeader = true
415
    ) {
416
        $urlAppend = api_get_configuration_value('url_append');
417
418
        if (empty($document_html)) {
419
            return false;
420
        }
421
422
        // clean styles and javascript document
423
        $clean_search = [
424
            '@<script[^>]*?>.*?</script>@si',
425
            '@<style[^>]*?>.*?</style>@siU',
426
        ];
427
428
        // Formatting the pdf
429
        $course_data = api_get_course_info($course_code);
430
        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

430
        self::/** @scrutinizer ignore-call */ 
431
              format_pdf($course_data, $completeHeader);
Loading history...
431
        $document_html = preg_replace($clean_search, '', $document_html);
432
433
        //absolute path for frames.css //TODO: necessary?
434
        $absolute_css_path = api_get_path(WEB_CSS_PATH).api_get_setting('stylesheets').'/frames.css';
435
        $document_html = str_replace('href="./css/frames.css"', 'href="'.$absolute_css_path.'"', $document_html);
436
        $document_html = str_replace('../../', '', $document_html);
437
        $document_html = str_replace('../', '', $document_html);
438
        $document_html = str_replace(
439
            (empty($urlAppend) ? '' : $urlAppend.'/').'courses/'.$course_code.'/document/',
440
            '',
441
            $document_html
442
        );
443
444
        if (!empty($course_data['path'])) {
445
            $document_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document/';
446
447
            $doc = new DOMDocument();
448
            @$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

448
            /** @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...
449
450
            //Fixing only images @todo do the same thing with other elements
451
            $elements = $doc->getElementsByTagName('img');
452
            $protocol = api_get_protocol();
453
            $replaced = [];
454
            if (!empty($elements)) {
455
                foreach ($elements as $item) {
456
                    $old_src = $item->getAttribute('src');
457
458
                    if (in_array($old_src, $replaced)) {
459
                        continue;
460
                    }
461
462
                    if (strpos($old_src, $protocol) === false) {
463
                        if (strpos($old_src, '/main/default_course_document') === false) {
464
                            if (strpos($old_src, '/main/inc/lib/') === false &&
465
                                strpos($old_src, '/app/upload/') === false
466
                            ) {
467
                                $old_src_fixed = str_replace(
468
                                    api_get_path(REL_COURSE_PATH).$course_data['path'].'/document/',
469
                                    '',
470
                                    $old_src
471
                                );
472
                                $old_src_fixed = str_replace(
473
                                    'courses/'.$course_data['path'].'/document/',
474
                                    '',
475
                                    $old_src_fixed
476
                                );
477
                                $new_path = $document_path.$old_src_fixed;
478
                                $document_html = str_replace($old_src, $new_path, $document_html);
479
                                $replaced[] = $old_src;
480
                            }
481
                        }
482
                    }
483
                }
484
            }
485
        }
486
487
        // Use sys path to correct export images
488
        $document_html = str_replace(
489
            api_get_path(WEB_CODE_PATH).'img/',
490
            api_get_path(SYS_CODE_PATH).'img/',
491
            $document_html
492
        );
493
494
        $theme = api_get_visual_theme();
495
        $document_html = str_replace(
496
            api_get_path(WEB_CSS_PATH).'themes/'.$theme,
497
            api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme,
498
            $document_html
499
        );
500
501
        $document_html = str_replace(api_get_path(WEB_UPLOAD_PATH), api_get_path(SYS_UPLOAD_PATH), $document_html);
502
        $document_html = str_replace(api_get_path(WEB_ARCHIVE_PATH), api_get_path(SYS_ARCHIVE_PATH), $document_html);
503
504
        // The library mPDF expects UTF-8 encoded input data.
505
        api_set_encoding_html($document_html, 'UTF-8');
506
        // At the moment the title is retrieved from the html document itself.
507
        if ($returnHtml) {
508
            return "<style>$css</style>".$document_html;
509
        }
510
511
        if (!empty($css)) {
512
            try {
513
                @$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

513
                /** @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...
514
            } catch (MpdfException $e) {
515
                error_log($e);
516
            }
517
        }
518
519
        $cssBootstrap = file_get_contents(api_get_path(SYS_PATH).'web/assets/bootstrap/dist/css/bootstrap.min.css');
520
        if ($addDefaultCss) {
521
            $cssContent = api_get_print_css();
522
            try {
523
                @$this->pdf->WriteHTML($cssBootstrap, 1);
524
                @$this->pdf->WriteHTML($cssContent, 1);
525
            } catch (MpdfException $e) {
526
                error_log($e);
527
            }
528
        }
529
530
        try {
531
            @$this->pdf->WriteHTML($document_html);
532
        } catch (MpdfException $e) {
533
            error_log($e);
534
        }
535
536
        if (empty($pdf_name)) {
537
            $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
538
        } else {
539
            $pdf_name = api_replace_dangerous_char($pdf_name);
540
            $output_file = $pdf_name.'.pdf';
541
        }
542
543
        if ($outputMode === 'F') {
544
            $output_file = api_get_path(SYS_ARCHIVE_PATH).$output_file;
545
        }
546
547
        if ($saveInFile) {
548
            $fileToSave = !empty($fileToSave) ? $fileToSave : api_get_path(SYS_ARCHIVE_PATH).uniqid();
549
            @$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

549
            /** @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...
550
                $fileToSave,
551
                $outputMode
552
            ); // F to save the pdf in a file
553
        } else {
554
            @$this->pdf->Output(
555
                $output_file,
556
                $outputMode
557
            ); // F to save the pdf in a file
558
        }
559
560
        if ($outputMode != 'F') {
561
            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...
562
        }
563
564
        return $output_file;
565
    }
566
567
    /**
568
     * Gets the watermark from the platform or a course.
569
     *
570
     * @param   string  course code (optional)
571
     * @param   mixed   web path of the watermark image, false if there is nothing to return
572
     *
573
     * @return string
574
     */
575
    public static function get_watermark($course_code = null)
576
    {
577
        $web_path = false;
578
        $urlId = api_get_current_access_url_id();
579
        if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') === 'true') {
580
            $course_info = api_get_course_info($course_code);
581
            // course path
582
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
583
            if (file_exists($store_path)) {
584
                $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
585
            }
586
        } else {
587
            // course path
588
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
589
            if (file_exists($store_path)) {
590
                $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
591
            }
592
        }
593
594
        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...
595
    }
596
597
    /**
598
     * Deletes the watermark from the Platform or Course.
599
     *
600
     * @param string $course_code course code (optional)
601
     * @param   mixed   web path of the watermark image, false if there is nothing to return
602
     *
603
     * @return bool
604
     */
605
    public function delete_watermark($course_code = null)
606
    {
607
        $urlId = api_get_current_access_url_id();
608
        if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') == 'true') {
609
            $course_info = api_get_course_info($course_code);
610
            // course path
611
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/'.$urlId.'_pdf_watermark.png';
612
        } else {
613
            // course path
614
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
615
        }
616
        if (file_exists($store_path)) {
617
            unlink($store_path);
618
619
            return true;
620
        }
621
622
        return false;
623
    }
624
625
    /**
626
     * Uploads the pdf watermark in the main/default_course_document directory or in the course directory.
627
     *
628
     * @param string $filename    filename
629
     * @param string $source_file path of the file
630
     * @param string $course_code
631
     *
632
     * @return mixed web path of the file if sucess, false otherwise
633
     */
634
    public function upload_watermark($filename, $source_file, $course_code = null)
635
    {
636
        $urlId = api_get_current_access_url_id();
637
        if (!empty($course_code) && api_get_setting('pdf_export_watermark_by_course') == 'true') {
638
            $course_info = api_get_course_info($course_code);
639
            $store_path = api_get_path(SYS_COURSE_PATH).$course_info['path']; // course path
640
            $web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/pdf_watermark.png';
641
        } else {
642
            $store_path = api_get_path(SYS_CODE_PATH).'default_course_document/images'; // course path
643
            $web_path = api_get_path(WEB_CODE_PATH).'default_course_document/images/'.$urlId.'_pdf_watermark.png';
644
        }
645
        $course_image = $store_path.'/'.$urlId.'_pdf_watermark.png';
646
647
        if (file_exists($course_image)) {
648
            @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

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

820
                        /** @scrutinizer ignore-call */ 
821
                        strcode2utf($watermark_text),
Loading history...
821
                        0.1
822
                    );
823
                    $this->pdf->showWatermarkText = true;
824
                }
825
            }
826
827
            if (empty($this->custom_header)) {
828
                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

828
                self::/** @scrutinizer ignore-call */ 
829
                      set_header($courseInfo);
Loading history...
829
            } else {
830
                $this->pdf->SetHTMLHeader($this->custom_header, 'E');
831
                $this->pdf->SetHTMLHeader($this->custom_header, 'O');
832
            }
833
834
            if (empty($this->custom_footer)) {
835
                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

835
                self::/** @scrutinizer ignore-call */ 
836
                      set_footer();
Loading history...
836
            } else {
837
                $this->pdf->SetHTMLFooter($this->custom_footer);
838
            }
839
        }
840
    }
841
842
    /**
843
     * Generate a PDF file from $html in SYS_APP_PATH.
844
     *
845
     * @param string $html     PDF content
846
     * @param string $fileName File name
847
     * @param string $dest     Optional. Directory to move file
848
     *
849
     * @return string The PDF path
850
     */
851
    public function exportFromHtmlToFile($html, $fileName, $dest = null)
852
    {
853
        $this->template = $this->template ?: new Template('', false, false, false, false, false, false);
854
855
        $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

855
        /** @scrutinizer ignore-call */ 
856
        $pdfPath = self::content_to_pdf(
Loading history...
856
            $html,
857
            api_get_print_css(),
858
            $fileName,
859
            $this->params['course_code'],
860
            'F'
861
        );
862
863
        if (!$dest) {
864
            return $pdfPath;
865
        }
866
867
        move($pdfPath, $dest);
868
869
        return $dest.basename($pdfPath);
870
    }
871
872
    /**
873
     * Create a PDF and save it into the documents area.
874
     *
875
     * @param string $htmlContent HTML Content
876
     * @param string $fileName    The file name
877
     * @param int    $courseId    The course ID
878
     * @param int    $sessionId   Optional. The session ID
879
     */
880
    public function exportFromHtmlToDocumentsArea(
881
        $htmlContent,
882
        $fileName,
883
        $courseId,
884
        $sessionId = 0
885
    ) {
886
        $userId = api_get_user_id();
887
        $courseInfo = api_get_course_info_by_id($courseId);
888
        $courseDirectory = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document/';
889
890
        $docPath = $this->exportFromHtmlToFile(
891
            $htmlContent,
892
            $fileName,
893
            $courseDirectory
894
        );
895
896
        $docId = add_document(
897
            $courseInfo,
898
            str_replace($courseDirectory, '/', $docPath),
899
            'file',
900
            filesize($docPath),
901
            $fileName,
902
            null,
903
            false,
904
            true,
905
            null,
906
            $sessionId,
907
            $userId
908
        );
909
910
        api_item_property_update(
911
            $courseInfo,
912
            TOOL_DOCUMENT,
913
            $docId,
914
            'DocumentAdded',
915
            $userId
916
        );
917
918
        Display::addFlash(Display::return_message(get_lang('ItemAdded')));
919
    }
920
921
    /**
922
     * @param string $theme
923
     * @param bool   $fullPage
924
     *
925
     * @throws MpdfException
926
     */
927
    public function setBackground($theme, $fullPage = false)
928
    {
929
        $themeName = empty($theme) ? api_get_visual_theme() : $theme;
930
        $themeDir = \Template::getThemeDir($themeName);
931
        $customLetterhead = $themeDir.'images/letterhead.png';
932
        $urlPathLetterhead = api_get_path(SYS_CSS_PATH).$customLetterhead;
933
934
        if (file_exists($urlPathLetterhead)) {
935
            $urlWebLetterhead = 'url('.api_get_path(WEB_CSS_PATH).$customLetterhead.')';
936
        } else {
937
            $urlWebLetterhead = 'url('.api_get_path(WEB_CSS_PATH).'themes/chamilo/images/letterhead.png)';
938
        }
939
940
        if ($fullPage) {
941
            $this->pdf->SetDisplayMode('fullpage');
942
            $this->pdf->SetDefaultBodyCSS('background', $urlWebLetterhead);
943
            $this->pdf->SetDefaultBodyCSS('background-image-resize', '6');
944
        }
945
    }
946
947
    /**
948
     * Fix images source paths to allow export to pdf.
949
     *
950
     * @param string $documentHtml
951
     * @param string $dirName
952
     *
953
     * @return string
954
     */
955
    private static function fixImagesPaths($documentHtml, array $courseInfo, $dirName = '')
956
    {
957
        $documentHtml = '<?xml encoding="utf-8" ?>'.$documentHtml;
958
        $doc = new DOMDocument();
959
        @$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

959
        /** @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...
960
961
        $elements = $doc->getElementsByTagName('img');
962
963
        if (empty($elements)) {
964
            return $doc->saveHTML();
965
        }
966
967
        $protocol = api_get_protocol();
968
        $sysCodePath = api_get_path(SYS_CODE_PATH);
969
        $sysCoursePath = api_get_path(SYS_COURSE_PATH);
970
        $sysUploadPath = api_get_path(SYS_UPLOAD_PATH);
971
972
        $documentPath = $courseInfo ? $sysCoursePath.$courseInfo['path'].'/document/' : '';
973
974
        /** @var \DOMElement $element */
975
        foreach ($elements as $element) {
976
            $src = $element->getAttribute('src');
977
            $src = trim($src);
978
979
            if (strpos($src, $protocol) !== false) {
980
                continue;
981
            }
982
983
            // It's a reference to a file in the system, do not change it
984
            if (file_exists($src)) {
985
                continue;
986
            }
987
988
            if (strpos($src, '/main/default_course_document') === 0) {
989
                $element->setAttribute(
990
                    'src',
991
                    str_replace('/main/default_course_document', $sysCodePath.'default_course_document', $src)
992
                );
993
                continue;
994
            }
995
996
            if (strpos($src, '/main/img') === 0) {
997
                $element->setAttribute(
998
                    'src',
999
                    str_replace('/main/img/', $sysCodePath.'img/', $src)
1000
                );
1001
                continue;
1002
            }
1003
1004
            if (strpos($src, '/app/upload/') === 0) {
1005
                $element->setAttribute(
1006
                    'src',
1007
                    str_replace('/app/upload/', $sysUploadPath, $src)
1008
                );
1009
                continue;
1010
            }
1011
1012
            if (empty($courseInfo)) {
1013
                continue;
1014
            }
1015
1016
            if (api_get_path(REL_PATH) != '/') {
1017
                $oldSrcFixed = str_replace(
1018
                    api_get_path(REL_PATH).'courses/'.$courseInfo['path'].'/document/',
1019
                    '',
1020
                    $src
1021
                );
1022
1023
                // Try with the dirname if exists
1024
                if ($oldSrcFixed == $src) {
1025
                    if (file_exists($dirName.'/'.$src)) {
1026
                        $documentPath = '';
1027
                        $oldSrcFixed = $dirName.'/'.$src;
1028
                    }
1029
                }
1030
            } else {
1031
                if (strpos($src, 'courses/'.$courseInfo['path'].'/document/') !== false) {
1032
                    $oldSrcFixed = str_replace('courses/'.$courseInfo['path'].'/document/', '', $src);
1033
                } else {
1034
                    // Try with the dirname if exists
1035
                    if (file_exists($dirName.'/'.$src)) {
1036
                        $documentPath = '';
1037
                        $oldSrcFixed = $dirName.'/'.$src;
1038
                    } else {
1039
                        $documentPath = '';
1040
                        $oldSrcFixed = $src;
1041
                    }
1042
                }
1043
            }
1044
1045
            $element->setAttribute('src', $documentPath.$oldSrcFixed);
1046
        }
1047
1048
        return $doc->saveHTML();
1049
    }
1050
}
1051