Passed
Push — developer ( 0a5a9d...dbf062 )
by Radosław
37:42 queued 18:16
created

Chromium::output()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 26
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 26
rs 8.6506
c 0
b 0
f 0
cc 7
nc 10
nop 2
1
<?php
2
/**
3
 * Chromium driver file for PDF generation.
4
 *
5
 * @see https://github.com/chrome-php/chrome
6
 * @see https://www.chromium.org
7
 *
8
 * @package App\Pdf
9
 *
10
 * @copyright YetiForce S.A.
11
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
12
 * @author	  Mariusz Krzaczkowski <[email protected]>
13
 */
14
15
namespace App\Pdf\Drivers;
16
17
/**
18
 * Chromium driver class for PDF generation.
19
 */
20
class Chromium extends Base
21
{
22
	/** {@inheritdoc} */
23
	const DRIVER_NAME = 'LBL_CHROMIUM_PDF';
24
25
	/** @var float Millimeter to inch converter */
26
	const MM_TO_IN = 0.0393701;
27
28
	/** @var float Millimeter to pixel converter */
29
	const MM_TO_PX = 3.7795275591;
30
31
	const PAGE_FORMATS = [
32
		// ISO 216 A Series + 2 SIS 014711 extensions
33
		'A0' => [33.11, 46.81], // = (  841 x 1189 ) mm  = ( 33.11 x 46.81 ) in
34
		'A1' => [23.39, 33.11], // = (  594 x 841  ) mm  = ( 23.39 x 33.11 ) in
35
		'A2' => [16.54, 23.39], // = (  420 x 594  ) mm  = ( 16.54 x 23.39 ) in
36
		'A3' => [11.69, 16.54], // = (  297 x 420  ) mm  = ( 11.69 x 16.54 ) in
37
		'A4' => [8.27, 11.69], // = (  210 x 297  ) mm  = (  8.27 x 11.69 ) in
38
		'A5' => [5.83, 8.27], // = (  148 x 210  ) mm  = (  5.83 x 8.27  ) in
39
		'A6' => [4.13, 5.83], // = (  105 x 148  ) mm  = (  4.13 x 5.83  ) in
40
		'A7' => [2.91, 4.13], // = (   74 x 105  ) mm  = (  2.91 x 4.13  ) in
41
		'A8' => [2.05, 2.91], // = (   52 x 74   ) mm  = (  2.05 x 2.91  ) in
42
		'A9' => [1.46, 2.05], // = (   37 x 52   ) mm  = (  1.46 x 2.05  ) in
43
		'A10' => [1.02, 1.46], // = (   26 x 37   ) mm  = (  1.02 x 1.46  ) in
44
		// ISO 216 B Series + 2 SIS 014711 extensions
45
		'B0' => [39.37, 55.67], // = ( 1000 x 1414 ) mm  = ( 39.37 x 55.67 ) in
46
		'B1' => [27.83, 39.37], // = (  707 x 1000 ) mm  = ( 27.83 x 39.37 ) in
47
		'B2' => [19.69, 27.83], // = (  500 x 707  ) mm  = ( 19.69 x 27.83 ) in
48
		'B3' => [13.90, 19.69], // = (  353 x 500  ) mm  = ( 13.90 x 19.69 ) in
49
		'B4' => [9.84, 13.90], // = (  250 x 353  ) mm  = (  9.84 x 13.90 ) in
50
		'B5' => [6.93, 9.84], // = (  176 x 250  ) mm  = (  6.93 x 9.84  ) in
51
		'B6' => [4.92, 6.93], // = (  125 x 176  ) mm  = (  4.92 x 6.93  ) in
52
		'B7' => [3.46, 4.92], // = (   88 x 125  ) mm  = (  3.46 x 4.92  ) in
53
		'B8' => [2.44, 3.46], // = (   62 x 88   ) mm  = (  2.44 x 3.46  ) in
54
		'B9' => [1.73, 2.44], // = (   44 x 62   ) mm  = (  1.73 x 2.44  ) in
55
		'B10' => [1.22, 1.73], // = (   31 x 44   ) mm  = (  1.22 x 1.73  ) in
56
		// ISO 216 C Series + 2 SIS 014711 extensions + 5 EXTENSION
57
		'C0' => [36.10, 51.06], // = (  917 x 1297 ) mm  = ( 36.10 x 51.06 ) in
58
		'C1' => [25.51, 36.10], // = (  648 x 917  ) mm  = ( 25.51 x 36.10 ) in
59
		'C2' => [18.03, 25.51], // = (  458 x 648  ) mm  = ( 18.03 x 25.51 ) in
60
		'C3' => [12.76, 18.03], // = (  324 x 458  ) mm  = ( 12.76 x 18.03 ) in
61
		'C4' => [9.02, 12.76], // = (  229 x 324  ) mm  = (  9.02 x 12.76 ) in
62
		'C5' => [6.38, 9.02], // = (  162 x 229  ) mm  = (  6.38 x 9.02  ) in
63
		'C6' => [4.49, 6.38], // = (  114 x 162  ) mm  = (  4.49 x 6.38  ) in
64
		'C7' => [3.19, 4.49], // = (   81 x 114  ) mm  = (  3.19 x 4.49  ) in
65
		'C8' => [2.24, 3.19], // = (   57 x 81   ) mm  = (  2.24 x 3.19  ) in
66
		'C9' => [1.57, 2.24], // = (   40 x 57   ) mm  = (  1.57 x 2.24  ) in
67
		'C10' => [1.10, 1.57], // = (   28 x 40   ) mm  = (  1.10 x 1.57  ) in
68
		// ISO Press
69
		'RA0' => [33.86, 48.03], // = (  860 x 1220 ) mm  = ( 33.86 x 48.03 ) in
70
		'RA1' => [24.02, 33.86], // = (  610 x 860  ) mm  = ( 24.02 x 33.86 ) in
71
		'RA2' => [16.93, 24.02], // = (  430 x 610  ) mm  = ( 16.93 x 24.02 ) in
72
		'RA3' => [12.01, 16.93], // = (  305 x 430  ) mm  = ( 12.01 x 16.93 ) in
73
		'RA4' => [8.46, 12.01], // = (  215 x 305  ) mm  = (  8.46 x 12.01 ) in
74
		'SRA0' => [35.43, 50.39], // = (  900 x 1280 ) mm  = ( 35.43 x 50.39 ) in
75
		'SRA1' => [25.20, 35.43], // = (  640 x 900  ) mm  = ( 25.20 x 35.43 ) in
76
		'SRA2' => [17.72, 25.20], // = (  450 x 640  ) mm  = ( 17.72 x 25.20 ) in
77
		'SRA3' => [12.60, 17.72], // = (  320 x 450  ) mm  = ( 12.60 x 17.72 ) in
78
		'SRA4' => [8.86, 12.60], // = (  225 x 320  ) mm  = (  8.86 x 12.60 ) in
79
		// German DIN 476
80
		'4A0' => [66.22, 93.62], // = ( 1682 x 2378 ) mm  = ( 66.22 x 93.62 ) in
81
		'2A0' => [46.81, 66.22], // = ( 1189 x 1682 ) mm  = ( 46.81 x 66.22 ) in
82
		// Traditional 'Loose' North American Paper Sizes
83
		'LETTER' => [8.50, 11.00], // = (  216 x 279  ) mm  = (  8.50 x 11.00 ) in
84
		'LEGAL' => [8.50, 14.00], // = (  216 x 356  ) mm  = (  8.50 x 14.00 ) in
85
		'LEDGER' => [17.00, 11.00], // = (  432 x 279  ) mm  = ( 17.00 x 11.00 ) in
86
		'TABLOID' => [11.00, 17.00], // = (  279 x 432  ) mm  = ( 11.00 x 17.00 ) in
87
		'EXECUTIVE' => [7.25, 10.50], // = (  184 x 267  ) mm  = (  7.25 x 10.50 ) in
88
		'FOLIO' => [8.50, 13.00], // = (  216 x 330  ) mm  = (  8.50 x 13.00 ) in
89
		'B' => [5.04, 7.80],  // = (  128 x 198  ) mm  = (  5.04 x 7.80 ) in
90
		'A' => [4.37, 7.00], // = (  111 x 178  ) mm  = (  4.37 x 7.00 ) in
91
		'DEMY' => [8.50, 5.31], // = (  135 x 216  ) mm  = (  8.50 x 5.31 ) in
92
		'ROYAL' => [6.02, 9.21], // = (  153 x 234  ) mm  = (  6.02 x 9.21 ) in
93
	];
94
95
	/** @var string Default font. */
96
	protected $font = '"Times New Roman", Times, serif';
97
98
	/** @var int Default font size (px). */
99
	protected $fontSize = '16px';
100
101
	/** @var \HeadlessChromium\Browser\ProcessAwareBrowser PDF generator instance. */
102
	protected $pdf;
103
104
	/** @var string Pdf HTML content. */
105
	protected $pdfHtml;
106
107
	/** @var array Pdf options. */
108
	protected $pdfOptions = [];
109
110
	/** {@inheritdoc} */
111
	public static function isActive(): bool
112
	{
113
		$status = false;
114
		if (\App\YetiForce\Register::isRegistered() && class_exists('HeadlessChromium\BrowserFactory')) {
115
			try {
116
				if (!empty(\Config\Components\Pdf::$chromiumBinaryPath)) {
0 ignored issues
show
Bug introduced by
The type Config\Components\Pdf was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
117
					$browserFactory = new \HeadlessChromium\BrowserFactory(\Config\Components\Pdf::$chromiumBinaryPath ?? '');
118
					$browser = $browserFactory->createBrowser(\Config\Components\Pdf::$chromiumBrowserOptions ?? []);
119
					$status = $browser instanceof \HeadlessChromium\Browser;
120
				}
121
			} catch (\Throwable $th) {
122
				\App\Log::warning($th->__toString(), __CLASS__);
123
			}
124
		}
125
		return $status;
126
	}
127
128
	/**
129
	 * Constructor.
130
	 */
131
	public function __construct()
132
	{
133
		$this->setInputCharset(\App\Config::main('default_charset', 'UTF-8'));
134
		$browserFactory = new \HeadlessChromium\BrowserFactory(\Config\Components\Pdf::$chromiumBinaryPath ?? '');
135
		$this->pdf = $browserFactory->createBrowser(\Config\Components\Pdf::$chromiumBrowserOptions ?? []);
136
		$this->pdfOptions = [
137
			'printBackground' => true,
138
			'headerTemplate' => ' ',
139
			'footerTemplate' => ' ',
140
		];
141
		$this->setTopMargin($this->defaultMargins['top']);
142
		$this->setBottomMargin($this->defaultMargins['bottom']);
143
		$this->setLeftMargin($this->defaultMargins['left']);
144
		$this->setRightMargin($this->defaultMargins['right']);
145
	}
146
147
	/** {@inheritdoc} */
148
	public function setTopMargin(float $margin)
149
	{
150
		$this->pdfOptions['marginTop'] = self::MM_TO_IN * $margin;
151
		return $this;
152
	}
153
154
	/** {@inheritdoc} */
155
	public function setBottomMargin(float $margin)
156
	{
157
		$this->pdfOptions['marginBottom'] = self::MM_TO_IN * $margin;
158
		return $this;
159
	}
160
161
	/**
162
	 * Set left margin.
163
	 *
164
	 * @param float $margin
165
	 */
166
	public function setLeftMargin(float $margin)
167
	{
168
		$this->pdfOptions['marginLeft'] = self::MM_TO_IN * $margin;
169
		return $this;
170
	}
171
172
	/** {@inheritdoc} */
173
	public function setRightMargin(float $margin)
174
	{
175
		$this->pdfOptions['marginRight'] = self::MM_TO_IN * $margin;
176
		return $this;
177
	}
178
179
	/** {@inheritdoc} */
180
	public function setHeaderMargin(float $margin)
181
	{
182
		$this->headerMargin = self::MM_TO_PX * $margin;
183
		return $this;
184
	}
185
186
	/** {@inheritdoc} */
187
	public function setFooterMargin(float $margin)
188
	{
189
		$this->footerMargin = self::MM_TO_PX * $margin;
190
		return $this;
191
	}
192
193
	/** {@inheritdoc} */
194
	public function setMargins(array $margins)
195
	{
196
		$this->setTopMargin($margins['top'] ?? $this->defaultMargins['top']);
197
		$this->setBottomMargin($margins['bottom'] ?? $this->defaultMargins['bottom']);
198
		$this->setLeftMargin($margins['left'] ?? $this->defaultMargins['left']);
199
		$this->setRightMargin($margins['right'] ?? $this->defaultMargins['right']);
200
		$this->setHeaderMargin($margins['header'] ?? $this->defaultMargins['header']);
201
		$this->setFooterMargin($margins['footer'] ?? $this->defaultMargins['footer']);
202
		return $this;
203
	}
204
205
	/** {@inheritdoc} */
206
	public function setPageSize(string $format, string $orientation = null)
207
	{
208
		$this->pdfOptions['paperWidth'] = self::PAGE_FORMATS[$format][0];
209
		$this->pdfOptions['paperHeight'] = self::PAGE_FORMATS[$format][1];
210
		if ($orientation) {
211
			$this->pdfOptions['landscape'] = 'L' === $orientation;
212
		}
213
		return $this;
214
	}
215
216
	/** {@inheritdoc} */
217
	public function setTitle(string $title)
218
	{
219
		// does not support this feature
220
		return $this;
221
	}
222
223
	/** {@inheritdoc} */
224
	public function setAuthor(string $author)
225
	{
226
		// does not support this feature
227
		return $this;
228
	}
229
230
	/** {@inheritdoc} */
231
	public function setCreator(string $creator)
232
	{
233
		// does not support this feature
234
		return $this;
235
	}
236
237
	/** {@inheritdoc} */
238
	public function setSubject(string $subject)
239
	{
240
		// does not support this feature
241
		return $this;
242
	}
243
244
	/** {@inheritdoc} */
245
	public function setKeywords(array $keywords)
246
	{
247
		// does not support this feature
248
		return $this;
249
	}
250
251
	/** {@inheritdoc} */
252
	public function setHeader(string $headerHtml)
253
	{
254
		$this->header = trim($headerHtml);
255
		return $this;
256
	}
257
258
	/** {@inheritdoc} */
259
	public function setFooter(string $footerHtml)
260
	{
261
		$this->footer = trim($footerHtml);
262
		return $this;
263
	}
264
265
	/**
266
	 * Get PDF options.
267
	 *
268
	 * @return array
269
	 */
270
	protected function getPdfOptions(): array
271
	{
272
		return $this->pdfOptions;
273
	}
274
275
	/**
276
	 * Get PDF HTML.
277
	 *
278
	 * @return string
279
	 */
280
	protected function getPdfHtml(): string
281
	{
282
		return $this->pdfHtml;
283
	}
284
285
	/**
286
	 * Write html.
287
	 *
288
	 * @return $this
289
	 */
290
	public function writeHTML()
291
	{
292
		if ($this->header) {
293
			$this->pdfOptions['headerTemplate'] = $this->wrapHeaderContent($this->header);
294
		}
295
		if ($this->footer) {
296
			$this->pdfOptions['footerTemplate'] = $this->wrapFooterContent($this->footer);
297
		} else {
298
			// Modification of the following condition will violate the license!
299
			if (!\App\YetiForce\Shop::check('YetiForceDisableBranding')) {
300
				$this->pdfOptions['footerTemplate'] = '<div style="position: fixed; font-size:6px; margin-left: 20px; text-align: left; z-index: 9999999;color: black !important">Powered by YetiForce</div>';
301
				$this->pdfOptions['marginBottom'] = 0.4;
302
			}
303
			// Modification of the following condition will violate the license!
304
		}
305
		if (!empty($this->pdfOptions['headerTemplate']) || !empty($this->pdfOptions['footerTemplate'])) {
306
			$this->pdfOptions['displayHeaderFooter'] = true;
307
		}
308
		$watermark = $this->watermark ? $this->wrapWatermark($this->template->parseVariables($this->watermark)) : '';
309
		$this->pdfHtml = $this->wrapContent('<div id="body">' . $watermark . '<div id="content">' . $this->getBody() . '</div></div>');
310
		return $this;
311
	}
312
313
	/**
314
	 * Wrap body content.
315
	 *
316
	 * @param string $content
317
	 *
318
	 * @return string
319
	 */
320
	protected function wrapContent(string $content): string
321
	{
322
		$style = 'body{' . ($this->font ? ('font-family: ' . $this->font . ';') : '') . ($this->fontSize ? ('font-size: ' . $this->fontSize . ';') : '') . ';}';
323
		if ($this->template->get('styles')) {
324
			$style .= PHP_EOL . str_replace(['<', '>'], '', $this->template->get('styles'));
325
		}
326
		$content = str_replace(['{p}', '{a}'], ['<span class="pageNumber"></span>', '<span class="totalPages"></span>'], $content);
327
		return '<!DOCTYPE html><html lang="' . ($this->template->get('language') ?: \App\Language::getShortLanguageName()) . '">
328
			<head>
329
				<meta charset="' . $this->charset . '" />
330
				<style>' . $style . '</style>
331
			</head>
332
			<body style="margin: 0; padding: 0;">' . $content . '</body></html>';
333
	}
334
335
	/**
336
	 * Wrap header content.
337
	 *
338
	 * @param string $content
339
	 *
340
	 * @return string
341
	 */
342
	public function wrapHeaderContent(string $content): string
343
	{
344
		$style = ";padding-top: {$this->headerMargin}px; padding-left: {$this->pdfOptions['marginLeft']}mm; padding-right: {$this->pdfOptions['marginRight']}mm;";
345
		return $this->wrapContent('<div id="header" style="color-adjust: exact; -webkit-print-color-adjust: exact; print-color-adjust: exact; ' . $style . '">' . $content . '</div>');
346
	}
347
348
	/**
349
	 * Wrap footer content.
350
	 *
351
	 * @param string $content
352
	 *
353
	 * @return string
354
	 */
355
	public function wrapFooterContent(string $content): string
356
	{
357
		$style = "padding-bottom: {$this->footerMargin}px; padding-left: {$this->pdfOptions['marginLeft']}mm; padding-right: {$this->pdfOptions['marginRight']}mm;";
358
		$content = '<div id="footer" style="color-adjust: exact; -webkit-print-color-adjust: exact; print-color-adjust: exact; ' . $style . '">' . $content;
359
		// Modification of the following condition will violate the license!
360
		if (!\App\YetiForce\Shop::check('YetiForceDisableBranding')) {
361
			$content .= '<div style="position: fixed; font-size:6px; margin-left: 20px; text-align: left; z-index: 9999999;color: black !important">Powered by YetiForce</div>';
362
			if ($this->pdfOptions['marginBottom'] < (float) 0.4) {
363
				$this->pdfOptions['marginBottom'] = 0.4;
364
			}
365
		}
366
		// Modification of the following condition will violate the license!
367
		$content .= '</div>';
368
		if (empty(trim($this->pdfOptions['headerTemplate']))) {
369
			return $this->wrapContent($content);
370
		}
371
		return str_replace(['{p}', '{a}'], ['<span class="pageNumber"></span>', '<span class="totalPages"></span>'], $content);
372
	}
373
374
	/**
375
	 * Wrap watermark.
376
	 *
377
	 * @param string $watermarkContent
378
	 *
379
	 * @return string
380
	 */
381
	public function wrapWatermark(string $watermarkContent): string
382
	{
383
		return '<div id="watermark" style="position: fixed; z-index: -1; text-align:center; vertical-align: middle; width: 100%; height: 100%;">' . $watermarkContent . '</div>';
384
	}
385
386
	/** {@inheritdoc} */
387
	public function loadWatermark()
388
	{
389
		$this->watermark = '';
390
		if (self::WATERMARK_TYPE_IMAGE === $this->template->get('watermark_type') && '' !== trim($this->template->get('watermark_image'))) {
391
			if ($this->template->get('watermark_image') && file_exists(ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . $this->template->get('watermark_image')) && ($base64 = \App\Fields\File::getImageBaseData(ROOT_DIRECTORY . \DIRECTORY_SEPARATOR . $this->template->get('watermark_image')))) {
392
				$this->watermark = '<img id="watermark-image" src="' . $base64 . '" style="max-width: 100%; max-height: 100%;"/>';
393
			}
394
		} elseif (self::WATERMARK_TYPE_TEXT === $this->template->get('watermark_type') && '' !== trim($this->template->get('watermark_text'))) {
395
			$this->watermark = '<div id="watermark-text" style="position: relative; top: 50%; transform: rotate(-' . $this->template->get('watermark_angle') . 'deg);">' . $this->template->get('watermark_text') . '</div>';
396
		}
397
		return $this;
398
	}
399
400
	/** {@inheritdoc} */
401
	public function output($fileName = '', $mode = 'D'): void
402
	{
403
		if (empty($fileName)) {
404
			$fileName = ($this->getFileName() ?: time()) . '.pdf';
405
		}
406
		$this->writeHTML();
407
		$page = $this->pdf->createPage();
408
		$tempFileName = \App\Fields\File::getTmpPath() . \App\Encryption::generatePassword(15) . '.html';
409
		file_put_contents($tempFileName, $this->getPdfHtml());
410
		$page->navigate('file://' . $tempFileName)->waitForNavigation(\HeadlessChromium\Page::LOAD, 60000);
411
		$pdf = $page->pdf($this->getPdfOptions());
412
		unlink($tempFileName);
413
		if (!\in_array($mode, ['I', 'D', 'AttachAndOutput'])) {
414
			$pdf->saveToFile($fileName);
415
			return;
416
		}
417
		if ('AttachAndOutput' === $mode) {
418
			$pdf->saveToFile($fileName);
419
			$fileName = ($this->getFileName() ?: time()) . '.pdf';
420
		}
421
		$destination = 'I' === $mode ? 'inline' : 'attachment';
422
		header('accept-charset: utf-8');
423
		header('content-type: application/pdf; charset=utf-8');
424
		$basename = \App\Fields\File::sanitizeUploadFileName($fileName);
425
		header("content-disposition: {$destination}; filename=\"{$basename}\"");
426
		echo base64_decode($pdf->getBase64(60000));
427
	}
428
}
429