Completed
Push — develop ( 1cec98...8d7602 )
by Adrien
24:39
created

Html::setUseInlineCss()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 1
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Cell\Cell;
7
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
8
use PhpOffice\PhpSpreadsheet\Chart\Chart;
9
use PhpOffice\PhpSpreadsheet\RichText\RichText;
10
use PhpOffice\PhpSpreadsheet\RichText\Run;
11
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
12
use PhpOffice\PhpSpreadsheet\Shared\File;
13
use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
14
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
15
use PhpOffice\PhpSpreadsheet\Spreadsheet;
16
use PhpOffice\PhpSpreadsheet\Style\Alignment;
17
use PhpOffice\PhpSpreadsheet\Style\Border;
18
use PhpOffice\PhpSpreadsheet\Style\Borders;
19
use PhpOffice\PhpSpreadsheet\Style\Fill;
20
use PhpOffice\PhpSpreadsheet\Style\Font;
21
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
22
use PhpOffice\PhpSpreadsheet\Style\Style;
23
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
24
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
25
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
26
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
27
28
class Html extends BaseWriter
29
{
30
    /**
31
     * Spreadsheet object.
32
     *
33
     * @var Spreadsheet
34
     */
35
    protected $spreadsheet;
36
37
    /**
38
     * Sheet index to write.
39
     *
40
     * @var int
41
     */
42
    private $sheetIndex = 0;
43
44
    /**
45
     * Images root.
46
     *
47
     * @var string
48
     */
49
    private $imagesRoot = '';
50
51
    /**
52
     * embed images, or link to images.
53
     *
54
     * @var bool
55
     */
56
    private $embedImages = false;
57
58
    /**
59
     * Use inline CSS?
60
     *
61
     * @var bool
62
     */
63
    private $useInlineCss = false;
64
65
    /**
66
     * Array of CSS styles.
67
     *
68
     * @var array
69
     */
70
    private $cssStyles;
71
72
    /**
73
     * Array of column widths in points.
74
     *
75
     * @var array
76
     */
77
    private $columnWidths;
78
79
    /**
80
     * Default font.
81
     *
82
     * @var Font
83
     */
84
    private $defaultFont;
85
86
    /**
87
     * Flag whether spans have been calculated.
88
     *
89
     * @var bool
90
     */
91
    private $spansAreCalculated = false;
92
93
    /**
94
     * Excel cells that should not be written as HTML cells.
95
     *
96
     * @var array
97
     */
98
    private $isSpannedCell = [];
99
100
    /**
101
     * Excel cells that are upper-left corner in a cell merge.
102
     *
103
     * @var array
104
     */
105
    private $isBaseCell = [];
106
107
    /**
108
     * Excel rows that should not be written as HTML rows.
109
     *
110
     * @var array
111
     */
112
    private $isSpannedRow = [];
113
114
    /**
115
     * Is the current writer creating PDF?
116
     *
117
     * @var bool
118
     */
119
    protected $isPdf = false;
120
121
    /**
122
     * Generate the Navigation block.
123
     *
124
     * @var bool
125
     */
126
    private $generateSheetNavigationBlock = true;
127
128
    /**
129
     * Create a new HTML.
130
     *
131
     * @param Spreadsheet $spreadsheet
132
     */
133 11
    public function __construct(Spreadsheet $spreadsheet)
134
    {
135 11
        $this->spreadsheet = $spreadsheet;
136 11
        $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont();
137 11
    }
138
139
    /**
140
     * Save Spreadsheet to file.
141
     *
142
     * @param string $pFilename
143
     *
144
     * @throws WriterException
145
     */
146 3
    public function save($pFilename)
147
    {
148
        // garbage collect
149 3
        $this->spreadsheet->garbageCollect();
150
151 3
        $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
152 3
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
153 3
        $saveArrayReturnType = Calculation::getArrayReturnType();
154 3
        Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
155
156
        // Build CSS
157 3
        $this->buildCSS(!$this->useInlineCss);
158
159
        // Open file
160 3
        $fileHandle = fopen($pFilename, 'wb+');
161 3
        if ($fileHandle === false) {
162
            throw new WriterException("Could not open file $pFilename for writing.");
163
        }
164
165
        // Write headers
166 3
        fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss));
167
168
        // Write navigation (tabs)
169 3
        if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) {
170 3
            fwrite($fileHandle, $this->generateNavigation());
171
        }
172
173
        // Write data
174 3
        fwrite($fileHandle, $this->generateSheetData());
175
176
        // Write footer
177 3
        fwrite($fileHandle, $this->generateHTMLFooter());
178
179
        // Close file
180 3
        fclose($fileHandle);
181
182 3
        Calculation::setArrayReturnType($saveArrayReturnType);
183 3
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
184 3
    }
185
186
    /**
187
     * Map VAlign.
188
     *
189
     * @param string $vAlign Vertical alignment
190
     *
191
     * @return string
192
     */
193 6
    private function mapVAlign($vAlign)
194
    {
195
        switch ($vAlign) {
196 6
            case Alignment::VERTICAL_BOTTOM:
197 6
                return 'bottom';
198 4
            case Alignment::VERTICAL_TOP:
199
                return 'top';
200 4
            case Alignment::VERTICAL_CENTER:
201
            case Alignment::VERTICAL_JUSTIFY:
202 4
                return 'middle';
203
            default:
204
                return 'baseline';
205
        }
206
    }
207
208
    /**
209
     * Map HAlign.
210
     *
211
     * @param string $hAlign Horizontal alignment
212
     *
213
     * @return false|string
214
     */
215 6
    private function mapHAlign($hAlign)
216
    {
217
        switch ($hAlign) {
218 6
            case Alignment::HORIZONTAL_GENERAL:
219 6
                return false;
220 5
            case Alignment::HORIZONTAL_LEFT:
221 4
                return 'left';
222 5
            case Alignment::HORIZONTAL_RIGHT:
223 4
                return 'right';
224 5
            case Alignment::HORIZONTAL_CENTER:
225 4
            case Alignment::HORIZONTAL_CENTER_CONTINUOUS:
226 1
                return 'center';
227 4
            case Alignment::HORIZONTAL_JUSTIFY:
228 4
                return 'justify';
229
            default:
230
                return false;
231
        }
232
    }
233
234
    /**
235
     * Map border style.
236
     *
237
     * @param int $borderStyle Sheet index
238
     *
239
     * @return string
240
     */
241 6
    private function mapBorderStyle($borderStyle)
242
    {
243
        switch ($borderStyle) {
244 6
            case Border::BORDER_NONE:
245 6
                return 'none';
246 4
            case Border::BORDER_DASHDOT:
247
                return '1px dashed';
248 4
            case Border::BORDER_DASHDOTDOT:
249
                return '1px dotted';
250 4
            case Border::BORDER_DASHED:
251
                return '1px dashed';
252 4
            case Border::BORDER_DOTTED:
253
                return '1px dotted';
254 4
            case Border::BORDER_DOUBLE:
255
                return '3px double';
256 4
            case Border::BORDER_HAIR:
257
                return '1px solid';
258 4
            case Border::BORDER_MEDIUM:
259
                return '2px solid';
260 4
            case Border::BORDER_MEDIUMDASHDOT:
261
                return '2px dashed';
262 4
            case Border::BORDER_MEDIUMDASHDOTDOT:
263
                return '2px dotted';
264 4
            case Border::BORDER_MEDIUMDASHED:
265
                return '2px dashed';
266 4
            case Border::BORDER_SLANTDASHDOT:
267
                return '2px dashed';
268 4
            case Border::BORDER_THICK:
269 4
                return '3px solid';
270 4
            case Border::BORDER_THIN:
271 4
                return '1px solid';
272
            default:
273
                // map others to thin
274
                return '1px solid';
275
        }
276
    }
277
278
    /**
279
     * Get sheet index.
280
     *
281
     * @return int
282
     */
283 4
    public function getSheetIndex()
284
    {
285 4
        return $this->sheetIndex;
286
    }
287
288
    /**
289
     * Set sheet index.
290
     *
291
     * @param int $pValue Sheet index
292
     *
293
     * @return HTML
294
     */
295
    public function setSheetIndex($pValue)
296
    {
297
        $this->sheetIndex = $pValue;
298
299
        return $this;
300
    }
301
302
    /**
303
     * Get sheet index.
304
     *
305
     * @return bool
306
     */
307
    public function getGenerateSheetNavigationBlock()
308
    {
309
        return $this->generateSheetNavigationBlock;
310
    }
311
312
    /**
313
     * Set sheet index.
314
     *
315
     * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not
316
     *
317
     * @return HTML
318
     */
319
    public function setGenerateSheetNavigationBlock($pValue)
320
    {
321
        $this->generateSheetNavigationBlock = (bool) $pValue;
322
323
        return $this;
324
    }
325
326
    /**
327
     * Write all sheets (resets sheetIndex to NULL).
328
     */
329
    public function writeAllSheets()
330
    {
331
        $this->sheetIndex = null;
332
333
        return $this;
334
    }
335
336
    /**
337
     * Generate HTML header.
338
     *
339
     * @param bool $pIncludeStyles Include styles?
340
     *
341
     * @throws WriterException
342
     *
343
     * @return string
344
     */
345 6
    public function generateHTMLHeader($pIncludeStyles = false)
346
    {
347
        // Spreadsheet object known?
348 6
        if ($this->spreadsheet === null) {
349
            throw new WriterException('Internal Spreadsheet object not set to an instance of an object.');
350
        }
351
352
        // Construct HTML
353 6
        $properties = $this->spreadsheet->getProperties();
354 6
        $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' . PHP_EOL;
355 6
        $html .= '<!-- Generated by Spreadsheet - https://github.com/PHPOffice/Spreadsheet -->' . PHP_EOL;
356 6
        $html .= '<html>' . PHP_EOL;
357 6
        $html .= '  <head>' . PHP_EOL;
358 6
        $html .= '      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . PHP_EOL;
359 6
        if ($properties->getTitle() > '') {
360 6
            $html .= '      <title>' . htmlspecialchars($properties->getTitle()) . '</title>' . PHP_EOL;
361
        }
362 6
        if ($properties->getCreator() > '') {
363 6
            $html .= '      <meta name="author" content="' . htmlspecialchars($properties->getCreator()) . '" />' . PHP_EOL;
364
        }
365 6
        if ($properties->getTitle() > '') {
366 6
            $html .= '      <meta name="title" content="' . htmlspecialchars($properties->getTitle()) . '" />' . PHP_EOL;
367
        }
368 6 View Code Duplication
        if ($properties->getDescription() > '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
369 5
            $html .= '      <meta name="description" content="' . htmlspecialchars($properties->getDescription()) . '" />' . PHP_EOL;
370
        }
371 6
        if ($properties->getSubject() > '') {
372 6
            $html .= '      <meta name="subject" content="' . htmlspecialchars($properties->getSubject()) . '" />' . PHP_EOL;
373
        }
374 6
        if ($properties->getKeywords() > '') {
375 6
            $html .= '      <meta name="keywords" content="' . htmlspecialchars($properties->getKeywords()) . '" />' . PHP_EOL;
376
        }
377 6
        if ($properties->getCategory() > '') {
378 6
            $html .= '      <meta name="category" content="' . htmlspecialchars($properties->getCategory()) . '" />' . PHP_EOL;
379
        }
380 6 View Code Duplication
        if ($properties->getCompany() > '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
381 6
            $html .= '      <meta name="company" content="' . htmlspecialchars($properties->getCompany()) . '" />' . PHP_EOL;
382
        }
383 6 View Code Duplication
        if ($properties->getManager() > '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
            $html .= '      <meta name="manager" content="' . htmlspecialchars($properties->getManager()) . '" />' . PHP_EOL;
385
        }
386
387 6
        if ($pIncludeStyles) {
388 3
            $html .= $this->generateStyles(true);
389
        }
390
391 6
        $html .= '  </head>' . PHP_EOL;
392 6
        $html .= '' . PHP_EOL;
393 6
        $html .= '  <body>' . PHP_EOL;
394
395 6
        return $html;
396
    }
397
398
    /**
399
     * Generate sheet data.
400
     *
401
     * @throws WriterException
402
     *
403
     * @return string
404
     */
405 6
    public function generateSheetData()
406
    {
407
        // Spreadsheet object known?
408 6
        if ($this->spreadsheet === null) {
409
            throw new WriterException('Internal Spreadsheet object not set to an instance of an object.');
410
        }
411
412
        // Ensure that Spans have been calculated?
413 6
        if ($this->sheetIndex !== null || !$this->spansAreCalculated) {
414 6
            $this->calculateSpans();
415
        }
416
417
        // Fetch sheets
418 6
        $sheets = [];
419 6 View Code Duplication
        if ($this->sheetIndex === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
420
            $sheets = $this->spreadsheet->getAllSheets();
421
        } else {
422 6
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
423
        }
424
425
        // Construct HTML
426 6
        $html = '';
427
428
        // Loop all sheets
429 6
        $sheetId = 0;
430 6
        foreach ($sheets as $sheet) {
431
            // Write table header
432 6
            $html .= $this->generateTableHeader($sheet);
433
434
            // Get worksheet dimension
435 6
            $dimension = explode(':', $sheet->calculateWorksheetDimension());
436 6
            $dimension[0] = Coordinate::coordinateFromString($dimension[0]);
437 6
            $dimension[0][0] = Coordinate::columnIndexFromString($dimension[0][0]);
438 6
            $dimension[1] = Coordinate::coordinateFromString($dimension[1]);
439 6
            $dimension[1][0] = Coordinate::columnIndexFromString($dimension[1][0]);
440
441
            // row min,max
442 6
            $rowMin = $dimension[0][1];
443 6
            $rowMax = $dimension[1][1];
444
445
            // calculate start of <tbody>, <thead>
446 6
            $tbodyStart = $rowMin;
447 6
            $theadStart = $theadEnd = 0; // default: no <thead>    no </thead>
448 6
            if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
449
                $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop();
450
451
                // we can only support repeating rows that start at top row
452
                if ($rowsToRepeatAtTop[0] == 1) {
453
                    $theadStart = $rowsToRepeatAtTop[0];
454
                    $theadEnd = $rowsToRepeatAtTop[1];
455
                    $tbodyStart = $rowsToRepeatAtTop[1] + 1;
456
                }
457
            }
458
459
            // Loop through cells
460 6
            $row = $rowMin - 1;
461 6
            while ($row++ < $rowMax) {
462
                // <thead> ?
463 6
                if ($row == $theadStart) {
464
                    $html .= '        <thead>' . PHP_EOL;
465
                    $cellType = 'th';
466
                }
467
468
                // <tbody> ?
469 6
                if ($row == $tbodyStart) {
470 6
                    $html .= '        <tbody>' . PHP_EOL;
471 6
                    $cellType = 'td';
472
                }
473
474
                // Write row if there are HTML table cells in it
475 6
                if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
476
                    // Start a new rowData
477 6
                    $rowData = [];
478
                    // Loop through columns
479 6
                    $column = $dimension[0][0];
480 6
                    while ($column <= $dimension[1][0]) {
481
                        // Cell exists?
482 6
                        if ($sheet->cellExistsByColumnAndRow($column, $row)) {
483 6
                            $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row;
484
                        } else {
485 4
                            $rowData[$column] = '';
486
                        }
487 6
                        ++$column;
488
                    }
489 6
                    $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType);
0 ignored issues
show
Bug introduced by
The variable $cellType does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
490
                }
491
492
                // </thead> ?
493 6
                if ($row == $theadEnd) {
494
                    $html .= '        </thead>' . PHP_EOL;
495
                }
496
            }
497 6
            $html .= $this->extendRowsForChartsAndImages($sheet, $row);
498
499
            // Close table body.
500 6
            $html .= '        </tbody>' . PHP_EOL;
501
502
            // Write table footer
503 6
            $html .= $this->generateTableFooter();
504
505
            // Writing PDF?
506 6
            if ($this->isPdf) {
507 4
                if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) {
508
                    $html .= '<div style="page-break-before:always" />';
509
                }
510
            }
511
512
            // Next sheet
513 6
            ++$sheetId;
514
        }
515
516 6
        return $html;
517
    }
518
519
    /**
520
     * Generate sheet tabs.
521
     *
522
     * @throws WriterException
523
     *
524
     * @return string
525
     */
526 3
    public function generateNavigation()
527
    {
528
        // Spreadsheet object known?
529 3
        if ($this->spreadsheet === null) {
530
            throw new WriterException('Internal Spreadsheet object not set to an instance of an object.');
531
        }
532
533
        // Fetch sheets
534 3
        $sheets = [];
535 3 View Code Duplication
        if ($this->sheetIndex === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
536
            $sheets = $this->spreadsheet->getAllSheets();
537
        } else {
538 3
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
539
        }
540
541
        // Construct HTML
542 3
        $html = '';
543
544
        // Only if there are more than 1 sheets
545 3
        if (count($sheets) > 1) {
546
            // Loop all sheets
547
            $sheetId = 0;
548
549
            $html .= '<ul class="navigation">' . PHP_EOL;
550
551
            foreach ($sheets as $sheet) {
552
                $html .= '  <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL;
553
                ++$sheetId;
554
            }
555
556
            $html .= '</ul>' . PHP_EOL;
557
        }
558
559 3
        return $html;
560
    }
561
562 6
    private function extendRowsForChartsAndImages(Worksheet $pSheet, $row)
563
    {
564 6
        $rowMax = $row;
565 6
        $colMax = 'A';
566 6
        if ($this->includeCharts) {
567
            foreach ($pSheet->getChartCollection() as $chart) {
568
                if ($chart instanceof Chart) {
569
                    $chartCoordinates = $chart->getTopLeftPosition();
570
                    $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']);
571
                    $chartCol = Coordinate::columnIndexFromString($chartTL[0]);
572
                    if ($chartTL[1] > $rowMax) {
573
                        $rowMax = $chartTL[1];
574
                        if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
575
                            $colMax = $chartTL[0];
576
                        }
577
                    }
578
                }
579
            }
580
        }
581
582 6
        foreach ($pSheet->getDrawingCollection() as $drawing) {
583 5
            if ($drawing instanceof Drawing) {
584 4
                $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates());
585 4
                $imageCol = Coordinate::columnIndexFromString($imageTL[0]);
586 4
                if ($imageTL[1] > $rowMax) {
587
                    $rowMax = $imageTL[1];
588
                    if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
589 5
                        $colMax = $imageTL[0];
590
                    }
591
                }
592
            }
593
        }
594
595
        // Don't extend rows if not needed
596 6
        if ($row === $rowMax) {
597 6
            return '';
598
        }
599
600
        $html = '';
601
        ++$colMax;
602
603
        while ($row <= $rowMax) {
604
            $html .= '<tr>';
605
            for ($col = 'A'; $col != $colMax; ++$col) {
606
                $html .= '<td>';
607
                $html .= $this->writeImageInCell($pSheet, $col . $row);
608
                if ($this->includeCharts) {
609
                    $html .= $this->writeChartInCell($pSheet, $col . $row);
610
                }
611
                $html .= '</td>';
612
            }
613
            ++$row;
614
            $html .= '</tr>';
615
        }
616
617
        return $html;
618
    }
619
620
    /**
621
     * Generate image tag in cell.
622
     *
623
     * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
624
     * @param string $coordinates Cell coordinates
625
     *
626
     * @throws WriterException
627
     *
628
     * @return string
629
     */
630 6
    private function writeImageInCell(Worksheet $pSheet, $coordinates)
631
    {
632
        // Construct HTML
633 6
        $html = '';
634
635
        // Write images
636 6
        foreach ($pSheet->getDrawingCollection() as $drawing) {
637 5
            if ($drawing instanceof Drawing) {
638 4
                if ($drawing->getCoordinates() == $coordinates) {
639 4
                    $filename = $drawing->getPath();
640
641
                    // Strip off eventual '.'
642 4
                    if (substr($filename, 0, 1) == '.') {
643
                        $filename = substr($filename, 1);
644
                    }
645
646
                    // Prepend images root
647 4
                    $filename = $this->getImagesRoot() . $filename;
648
649
                    // Strip off eventual '.'
650 4
                    if (substr($filename, 0, 1) == '.' && substr($filename, 0, 2) != './') {
651
                        $filename = substr($filename, 1);
652
                    }
653
654
                    // Convert UTF8 data to PCDATA
655 4
                    $filename = htmlspecialchars($filename);
656
657 4
                    $html .= PHP_EOL;
658 4
                    if ((!$this->embedImages) || ($this->isPdf)) {
659 4
                        $imageData = $filename;
660
                    } else {
661
                        $imageDetails = getimagesize($filename);
662
                        if ($fp = fopen($filename, 'rb', 0)) {
663
                            $picture = fread($fp, filesize($filename));
664
                            fclose($fp);
665
                            // base64 encode the binary data, then break it
666
                            // into chunks according to RFC 2045 semantics
667
                            $base64 = chunk_split(base64_encode($picture));
668
                            $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
669
                        } else {
670
                            $imageData = $filename;
671
                        }
672
                    }
673
674 4
                    $html .= '<div style="position: relative;">';
675
                    $html .= '<img style="position: absolute; z-index: 1; left: ' .
676 4
                        $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' .
677 4
                        $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
678 4
                        $imageData . '" border="0" />';
679 4
                    $html .= '</div>';
680
                }
681 1
            } elseif ($drawing instanceof MemoryDrawing) {
682 1
                if ($drawing->getCoordinates() != $coordinates) {
683
                    continue;
684
                }
685 1
                ob_start(); //  Let's start output buffering.
686 1
                imagepng($drawing->getImageResource()); //  This will normally output the image, but because of ob_start(), it won't.
687 1
                $contents = ob_get_contents(); //  Instead, output above is saved to $contents
688 1
                ob_end_clean(); //  End the output buffer.
689
690 1
                $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
691
692
                //  Because of the nature of tables, width is more important than height.
693
                //  max-width: 100% ensures that image doesnt overflow containing cell
694
                //  width: X sets width of supplied image.
695
                //  As a result, images bigger than cell will be contained and images smaller will not get stretched
696 5
                $html .= '<img src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
697
            }
698
        }
699
700 6
        return $html;
701
    }
702
703
    /**
704
     * Generate chart tag in cell.
705
     *
706
     * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
707
     * @param string $coordinates Cell coordinates
708
     *
709
     * @throws WriterException
710
     *
711
     * @return string
712
     */
713
    private function writeChartInCell(Worksheet $pSheet, $coordinates)
714
    {
715
        // Construct HTML
716
        $html = '';
717
718
        // Write charts
719
        foreach ($pSheet->getChartCollection() as $chart) {
720
            if ($chart instanceof Chart) {
721
                $chartCoordinates = $chart->getTopLeftPosition();
722
                if ($chartCoordinates['cell'] == $coordinates) {
723
                    $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
724
                    if (!$chart->render($chartFileName)) {
725
                        return;
726
                    }
727
728
                    $html .= PHP_EOL;
729
                    $imageDetails = getimagesize($chartFileName);
730
                    if ($fp = fopen($chartFileName, 'rb', 0)) {
731
                        $picture = fread($fp, filesize($chartFileName));
732
                        fclose($fp);
733
                        // base64 encode the binary data, then break it
734
                        // into chunks according to RFC 2045 semantics
735
                        $base64 = chunk_split(base64_encode($picture));
736
                        $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
737
738
                        $html .= '<div style="position: relative;">';
739
                        $html .= '<img style="position: absolute; z-index: 1; left: ' . $chartCoordinates['xOffset'] . 'px; top: ' . $chartCoordinates['yOffset'] . 'px; width: ' . $imageDetails[0] . 'px; height: ' . $imageDetails[1] . 'px;" src="' . $imageData . '" border="0" />' . PHP_EOL;
740
                        $html .= '</div>';
741
742
                        unlink($chartFileName);
743
                    }
744
                }
745
            }
746
        }
747
748
        // Return
749
        return $html;
750
    }
751
752
    /**
753
     * Generate CSS styles.
754
     *
755
     * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (&lt;style&gt; and &lt;/style&gt;)
756
     *
757
     * @throws WriterException
758
     *
759
     * @return string
760
     */
761 3
    public function generateStyles($generateSurroundingHTML = true)
762
    {
763
        // Spreadsheet object known?
764 3
        if ($this->spreadsheet === null) {
765
            throw new WriterException('Internal Spreadsheet object not set to an instance of an object.');
766
        }
767
768
        // Build CSS
769 3
        $css = $this->buildCSS($generateSurroundingHTML);
770
771
        // Construct HTML
772 3
        $html = '';
773
774
        // Start styles
775 3
        if ($generateSurroundingHTML) {
776 3
            $html .= '    <style type="text/css">' . PHP_EOL;
777 3
            $html .= '      html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL;
778
        }
779
780
        // Write all other styles
781 3
        foreach ($css as $styleName => $styleDefinition) {
782 3
            if ($styleName != 'html') {
783 3
                $html .= '      ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL;
784
            }
785
        }
786
787
        // End styles
788 3
        if ($generateSurroundingHTML) {
789 3
            $html .= '    </style>' . PHP_EOL;
790
        }
791
792
        // Return
793 3
        return $html;
794
    }
795
796
    /**
797
     * Build CSS styles.
798
     *
799
     * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { })
800
     *
801
     * @throws WriterException
802
     *
803
     * @return array
804
     */
805 6
    public function buildCSS($generateSurroundingHTML = true)
806
    {
807
        // Spreadsheet object known?
808 6
        if ($this->spreadsheet === null) {
809
            throw new WriterException('Internal Spreadsheet object not set to an instance of an object.');
810
        }
811
812
        // Cached?
813 6
        if ($this->cssStyles !== null) {
814 3
            return $this->cssStyles;
815
        }
816
817
        // Ensure that spans have been calculated
818 6
        if (!$this->spansAreCalculated) {
819 6
            $this->calculateSpans();
820
        }
821
822
        // Construct CSS
823 6
        $css = [];
824
825
        // Start styles
826 6
        if ($generateSurroundingHTML) {
827
            // html { }
828 6
            $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif';
829 6
            $css['html']['font-size'] = '11pt';
830 6
            $css['html']['background-color'] = 'white';
831
        }
832
833
        // table { }
834 6
        $css['table']['border-collapse'] = 'collapse';
835 6
        if (!$this->isPdf) {
836 3
            $css['table']['page-break-after'] = 'always';
837
        }
838
839
        // .gridlines td { }
840 6
        $css['.gridlines td']['border'] = '1px dotted black';
841 6
        $css['.gridlines th']['border'] = '1px dotted black';
842
843
        // .b {}
844 6
        $css['.b']['text-align'] = 'center'; // BOOL
845
846
        // .e {}
847 6
        $css['.e']['text-align'] = 'center'; // ERROR
848
849
        // .f {}
850 6
        $css['.f']['text-align'] = 'right'; // FORMULA
851
852
        // .inlineStr {}
853 6
        $css['.inlineStr']['text-align'] = 'left'; // INLINE
854
855
        // .n {}
856 6
        $css['.n']['text-align'] = 'right'; // NUMERIC
857
858
        // .s {}
859 6
        $css['.s']['text-align'] = 'left'; // STRING
860
861
        // Calculate cell style hashes
862 6
        foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
863 6
            $css['td.style' . $index] = $this->createCSSStyle($style);
864 6
            $css['th.style' . $index] = $this->createCSSStyle($style);
865
        }
866
867
        // Fetch sheets
868 6
        $sheets = [];
869 6 View Code Duplication
        if ($this->sheetIndex === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
870
            $sheets = $this->spreadsheet->getAllSheets();
871
        } else {
872 6
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
873
        }
874
875
        // Build styles per sheet
876 6
        foreach ($sheets as $sheet) {
877
            // Calculate hash code
878 6
            $sheetIndex = $sheet->getParent()->getIndex($sheet);
879
880
            // Build styles
881
            // Calculate column widths
882 6
            $sheet->calculateColumnWidths();
883
884
            // col elements, initialize
885 6
            $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
886 6
            $column = -1;
887 6
            while ($column++ < $highestColumnIndex) {
888 6
                $this->columnWidths[$sheetIndex][$column] = 42; // approximation
889 6
                $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
890
            }
891
892
            // col elements, loop through columnDimensions and set width
893 6
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
894 5
                if (($width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont)) >= 0) {
895 5
                    $width = SharedDrawing::pixelsToPoints($width);
896 5
                    $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1;
897 5
                    $this->columnWidths[$sheetIndex][$column] = $width;
898 5
                    $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt';
899
900 5 View Code Duplication
                    if ($columnDimension->getVisible() === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
901
                        $css['table.sheet' . $sheetIndex . ' col.col' . $column]['visibility'] = 'collapse';
902 5
                        $css['table.sheet' . $sheetIndex . ' col.col' . $column]['*display'] = 'none'; // target IE6+7
903
                    }
904
                }
905
            }
906
907
            // Default row height
908 6
            $rowDimension = $sheet->getDefaultRowDimension();
909
910
            // table.sheetN tr { }
911 6
            $css['table.sheet' . $sheetIndex . ' tr'] = [];
912
913 6 View Code Duplication
            if ($rowDimension->getRowHeight() == -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
914 6
                $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
915
            } else {
916
                $pt_height = $rowDimension->getRowHeight();
917
            }
918 6
            $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt';
919 6
            if ($rowDimension->getVisible() === false) {
920
                $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none';
921
                $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden';
922
            }
923
924
            // Calculate row heights
925 6
            foreach ($sheet->getRowDimensions() as $rowDimension) {
926 2
                $row = $rowDimension->getRowIndex() - 1;
927
928
                // table.sheetN tr.rowYYYYYY { }
929 2
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = [];
930
931 2 View Code Duplication
                if ($rowDimension->getRowHeight() == -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
932 2
                    $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
933
                } else {
934 1
                    $pt_height = $rowDimension->getRowHeight();
935
                }
936 2
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt';
937 2 View Code Duplication
                if ($rowDimension->getVisible() === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
938
                    $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none';
939 6
                    $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden';
940
                }
941
            }
942
        }
943
944
        // Cache
945 6
        if ($this->cssStyles === null) {
946 6
            $this->cssStyles = $css;
947
        }
948
949
        // Return
950 6
        return $css;
951
    }
952
953
    /**
954
     * Create CSS style.
955
     *
956
     * @param Style $pStyle
957
     *
958
     * @return array
959
     */
960 6
    private function createCSSStyle(Style $pStyle)
961
    {
962
        // Create CSS
963 6
        $css = array_merge(
964 6
            $this->createCSSStyleAlignment($pStyle->getAlignment()),
965 6
            $this->createCSSStyleBorders($pStyle->getBorders()),
966 6
            $this->createCSSStyleFont($pStyle->getFont()),
967 6
            $this->createCSSStyleFill($pStyle->getFill())
968
        );
969
970
        // Return
971 6
        return $css;
972
    }
973
974
    /**
975
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment).
976
     *
977
     * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment
978
     *
979
     * @return array
980
     */
981 6
    private function createCSSStyleAlignment(Alignment $pStyle)
982
    {
983
        // Construct CSS
984 6
        $css = [];
985
986
        // Create CSS
987 6
        $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical());
988 6
        if ($textAlign = $this->mapHAlign($pStyle->getHorizontal())) {
989 5
            $css['text-align'] = $textAlign;
990 5
            if (in_array($textAlign, ['left', 'right'])) {
991 4
                $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px';
992
            }
993
        }
994
995 6
        return $css;
996
    }
997
998
    /**
999
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font).
1000
     *
1001
     * @param Font $pStyle
1002
     *
1003
     * @return array
1004
     */
1005 6
    private function createCSSStyleFont(Font $pStyle)
1006
    {
1007
        // Construct CSS
1008 6
        $css = [];
1009
1010
        // Create CSS
1011 6
        if ($pStyle->getBold()) {
1012 5
            $css['font-weight'] = 'bold';
1013
        }
1014 6
        if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) {
1015
            $css['text-decoration'] = 'underline line-through';
1016 6
        } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) {
1017 4
            $css['text-decoration'] = 'underline';
1018 6
        } elseif ($pStyle->getStrikethrough()) {
1019
            $css['text-decoration'] = 'line-through';
1020
        }
1021 6
        if ($pStyle->getItalic()) {
1022 4
            $css['font-style'] = 'italic';
1023
        }
1024
1025 6
        $css['color'] = '#' . $pStyle->getColor()->getRGB();
1026 6
        $css['font-family'] = '\'' . $pStyle->getName() . '\'';
1027 6
        $css['font-size'] = $pStyle->getSize() . 'pt';
1028
1029 6
        return $css;
1030
    }
1031
1032
    /**
1033
     * Create CSS style (Borders).
1034
     *
1035
     * @param Borders $pStyle Borders
1036
     *
1037
     * @return array
1038
     */
1039 6
    private function createCSSStyleBorders(Borders $pStyle)
1040
    {
1041
        // Construct CSS
1042 6
        $css = [];
1043
1044
        // Create CSS
1045 6
        $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom());
1046 6
        $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop());
1047 6
        $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft());
1048 6
        $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight());
1049
1050 6
        return $css;
1051
    }
1052
1053
    /**
1054
     * Create CSS style (Border).
1055
     *
1056
     * @param Border $pStyle Border
1057
     *
1058
     * @return string
1059
     */
1060 6
    private function createCSSStyleBorder(Border $pStyle)
1061
    {
1062
        //    Create CSS - add !important to non-none border styles for merged cells
1063 6
        $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle());
1064 6
        $css = $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
1065
1066 6
        return $css;
1067
    }
1068
1069
    /**
1070
     * Create CSS style (Fill).
1071
     *
1072
     * @param Fill $pStyle Fill
1073
     *
1074
     * @return array
1075
     */
1076 6
    private function createCSSStyleFill(Fill $pStyle)
1077
    {
1078
        // Construct HTML
1079 6
        $css = [];
1080
1081
        // Create CSS
1082 6
        $value = $pStyle->getFillType() == Fill::FILL_NONE ?
1083 6
            'white' : '#' . $pStyle->getStartColor()->getRGB();
1084 6
        $css['background-color'] = $value;
1085
1086 6
        return $css;
1087
    }
1088
1089
    /**
1090
     * Generate HTML footer.
1091
     */
1092 6
    public function generateHTMLFooter()
1093
    {
1094
        // Construct HTML
1095 6
        $html = '';
1096 6
        $html .= '  </body>' . PHP_EOL;
1097 6
        $html .= '</html>' . PHP_EOL;
1098
1099 6
        return $html;
1100
    }
1101
1102
    /**
1103
     * Generate table header.
1104
     *
1105
     * @param Worksheet $pSheet The worksheet for the table we are writing
1106
     *
1107
     * @throws WriterException
1108
     *
1109
     * @return string
1110
     */
1111 6
    private function generateTableHeader($pSheet)
1112
    {
1113 6
        $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
1114
1115
        // Construct HTML
1116 6
        $html = '';
1117 6
        $html .= $this->setMargins($pSheet);
1118
1119 6
        if (!$this->useInlineCss) {
1120 3
            $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : '';
1121 3
            $html .= '    <table border="0" cellpadding="0" cellspacing="0" id="sheet' . $sheetIndex . '" class="sheet' . $sheetIndex . $gridlines . '">' . PHP_EOL;
1122
        } else {
1123 4
            $style = isset($this->cssStyles['table']) ?
1124 4
                $this->assembleCSS($this->cssStyles['table']) : '';
1125
1126 4
            if ($this->isPdf && $pSheet->getShowGridlines()) {
1127 1
                $html .= '    <table border="1" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="1" style="' . $style . '">' . PHP_EOL;
1128
            } else {
1129 3
                $html .= '    <table border="0" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="0" style="' . $style . '">' . PHP_EOL;
1130
            }
1131
        }
1132
1133
        // Write <col> elements
1134 6
        $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1;
1135 6
        $i = -1;
1136 6
        while ($i++ < $highestColumnIndex) {
1137 6 View Code Duplication
            if (!$this->isPdf) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1138 3
                if (!$this->useInlineCss) {
1139 3
                    $html .= '        <col class="col' . $i . '">' . PHP_EOL;
1140
                } else {
1141
                    $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
1142
                        $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
1143
                    $html .= '        <col style="' . $style . '">' . PHP_EOL;
1144
                }
1145
            }
1146
        }
1147
1148 6
        return $html;
1149
    }
1150
1151
    /**
1152
     * Generate table footer.
1153
     *
1154
     * @throws WriterException
1155
     */
1156 6
    private function generateTableFooter()
1157
    {
1158 6
        $html = '    </table>' . PHP_EOL;
1159
1160 6
        return $html;
1161
    }
1162
1163
    /**
1164
     * Generate row.
1165
     *
1166
     * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
1167
     * @param array $pValues Array containing cells in a row
1168
     * @param int $pRow Row number (0-based)
1169
     * @param string $cellType eg: 'td'
1170
     *
1171
     * @throws WriterException
1172
     *
1173
     * @return string
1174
     */
1175 6
    private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType)
1176
    {
1177
        // Construct HTML
1178 6
        $html = '';
1179
1180
        // Sheet index
1181 6
        $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
1182
1183
        // Dompdf and breaks
1184 6
        if ($this->isPdf && count($pSheet->getBreaks()) > 0) {
1185
            $breaks = $pSheet->getBreaks();
1186
1187
            // check if a break is needed before this row
1188
            if (isset($breaks['A' . $pRow])) {
1189
                // close table: </table>
1190
                $html .= $this->generateTableFooter();
1191
1192
                // insert page break
1193
                $html .= '<div style="page-break-before:always" />';
1194
1195
                // open table again: <table> + <col> etc.
1196
                $html .= $this->generateTableHeader($pSheet);
1197
            }
1198
        }
1199
1200
        // Write row start
1201 6 View Code Duplication
        if (!$this->useInlineCss) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1202 3
            $html .= '          <tr class="row' . $pRow . '">' . PHP_EOL;
1203
        } else {
1204 4
            $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow])
1205 4
                ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : '';
1206
1207 4
            $html .= '          <tr style="' . $style . '">' . PHP_EOL;
1208
        }
1209
1210
        // Write cells
1211 6
        $colNum = 0;
1212 6
        foreach ($pValues as $cellAddress) {
1213 6
            $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : '';
1214 6
            $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1);
1215 6
            if (!$this->useInlineCss) {
1216 3
                $cssClass = 'column' . $colNum;
1217
            } else {
1218 4
                $cssClass = [];
1219 4
                if ($cellType == 'th') {
1220 View Code Duplication
                    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1221
                        $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
1222
                    }
1223 View Code Duplication
                } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1224 4
                    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
1225
                        $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
1226
                    }
1227
                }
1228
            }
1229 6
            $colSpan = 1;
1230 6
            $rowSpan = 1;
1231
1232
            // initialize
1233 6
            $cellData = '&nbsp;';
1234
1235
            // Cell
1236 6
            if ($cell instanceof Cell) {
1237 6
                $cellData = '';
1238 6
                if ($cell->getParent() === null) {
1239
                    $cell->attach($pSheet);
0 ignored issues
show
Documentation introduced by
$pSheet is of type object<PhpOffice\PhpSpre...et\Worksheet\Worksheet>, but the function expects a object<PhpOffice\PhpSpreadsheet\Collection\Cells>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1240
                }
1241
                // Value
1242 6
                if ($cell->getValue() instanceof RichText) {
1243
                    // Loop through rich text elements
1244 5
                    $elements = $cell->getValue()->getRichTextElements();
1245 5
                    foreach ($elements as $element) {
1246
                        // Rich text start?
1247 5
                        if ($element instanceof Run) {
1248 5
                            $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">';
1249
1250 5
                            if ($element->getFont()->getSuperscript()) {
1251
                                $cellData .= '<sup>';
1252 5
                            } elseif ($element->getFont()->getSubscript()) {
1253
                                $cellData .= '<sub>';
1254
                            }
1255
                        }
1256
1257
                        // Convert UTF8 data to PCDATA
1258 5
                        $cellText = $element->getText();
1259 5
                        $cellData .= htmlspecialchars($cellText);
1260
1261 5
                        if ($element instanceof Run) {
1262 5
                            if ($element->getFont()->getSuperscript()) {
1263
                                $cellData .= '</sup>';
1264 5
                            } elseif ($element->getFont()->getSubscript()) {
1265
                                $cellData .= '</sub>';
1266
                            }
1267
1268 5
                            $cellData .= '</span>';
1269
                        }
1270
                    }
1271
                } else {
1272 6
                    if ($this->preCalculateFormulas) {
1273 6
                        $cellData = NumberFormat::toFormattedString(
1274 6
                            $cell->getCalculatedValue(),
1275 6
                            $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
1276 6
                            [$this, 'formatColor']
1277
                        );
1278
                    } else {
1279
                        $cellData = NumberFormat::toFormattedString(
1280
                            $cell->getValue(),
1281
                            $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
1282
                            [$this, 'formatColor']
1283
                        );
1284
                    }
1285 6
                    $cellData = htmlspecialchars($cellData);
1286 6
                    if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
1287
                        $cellData = '<sup>' . $cellData . '</sup>';
1288 6
                    } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
1289
                        $cellData = '<sub>' . $cellData . '</sub>';
1290
                    }
1291
                }
1292
1293
                // Converts the cell content so that spaces occuring at beginning of each new line are replaced by &nbsp;
1294
                // Example: "  Hello\n to the world" is converted to "&nbsp;&nbsp;Hello\n&nbsp;to the world"
1295 6
                $cellData = preg_replace('/(?m)(?:^|\\G) /', '&nbsp;', $cellData);
1296
1297
                // convert newline "\n" to '<br>'
1298 6
                $cellData = nl2br($cellData);
1299
1300
                // Extend CSS class?
1301 6
                if (!$this->useInlineCss) {
1302 3
                    $cssClass .= ' style' . $cell->getXfIndex();
1303 3
                    $cssClass .= ' ' . $cell->getDataType();
1304
                } else {
1305 4
                    if ($cellType == 'th') {
1306 View Code Duplication
                        if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1307
                            $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
1308
                        }
1309 View Code Duplication
                    } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1310 4
                        if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
1311 4
                            $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
1312
                        }
1313
                    }
1314
1315
                    // General horizontal alignment: Actual horizontal alignment depends on dataType
1316 4
                    $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex());
1317 4
                    if ($sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
1318 4
                        && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
1319
                    ) {
1320 4
                        $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align'];
1321
                    }
1322
                }
1323
            }
1324
1325
            // Hyperlink?
1326 6
            if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) {
1327 4
                $cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>';
1328
            }
1329
1330
            // Should the cell be written or is it swallowed by a rowspan or colspan?
1331 6
            $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])
1332 6
                && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]);
1333
1334
            // Colspan and Rowspan
1335 6
            $colspan = 1;
0 ignored issues
show
Unused Code introduced by
$colspan is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1336 6
            $rowspan = 1;
0 ignored issues
show
Unused Code introduced by
$rowspan is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1337 6
            if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) {
1338 5
                $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum];
1339 5
                $rowSpan = $spans['rowspan'];
1340 5
                $colSpan = $spans['colspan'];
1341
1342
                //    Also apply style from last cell in merge to fix borders -
1343
                //        relies on !important for non-none border declarations in createCSSStyleBorder
1344 5
                $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan);
1345 5
                if (!$this->useInlineCss) {
1346 2
                    $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex();
1347
                }
1348
            }
1349
1350
            // Write
1351 6
            if ($writeCell) {
1352
                // Column start
1353 6
                $html .= '            <' . $cellType;
1354 6
                if (!$this->useInlineCss) {
1355 3
                    $html .= ' class="' . $cssClass . '"';
1356
                } else {
1357
                    //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf **
1358
                    // We must explicitly write the width of the <td> element because TCPDF
1359
                    // does not recognize e.g. <col style="width:42pt">
1360 4
                    $width = 0;
1361 4
                    $i = $colNum - 1;
1362 4
                    $e = $colNum + $colSpan - 1;
1363 4
                    while ($i++ < $e) {
1364 4
                        if (isset($this->columnWidths[$sheetIndex][$i])) {
1365 4
                            $width += $this->columnWidths[$sheetIndex][$i];
1366
                        }
1367
                    }
1368 4
                    $cssClass['width'] = $width . 'pt';
1369
1370
                    // We must also explicitly write the height of the <td> element because TCPDF
1371
                    // does not recognize e.g. <tr style="height:50pt">
1372 4
                    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) {
1373 1
                        $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'];
1374 1
                        $cssClass['height'] = $height;
1375
                    }
1376
                    //** end of redundant code **
1377
1378 4
                    $html .= ' style="' . $this->assembleCSS($cssClass) . '"';
1379
                }
1380 6
                if ($colSpan > 1) {
1381 5
                    $html .= ' colspan="' . $colSpan . '"';
1382
                }
1383 6
                if ($rowSpan > 1) {
1384
                    $html .= ' rowspan="' . $rowSpan . '"';
1385
                }
1386 6
                $html .= '>';
1387
1388
                // Image?
1389 6
                $html .= $this->writeImageInCell($pSheet, $coordinate);
1390
1391
                // Chart?
1392 6
                if ($this->includeCharts) {
1393
                    $html .= $this->writeChartInCell($pSheet, $coordinate);
1394
                }
1395
1396
                // Cell data
1397 6
                $html .= $cellData;
1398
1399
                // Column end
1400 6
                $html .= '</' . $cellType . '>' . PHP_EOL;
1401
            }
1402
1403
            // Next column
1404 6
            ++$colNum;
1405
        }
1406
1407
        // Write row end
1408 6
        $html .= '          </tr>' . PHP_EOL;
1409
1410
        // Return
1411 6
        return $html;
1412
    }
1413
1414
    /**
1415
     * Takes array where of CSS properties / values and converts to CSS string.
1416
     *
1417
     * @param array
1418
     * @param mixed $pValue
1419
     *
1420
     * @return string
1421
     */
1422 6
    private function assembleCSS($pValue = [])
1423
    {
1424 6
        $pairs = [];
1425 6
        foreach ($pValue as $property => $value) {
1426 6
            $pairs[] = $property . ':' . $value;
1427
        }
1428 6
        $string = implode('; ', $pairs);
1429
1430 6
        return $string;
1431
    }
1432
1433
    /**
1434
     * Get images root.
1435
     *
1436
     * @return string
1437
     */
1438 4
    public function getImagesRoot()
1439
    {
1440 4
        return $this->imagesRoot;
1441
    }
1442
1443
    /**
1444
     * Set images root.
1445
     *
1446
     * @param string $pValue
1447
     *
1448
     * @return HTML
1449
     */
1450
    public function setImagesRoot($pValue)
1451
    {
1452
        $this->imagesRoot = $pValue;
1453
1454
        return $this;
1455
    }
1456
1457
    /**
1458
     * Get embed images.
1459
     *
1460
     * @return bool
1461
     */
1462
    public function getEmbedImages()
1463
    {
1464
        return $this->embedImages;
1465
    }
1466
1467
    /**
1468
     * Set embed images.
1469
     *
1470
     * @param bool $pValue
1471
     *
1472
     * @return HTML
1473
     */
1474
    public function setEmbedImages($pValue)
1475
    {
1476
        $this->embedImages = $pValue;
1477
1478
        return $this;
1479
    }
1480
1481
    /**
1482
     * Get use inline CSS?
1483
     *
1484
     * @return bool
1485
     */
1486
    public function getUseInlineCss()
1487
    {
1488
        return $this->useInlineCss;
1489
    }
1490
1491
    /**
1492
     * Set use inline CSS?
1493
     *
1494
     * @param bool $pValue
1495
     *
1496
     * @return HTML
1497
     */
1498 8
    public function setUseInlineCss($pValue)
1499
    {
1500 8
        $this->useInlineCss = $pValue;
1501
1502 8
        return $this;
1503
    }
1504
1505
    /**
1506
     * Add color to formatted string as inline style.
1507
     *
1508
     * @param string $pValue Plain formatted value without color
1509
     * @param string $pFormat Format code
1510
     *
1511
     * @return string
1512
     */
1513 4
    public function formatColor($pValue, $pFormat)
1514
    {
1515
        // Color information, e.g. [Red] is always at the beginning
1516 4
        $color = null; // initialize
1517 4
        $matches = [];
1518
1519 4
        $color_regex = '/^\\[[a-zA-Z]+\\]/';
1520 4
        if (preg_match($color_regex, $pFormat, $matches)) {
1521
            $color = str_replace(['[', ']'], '', $matches[0]);
1522
            $color = strtolower($color);
1523
        }
1524
1525
        // convert to PCDATA
1526 4
        $value = htmlspecialchars($pValue);
1527
1528
        // color span tag
1529 4
        if ($color !== null) {
1530
            $value = '<span style="color:' . $color . '">' . $value . '</span>';
1531
        }
1532
1533 4
        return $value;
1534
    }
1535
1536
    /**
1537
     * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
1538
     */
1539 6
    private function calculateSpans()
1540
    {
1541
        // Identify all cells that should be omitted in HTML due to cell merge.
1542
        // In HTML only the upper-left cell should be written and it should have
1543
        //   appropriate rowspan / colspan attribute
1544 6
        $sheetIndexes = $this->sheetIndex !== null ?
1545 6
            [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
1546
1547 6
        foreach ($sheetIndexes as $sheetIndex) {
1548 6
            $sheet = $this->spreadsheet->getSheet($sheetIndex);
1549
1550 6
            $candidateSpannedRow = [];
1551
1552
            // loop through all Excel merged cells
1553 6
            foreach ($sheet->getMergeCells() as $cells) {
1554 5
                list($cells) = Coordinate::splitRange($cells);
0 ignored issues
show
Documentation introduced by
$cells is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1555 5
                $first = $cells[0];
1556 5
                $last = $cells[1];
1557
1558 5
                list($fc, $fr) = Coordinate::coordinateFromString($first);
1559 5
                $fc = Coordinate::columnIndexFromString($fc) - 1;
1560
1561 5
                list($lc, $lr) = Coordinate::coordinateFromString($last);
1562 5
                $lc = Coordinate::columnIndexFromString($lc) - 1;
1563
1564
                // loop through the individual cells in the individual merge
1565 5
                $r = $fr - 1;
1566 5
                while ($r++ < $lr) {
1567
                    // also, flag this row as a HTML row that is candidate to be omitted
1568 5
                    $candidateSpannedRow[$r] = $r;
1569
1570 5
                    $c = $fc - 1;
1571 5
                    while ($c++ < $lc) {
1572 5
                        if (!($c == $fc && $r == $fr)) {
1573
                            // not the upper-left cell (should not be written in HTML)
1574 5
                            $this->isSpannedCell[$sheetIndex][$r][$c] = [
1575 5
                                'baseCell' => [$fr, $fc],
1576
                            ];
1577
                        } else {
1578
                            // upper-left is the base cell that should hold the colspan/rowspan attribute
1579 5
                            $this->isBaseCell[$sheetIndex][$r][$c] = [
1580 5
                                'xlrowspan' => $lr - $fr + 1, // Excel rowspan
1581 5
                                'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
1582 5
                                'xlcolspan' => $lc - $fc + 1, // Excel colspan
1583 5
                                'colspan' => $lc - $fc + 1, // HTML colspan, value may change
1584
                            ];
1585
                        }
1586
                    }
1587
                }
1588
            }
1589
1590
            // Identify which rows should be omitted in HTML. These are the rows where all the cells
1591
            //   participate in a merge and the where base cells are somewhere above.
1592 6
            $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn());
1593 6
            foreach ($candidateSpannedRow as $rowIndex) {
1594 5
                if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
1595 5
                    if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
1596 5
                        $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
1597
                    }
1598
                }
1599
            }
1600
1601
            // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
1602 6
            if (isset($this->isSpannedRow[$sheetIndex])) {
1603 4
                foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
1604 4
                    $adjustedBaseCells = [];
1605 4
                    $c = -1;
1606 4
                    $e = $countColumns - 1;
1607 6
                    while ($c++ < $e) {
1608 4
                        $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
1609
1610 4
                        if (!in_array($baseCell, $adjustedBaseCells)) {
1611
                            // subtract rowspan by 1
1612 4
                            --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
1613 4
                            $adjustedBaseCells[] = $baseCell;
1614
                        }
1615
                    }
1616
                }
1617
            }
1618
1619
            // TODO: Same for columns
1620
        }
1621
1622
        // We have calculated the spans
1623 6
        $this->spansAreCalculated = true;
1624 6
    }
1625
1626 6
    private function setMargins(Worksheet $pSheet)
1627
    {
1628 6
        $htmlPage = '@page { ';
1629 6
        $htmlBody = 'body { ';
1630
1631 6
        $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; ';
1632 6
        $htmlPage .= 'margin-left: ' . $left;
1633 6
        $htmlBody .= 'margin-left: ' . $left;
1634 6
        $right = StringHelper::formatNumber($pSheet->getPageMargins()->getRight()) . 'in; ';
1635 6
        $htmlPage .= 'margin-right: ' . $right;
1636 6
        $htmlBody .= 'margin-right: ' . $right;
1637 6
        $top = StringHelper::formatNumber($pSheet->getPageMargins()->getTop()) . 'in; ';
1638 6
        $htmlPage .= 'margin-top: ' . $top;
1639 6
        $htmlBody .= 'margin-top: ' . $top;
1640 6
        $bottom = StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()) . 'in; ';
1641 6
        $htmlPage .= 'margin-bottom: ' . $bottom;
1642 6
        $htmlBody .= 'margin-bottom: ' . $bottom;
1643
1644 6
        $htmlPage .= "}\n";
1645 6
        $htmlBody .= "}\n";
1646
1647 6
        return "<style>\n" . $htmlPage . $htmlBody . "</style>\n";
1648
    }
1649
}
1650