Completed
Push — develop ( 66f372...1cdc85 )
by Adrien
20:59
created

HTML::mapVAlign()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.9256

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 1
dl 0
loc 14
rs 8.8571
c 0
b 0
f 0
ccs 6
cts 9
cp 0.6667
crap 5.9256
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer;
4
5
use PhpOffice\PhpSpreadsheet\Calculation;
6
use PhpOffice\PhpSpreadsheet\Shared\Font;
7
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
8
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9
10
/**
11
 * Copyright (c) 2006 - 2015 Spreadsheet.
12
 *
13
 * This library is free software; you can redistribute it and/or
14
 * modify it under the terms of the GNU Lesser General Public
15
 * License as published by the Free Software Foundation; either
16
 * version 2.1 of the License, or (at your option) any later version.
17
 *
18
 * This library is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21
 * Lesser General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Lesser General Public
24
 * License along with this library; if not, write to the Free Software
25
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26
 *
27
 * @category   Spreadsheet
28
 *
29
 * @copyright  Copyright (c) 2006 - 2015 Spreadsheet (https://github.com/PHPOffice/Spreadsheet)
30
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
31
 */
32
class HTML extends BaseWriter implements IWriter
33
{
34
    /**
35
     * Spreadsheet object.
36
     *
37
     * @var Spreadsheet
38
     */
39
    protected $spreadsheet;
40
41
    /**
42
     * Sheet index to write.
43
     *
44
     * @var int
45
     */
46
    private $sheetIndex = 0;
47
48
    /**
49
     * Images root.
50
     *
51
     * @var string
52
     */
53
    private $imagesRoot = '';
54
55
    /**
56
     * embed images, or link to images.
57
     *
58
     * @var bool
59
     */
60
    private $embedImages = false;
61
62
    /**
63
     * Use inline CSS?
64
     *
65
     * @var bool
66
     */
67
    private $useInlineCss = false;
68
69
    /**
70
     * Array of CSS styles.
71
     *
72
     * @var array
73
     */
74
    private $cssStyles;
75
76
    /**
77
     * Array of column widths in points.
78
     *
79
     * @var array
80
     */
81
    private $columnWidths;
82
83
    /**
84
     * Default font.
85
     *
86
     * @var \PhpOffice\PhpSpreadsheet\Style\Font
87
     */
88
    private $defaultFont;
89
90
    /**
91
     * Flag whether spans have been calculated.
92
     *
93
     * @var bool
94
     */
95
    private $spansAreCalculated = false;
96
97
    /**
98
     * Excel cells that should not be written as HTML cells.
99
     *
100
     * @var array
101
     */
102
    private $isSpannedCell = [];
103
104
    /**
105
     * Excel cells that are upper-left corner in a cell merge.
106
     *
107
     * @var array
108
     */
109
    private $isBaseCell = [];
110
111
    /**
112
     * Excel rows that should not be written as HTML rows.
113
     *
114
     * @var array
115
     */
116
    private $isSpannedRow = [];
117
118
    /**
119
     * Is the current writer creating PDF?
120
     *
121
     * @var bool
122
     */
123
    protected $isPdf = false;
124
125
    /**
126
     * Generate the Navigation block.
127
     *
128
     * @var bool
129
     */
130
    private $generateSheetNavigationBlock = true;
131
132
    /**
133
     * Create a new HTML.
134
     *
135
     * @param    \PhpOffice\PhpSpreadsheet\Spreadsheet    $spreadsheet
136
     */
137 6
    public function __construct(Spreadsheet $spreadsheet)
138
    {
139 6
        $this->spreadsheet = $spreadsheet;
140 6
        $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont();
141 6
    }
142
143
    /**
144
     * Save Spreadsheet to file.
145
     *
146
     * @param    string        $pFilename
147
     *
148
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
149
     */
150 3
    public function save($pFilename = null)
151
    {
152
        // garbage collect
153 3
        $this->spreadsheet->garbageCollect();
154
155 3
        $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
156 3
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
157 3
        $saveArrayReturnType = Calculation::getArrayReturnType();
158 3
        Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
159
160
        // Build CSS
161 3
        $this->buildCSS(!$this->useInlineCss);
162
163
        // Open file
164 3
        $fileHandle = fopen($pFilename, 'wb+');
165 3
        if ($fileHandle === false) {
166
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception("Could not open file $pFilename for writing.");
167
        }
168
169
        // Write headers
170 3
        fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss));
171
172
        // Write navigation (tabs)
173 3
        if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) {
174 3
            fwrite($fileHandle, $this->generateNavigation());
175
        }
176
177
        // Write data
178 3
        fwrite($fileHandle, $this->generateSheetData());
179
180
        // Write footer
181 3
        fwrite($fileHandle, $this->generateHTMLFooter());
182
183
        // Close file
184 3
        fclose($fileHandle);
185
186 3
        Calculation::setArrayReturnType($saveArrayReturnType);
187 3
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
188 3
    }
189
190
    /**
191
     * Map VAlign.
192
     *
193
     * @param    string        $vAlign        Vertical alignment
194
     *
195
     * @return string
196
     */
197 6
    private function mapVAlign($vAlign)
198
    {
199
        switch ($vAlign) {
200 6
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_BOTTOM:
201 6
                return 'bottom';
202 4
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP:
203
                return 'top';
204 4
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER:
205
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_JUSTIFY:
206 4
                return 'middle';
207
            default:
208
                return 'baseline';
209
        }
210
    }
211
212
    /**
213
     * Map HAlign.
214
     *
215
     * @param    string        $hAlign        Horizontal alignment
216
     *
217
     * @return string|false
218
     */
219 6
    private function mapHAlign($hAlign)
220
    {
221
        switch ($hAlign) {
222 6
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_GENERAL:
223 6
                return false;
224 5
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT:
225 4
                return 'left';
226 5
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT:
227 4
                return 'right';
228 5
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER:
229 4
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER_CONTINUOUS:
230 1
                return 'center';
231 4
            case \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_JUSTIFY:
232 4
                return 'justify';
233
            default:
234
                return false;
235
        }
236
    }
237
238
    /**
239
     * Map border style.
240
     *
241
     * @param    int        $borderStyle        Sheet index
242
     *
243
     * @return    string
244
     */
245 6
    private function mapBorderStyle($borderStyle)
246
    {
247
        switch ($borderStyle) {
248 6
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_NONE:
249 6
                return 'none';
250 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DASHDOT:
251
                return '1px dashed';
252 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DASHDOTDOT:
253
                return '1px dotted';
254 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DASHED:
255
                return '1px dashed';
256 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DOTTED:
257
                return '1px dotted';
258 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_DOUBLE:
259
                return '3px double';
260 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_HAIR:
261
                return '1px solid';
262 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUM:
263
                return '2px solid';
264 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUMDASHDOT:
265
                return '2px dashed';
266 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUMDASHDOTDOT:
267
                return '2px dotted';
268 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUMDASHED:
269
                return '2px dashed';
270 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_SLANTDASHDOT:
271
                return '2px dashed';
272 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK:
273 4
                return '3px solid';
274 4
            case \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN:
275 4
                return '1px solid';
276
            default:
277
                // map others to thin
278
                return '1px solid';
279
        }
280
    }
281
282
    /**
283
     * Get sheet index.
284
     *
285
     * @return int
286
     */
287 4
    public function getSheetIndex()
288
    {
289 4
        return $this->sheetIndex;
290
    }
291
292
    /**
293
     * Set sheet index.
294
     *
295
     * @param    int        $pValue        Sheet index
296
     *
297
     * @return HTML
298
     */
299
    public function setSheetIndex($pValue = 0)
300
    {
301
        $this->sheetIndex = $pValue;
302
303
        return $this;
304
    }
305
306
    /**
307
     * Get sheet index.
308
     *
309
     * @return bool
310
     */
311
    public function getGenerateSheetNavigationBlock()
312
    {
313
        return $this->generateSheetNavigationBlock;
314
    }
315
316
    /**
317
     * Set sheet index.
318
     *
319
     * @param    bool        $pValue        Flag indicating whether the sheet navigation block should be generated or not
320
     *
321
     * @return HTML
322
     */
323
    public function setGenerateSheetNavigationBlock($pValue = true)
324
    {
325
        $this->generateSheetNavigationBlock = (bool) $pValue;
326
327
        return $this;
328
    }
329
330
    /**
331
     * Write all sheets (resets sheetIndex to NULL).
332
     */
333
    public function writeAllSheets()
334
    {
335
        $this->sheetIndex = null;
336
337
        return $this;
338
    }
339
340
    /**
341
     * Generate HTML header.
342
     *
343
     * @param    bool        $pIncludeStyles        Include styles?
344
     *
345
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
346
     *
347
     * @return    string
348
     */
349 6
    public function generateHTMLHeader($pIncludeStyles = false)
350
    {
351
        // Spreadsheet object known?
352 6
        if (is_null($this->spreadsheet)) {
353
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.');
354
        }
355
356
        // Construct HTML
357 6
        $properties = $this->spreadsheet->getProperties();
358 6
        $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' . PHP_EOL;
359 6
        $html .= '<!-- Generated by Spreadsheet - https://github.com/PHPOffice/Spreadsheet -->' . PHP_EOL;
360 6
        $html .= '<html>' . PHP_EOL;
361 6
        $html .= '  <head>' . PHP_EOL;
362 6
        $html .= '      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . PHP_EOL;
363 6
        if ($properties->getTitle() > '') {
364 6
            $html .= '      <title>' . htmlspecialchars($properties->getTitle()) . '</title>' . PHP_EOL;
365
        }
366 6
        if ($properties->getCreator() > '') {
367 6
            $html .= '      <meta name="author" content="' . htmlspecialchars($properties->getCreator()) . '" />' . PHP_EOL;
368
        }
369 6
        if ($properties->getTitle() > '') {
370 6
            $html .= '      <meta name="title" content="' . htmlspecialchars($properties->getTitle()) . '" />' . PHP_EOL;
371
        }
372 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...
373 5
            $html .= '      <meta name="description" content="' . htmlspecialchars($properties->getDescription()) . '" />' . PHP_EOL;
374
        }
375 6
        if ($properties->getSubject() > '') {
376 6
            $html .= '      <meta name="subject" content="' . htmlspecialchars($properties->getSubject()) . '" />' . PHP_EOL;
377
        }
378 6
        if ($properties->getKeywords() > '') {
379 6
            $html .= '      <meta name="keywords" content="' . htmlspecialchars($properties->getKeywords()) . '" />' . PHP_EOL;
380
        }
381 6
        if ($properties->getCategory() > '') {
382 6
            $html .= '      <meta name="category" content="' . htmlspecialchars($properties->getCategory()) . '" />' . PHP_EOL;
383
        }
384 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...
385 6
            $html .= '      <meta name="company" content="' . htmlspecialchars($properties->getCompany()) . '" />' . PHP_EOL;
386
        }
387 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...
388
            $html .= '      <meta name="manager" content="' . htmlspecialchars($properties->getManager()) . '" />' . PHP_EOL;
389
        }
390
391 6
        if ($pIncludeStyles) {
392 3
            $html .= $this->generateStyles(true);
393
        }
394
395 6
        $html .= '  </head>' . PHP_EOL;
396 6
        $html .= '' . PHP_EOL;
397 6
        $html .= '  <body>' . PHP_EOL;
398
399 6
        return $html;
400
    }
401
402
    /**
403
     * Generate sheet data.
404
     *
405
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
406
     *
407
     * @return    string
408
     */
409 6
    public function generateSheetData()
410
    {
411
        // Spreadsheet object known?
412 6
        if (is_null($this->spreadsheet)) {
413
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.');
414
        }
415
416
        // Ensure that Spans have been calculated?
417 6
        if ($this->sheetIndex !== null || !$this->spansAreCalculated) {
418 6
            $this->calculateSpans();
419
        }
420
421
        // Fetch sheets
422 6
        $sheets = [];
423 6 View Code Duplication
        if (is_null($this->sheetIndex)) {
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...
424
            $sheets = $this->spreadsheet->getAllSheets();
425
        } else {
426 6
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
427
        }
428
429
        // Construct HTML
430 6
        $html = '';
431
432
        // Loop all sheets
433 6
        $sheetId = 0;
434 6
        foreach ($sheets as $sheet) {
435
            // Write table header
436 6
            $html .= $this->generateTableHeader($sheet);
437
438
            // Get worksheet dimension
439 6
            $dimension = explode(':', $sheet->calculateWorksheetDimension());
440 6
            $dimension[0] = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($dimension[0]);
441 6
            $dimension[0][0] = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($dimension[0][0]) - 1;
442 6
            $dimension[1] = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($dimension[1]);
443 6
            $dimension[1][0] = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($dimension[1][0]) - 1;
444
445
            // row min,max
446 6
            $rowMin = $dimension[0][1];
447 6
            $rowMax = $dimension[1][1];
448
449
            // calculate start of <tbody>, <thead>
450 6
            $tbodyStart = $rowMin;
451 6
            $theadStart = $theadEnd = 0; // default: no <thead>    no </thead>
452 6
            if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
453
                $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop();
454
455
                // we can only support repeating rows that start at top row
456
                if ($rowsToRepeatAtTop[0] == 1) {
457
                    $theadStart = $rowsToRepeatAtTop[0];
458
                    $theadEnd = $rowsToRepeatAtTop[1];
459
                    $tbodyStart = $rowsToRepeatAtTop[1] + 1;
460
                }
461
            }
462
463
            // Loop through cells
464 6
            $row = $rowMin - 1;
465 6
            while ($row++ < $rowMax) {
466
                // <thead> ?
467 6
                if ($row == $theadStart) {
468
                    $html .= '        <thead>' . PHP_EOL;
469
                    $cellType = 'th';
470
                }
471
472
                // <tbody> ?
473 6
                if ($row == $tbodyStart) {
474 6
                    $html .= '        <tbody>' . PHP_EOL;
475 6
                    $cellType = 'td';
476
                }
477
478
                // Write row if there are HTML table cells in it
479 6
                if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
480
                    // Start a new rowData
481 6
                    $rowData = [];
482
                    // Loop through columns
483 6
                    $column = $dimension[0][0] - 1;
484 6
                    while ($column++ < $dimension[1][0]) {
485
                        // Cell exists?
486 6
                        if ($sheet->cellExistsByColumnAndRow($column, $row)) {
487 6
                            $rowData[$column] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column) . $row;
488
                        } else {
489 4
                            $rowData[$column] = '';
490
                        }
491
                    }
492 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...
493
                }
494
495
                // </thead> ?
496 6
                if ($row == $theadEnd) {
497
                    $html .= '        </thead>' . PHP_EOL;
498
                }
499
            }
500 6
            $html .= $this->extendRowsForChartsAndImages($sheet, $row);
501
502
            // Close table body.
503 6
            $html .= '        </tbody>' . PHP_EOL;
504
505
            // Write table footer
506 6
            $html .= $this->generateTableFooter();
507
508
            // Writing PDF?
509 6
            if ($this->isPdf) {
510 4
                if (is_null($this->sheetIndex) && $sheetId + 1 < $this->spreadsheet->getSheetCount()) {
511
                    $html .= '<div style="page-break-before:always" />';
512
                }
513
            }
514
515
            // Next sheet
516 6
            ++$sheetId;
517
        }
518
519 6
        return $html;
520
    }
521
522
    /**
523
     * Generate sheet tabs.
524
     *
525
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
526
     *
527
     * @return    string
528
     */
529 3
    public function generateNavigation()
530
    {
531
        // Spreadsheet object known?
532 3
        if (is_null($this->spreadsheet)) {
533
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.');
534
        }
535
536
        // Fetch sheets
537 3
        $sheets = [];
538 3 View Code Duplication
        if (is_null($this->sheetIndex)) {
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...
539
            $sheets = $this->spreadsheet->getAllSheets();
540
        } else {
541 3
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
542
        }
543
544
        // Construct HTML
545 3
        $html = '';
546
547
        // Only if there are more than 1 sheets
548 3
        if (count($sheets) > 1) {
549
            // Loop all sheets
550
            $sheetId = 0;
551
552
            $html .= '<ul class="navigation">' . PHP_EOL;
553
554
            foreach ($sheets as $sheet) {
555
                $html .= '  <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL;
556
                ++$sheetId;
557
            }
558
559
            $html .= '</ul>' . PHP_EOL;
560
        }
561
562 3
        return $html;
563
    }
564
565 6
    private function extendRowsForChartsAndImages(\PhpOffice\PhpSpreadsheet\Worksheet $pSheet, $row)
566
    {
567 6
        $rowMax = $row;
568 6
        $colMax = 'A';
569 6
        if ($this->includeCharts) {
570
            foreach ($pSheet->getChartCollection() as $chart) {
571
                if ($chart instanceof \PhpOffice\PhpSpreadsheet\Chart) {
572
                    $chartCoordinates = $chart->getTopLeftPosition();
573
                    $chartTL = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($chartCoordinates['cell']);
574
                    $chartCol = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($chartTL[0]);
575
                    if ($chartTL[1] > $rowMax) {
576
                        $rowMax = $chartTL[1];
577
                        if ($chartCol > \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($colMax)) {
578
                            $colMax = $chartTL[0];
579
                        }
580
                    }
581
                }
582
            }
583
        }
584
585 6
        foreach ($pSheet->getDrawingCollection() as $drawing) {
586 5
            if ($drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
587 4
                $imageTL = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($drawing->getCoordinates());
588 4
                $imageCol = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($imageTL[0]);
589 4
                if ($imageTL[1] > $rowMax) {
590
                    $rowMax = $imageTL[1];
591
                    if ($imageCol > \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($colMax)) {
592 5
                        $colMax = $imageTL[0];
593
                    }
594
                }
595
            }
596
        }
597 6
        $html = '';
598 6
        ++$colMax;
599 6
        while ($row <= $rowMax) {
600 6
            $html .= '<tr>';
601 6
            for ($col = 'A'; $col != $colMax; ++$col) {
602 6
                $html .= '<td>';
603 6
                $html .= $this->writeImageInCell($pSheet, $col . $row);
604 6
                if ($this->includeCharts) {
605
                    $html .= $this->writeChartInCell($pSheet, $col . $row);
606
                }
607 6
                $html .= '</td>';
608
            }
609 6
            ++$row;
610 6
            $html .= '</tr>';
611
        }
612
613 6
        return $html;
614
    }
615
616
    /**
617
     * Generate image tag in cell.
618
     *
619
     * @param    \PhpOffice\PhpSpreadsheet\Worksheet    $pSheet            \PhpOffice\PhpSpreadsheet\Worksheet
620
     * @param    string                $coordinates    Cell coordinates
621
     *
622
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
623
     *
624
     * @return    string
625
     */
626 6
    private function writeImageInCell(\PhpOffice\PhpSpreadsheet\Worksheet $pSheet, $coordinates)
627
    {
628
        // Construct HTML
629 6
        $html = '';
630
631
        // Write images
632 6
        foreach ($pSheet->getDrawingCollection() as $drawing) {
633 5
            if ($drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
634 4
                if ($drawing->getCoordinates() == $coordinates) {
635 4
                    $filename = $drawing->getPath();
636
637
                    // Strip off eventual '.'
638 4
                    if (substr($filename, 0, 1) == '.') {
639
                        $filename = substr($filename, 1);
640
                    }
641
642
                    // Prepend images root
643 4
                    $filename = $this->getImagesRoot() . $filename;
644
645
                    // Strip off eventual '.'
646 4
                    if (substr($filename, 0, 1) == '.' && substr($filename, 0, 2) != './') {
647
                        $filename = substr($filename, 1);
648
                    }
649
650
                    // Convert UTF8 data to PCDATA
651 4
                    $filename = htmlspecialchars($filename);
652
653 4
                    $html .= PHP_EOL;
654 4
                    if ((!$this->embedImages) || ($this->isPdf)) {
655 4
                        $imageData = $filename;
656
                    } else {
657
                        $imageDetails = getimagesize($filename);
658
                        if ($fp = fopen($filename, 'rb', 0)) {
659
                            $picture = fread($fp, filesize($filename));
660
                            fclose($fp);
661
                            // base64 encode the binary data, then break it
662
                            // into chunks according to RFC 2045 semantics
663
                            $base64 = chunk_split(base64_encode($picture));
664
                            $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
665
                        } else {
666
                            $imageData = $filename;
667
                        }
668
                    }
669
670 4
                    $html .= '<div style="position: relative;">';
671
                    $html .= '<img style="position: absolute; z-index: 1; left: ' .
672 4
                        $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' .
673 4
                        $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
674 4
                        $imageData . '" border="0" />';
675 4
                    $html .= '</div>';
676
                }
677
            } elseif ($drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing) {
678 1
                if ($drawing->getCoordinates() != $coordinates) {
679 1
                    continue;
680
                }
681 1
                ob_start(); //  Let's start output buffering.
682 1
                imagepng($drawing->getImageResource()); //  This will normally output the image, but because of ob_start(), it won't.
683 1
                $contents = ob_get_contents(); //  Instead, output above is saved to $contents
684 1
                ob_end_clean(); //  End the output buffer.
685
686 1
                $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
687
688
                //  Because of the nature of tables, width is more important than height.
689
                //  max-width: 100% ensures that image doesnt overflow containing cell
690
                //  width: X sets width of supplied image.
691
                //  As a result, images bigger than cell will be contained and images smaller will not get stretched
692 5
                $html .= '<img src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
693
            }
694
        }
695
696 6
        return $html;
697
    }
698
699
    /**
700
     * Generate chart tag in cell.
701
     *
702
     * @param    \PhpOffice\PhpSpreadsheet\Worksheet    $pSheet            \PhpOffice\PhpSpreadsheet\Worksheet
703
     * @param    string                $coordinates    Cell coordinates
704
     *
705
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
706
     *
707
     * @return    string
708
     */
709
    private function writeChartInCell(\PhpOffice\PhpSpreadsheet\Worksheet $pSheet, $coordinates)
710
    {
711
        // Construct HTML
712
        $html = '';
713
714
        // Write charts
715
        foreach ($pSheet->getChartCollection() as $chart) {
716
            if ($chart instanceof \PhpOffice\PhpSpreadsheet\Chart) {
717
                $chartCoordinates = $chart->getTopLeftPosition();
718
                if ($chartCoordinates['cell'] == $coordinates) {
719
                    $chartFileName = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir() . '/' . uniqid() . '.png';
720
                    if (!$chart->render($chartFileName)) {
721
                        return;
722
                    }
723
724
                    $html .= PHP_EOL;
725
                    $imageDetails = getimagesize($chartFileName);
726
                    if ($fp = fopen($chartFileName, 'rb', 0)) {
727
                        $picture = fread($fp, filesize($chartFileName));
728
                        fclose($fp);
729
                        // base64 encode the binary data, then break it
730
                        // into chunks according to RFC 2045 semantics
731
                        $base64 = chunk_split(base64_encode($picture));
732
                        $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
733
734
                        $html .= '<div style="position: relative;">';
735
                        $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;
736
                        $html .= '</div>';
737
738
                        unlink($chartFileName);
739
                    }
740
                }
741
            }
742
        }
743
744
        // Return
745
        return $html;
746
    }
747
748
    /**
749
     * Generate CSS styles.
750
     *
751
     * @param    bool    $generateSurroundingHTML    Generate surrounding HTML tags? (&lt;style&gt; and &lt;/style&gt;)
752
     *
753
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
754
     *
755
     * @return    string
756
     */
757 3
    public function generateStyles($generateSurroundingHTML = true)
758
    {
759
        // Spreadsheet object known?
760 3
        if (is_null($this->spreadsheet)) {
761
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.');
762
        }
763
764
        // Build CSS
765 3
        $css = $this->buildCSS($generateSurroundingHTML);
766
767
        // Construct HTML
768 3
        $html = '';
769
770
        // Start styles
771 3
        if ($generateSurroundingHTML) {
772 3
            $html .= '    <style type="text/css">' . PHP_EOL;
773 3
            $html .= '      html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL;
774
        }
775
776
        // Write all other styles
777 3
        foreach ($css as $styleName => $styleDefinition) {
778 3
            if ($styleName != 'html') {
779 3
                $html .= '      ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL;
780
            }
781
        }
782
783
        // End styles
784 3
        if ($generateSurroundingHTML) {
785 3
            $html .= '    </style>' . PHP_EOL;
786
        }
787
788
        // Return
789 3
        return $html;
790
    }
791
792
    /**
793
     * Build CSS styles.
794
     *
795
     * @param    bool    $generateSurroundingHTML    Generate surrounding HTML style? (html { })
796
     *
797
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
798
     *
799
     * @return    array
800
     */
801 6
    public function buildCSS($generateSurroundingHTML = true)
802
    {
803
        // Spreadsheet object known?
804 6
        if (is_null($this->spreadsheet)) {
805
            throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Internal Spreadsheet object not set to an instance of an object.');
806
        }
807
808
        // Cached?
809 6
        if (!is_null($this->cssStyles)) {
810 3
            return $this->cssStyles;
811
        }
812
813
        // Ensure that spans have been calculated
814 6
        if (!$this->spansAreCalculated) {
815 6
            $this->calculateSpans();
816
        }
817
818
        // Construct CSS
819 6
        $css = [];
820
821
        // Start styles
822 6
        if ($generateSurroundingHTML) {
823
            // html { }
824 6
            $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif';
825 6
            $css['html']['font-size'] = '11pt';
826 6
            $css['html']['background-color'] = 'white';
827
        }
828
829
        // table { }
830 6
        $css['table']['border-collapse'] = 'collapse';
831 6
        if (!$this->isPdf) {
832 3
            $css['table']['page-break-after'] = 'always';
833
        }
834
835
        // .gridlines td { }
836 6
        $css['.gridlines td']['border'] = '1px dotted black';
837 6
        $css['.gridlines th']['border'] = '1px dotted black';
838
839
        // .b {}
840 6
        $css['.b']['text-align'] = 'center'; // BOOL
841
842
        // .e {}
843 6
        $css['.e']['text-align'] = 'center'; // ERROR
844
845
        // .f {}
846 6
        $css['.f']['text-align'] = 'right'; // FORMULA
847
848
        // .inlineStr {}
849 6
        $css['.inlineStr']['text-align'] = 'left'; // INLINE
850
851
        // .n {}
852 6
        $css['.n']['text-align'] = 'right'; // NUMERIC
853
854
        // .s {}
855 6
        $css['.s']['text-align'] = 'left'; // STRING
856
857
        // Calculate cell style hashes
858 6
        foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
859 6
            $css['td.style' . $index] = $this->createCSSStyle($style);
860 6
            $css['th.style' . $index] = $this->createCSSStyle($style);
861
        }
862
863
        // Fetch sheets
864 6
        $sheets = [];
865 6 View Code Duplication
        if (is_null($this->sheetIndex)) {
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...
866
            $sheets = $this->spreadsheet->getAllSheets();
867
        } else {
868 6
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
869
        }
870
871
        // Build styles per sheet
872 6
        foreach ($sheets as $sheet) {
873
            // Calculate hash code
874 6
            $sheetIndex = $sheet->getParent()->getIndex($sheet);
875
876
            // Build styles
877
            // Calculate column widths
878 6
            $sheet->calculateColumnWidths();
879
880
            // col elements, initialize
881 6
            $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($sheet->getHighestColumn()) - 1;
882 6
            $column = -1;
883 6
            while ($column++ < $highestColumnIndex) {
884 6
                $this->columnWidths[$sheetIndex][$column] = 42; // approximation
885 6
                $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
886
            }
887
888
            // col elements, loop through columnDimensions and set width
889 6
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
890 5
                if (($width = \PhpOffice\PhpSpreadsheet\Shared\Drawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont)) >= 0) {
891 5
                    $width = \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToPoints($width);
892 5
                    $column = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($columnDimension->getColumnIndex()) - 1;
893 5
                    $this->columnWidths[$sheetIndex][$column] = $width;
894 5
                    $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt';
895
896 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...
897
                        $css['table.sheet' . $sheetIndex . ' col.col' . $column]['visibility'] = 'collapse';
898 5
                        $css['table.sheet' . $sheetIndex . ' col.col' . $column]['*display'] = 'none'; // target IE6+7
899
                    }
900
                }
901
            }
902
903
            // Default row height
904 6
            $rowDimension = $sheet->getDefaultRowDimension();
905
906
            // table.sheetN tr { }
907 6
            $css['table.sheet' . $sheetIndex . ' tr'] = [];
908
909 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...
910 6
                $pt_height = Font::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
911
            } else {
912
                $pt_height = $rowDimension->getRowHeight();
913
            }
914 6
            $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt';
915 6
            if ($rowDimension->getVisible() === false) {
916
                $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none';
917
                $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden';
918
            }
919
920
            // Calculate row heights
921 6
            foreach ($sheet->getRowDimensions() as $rowDimension) {
922 2
                $row = $rowDimension->getRowIndex() - 1;
923
924
                // table.sheetN tr.rowYYYYYY { }
925 2
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = [];
926
927 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...
928 2
                    $pt_height = Font::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
929
                } else {
930 1
                    $pt_height = $rowDimension->getRowHeight();
931
                }
932 2
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt';
933 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...
934
                    $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none';
935 6
                    $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden';
936
                }
937
            }
938
        }
939
940
        // Cache
941 6
        if (is_null($this->cssStyles)) {
942 6
            $this->cssStyles = $css;
943
        }
944
945
        // Return
946 6
        return $css;
947
    }
948
949
    /**
950
     * Create CSS style.
951
     *
952
     * @param    \PhpOffice\PhpSpreadsheet\Style        $pStyle
953
     *
954
     * @return    array
955
     */
956 6
    private function createCSSStyle(\PhpOffice\PhpSpreadsheet\Style $pStyle)
957
    {
958
        // Construct CSS
959 6
        $css = '';
0 ignored issues
show
Unused Code introduced by
$css 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...
960
961
        // Create CSS
962 6
        $css = array_merge(
963 6
            $this->createCSSStyleAlignment($pStyle->getAlignment()),
964 6
            $this->createCSSStyleBorders($pStyle->getBorders()),
965 6
            $this->createCSSStyleFont($pStyle->getFont()),
966 6
            $this->createCSSStyleFill($pStyle->getFill())
967
        );
968
969
        // Return
970 6
        return $css;
971
    }
972
973
    /**
974
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment).
975
     *
976
     * @param    \PhpOffice\PhpSpreadsheet\Style\Alignment        $pStyle            \PhpOffice\PhpSpreadsheet\Style\Alignment
977
     *
978
     * @return    array
979
     */
980 6
    private function createCSSStyleAlignment(\PhpOffice\PhpSpreadsheet\Style\Alignment $pStyle)
981
    {
982
        // Construct CSS
983 6
        $css = [];
984
985
        // Create CSS
986 6
        $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical());
987 6
        if ($textAlign = $this->mapHAlign($pStyle->getHorizontal())) {
988 5
            $css['text-align'] = $textAlign;
989 5
            if (in_array($textAlign, ['left', 'right'])) {
990 4
                $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px';
991
            }
992
        }
993
994 6
        return $css;
995
    }
996
997
    /**
998
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font).
999
     *
1000
     * @param    \PhpOffice\PhpSpreadsheet\Style\Font        $pStyle            \PhpOffice\PhpSpreadsheet\Style\Font
1001
     *
1002
     * @return    array
1003
     */
1004 6
    private function createCSSStyleFont(\PhpOffice\PhpSpreadsheet\Style\Font $pStyle)
1005
    {
1006
        // Construct CSS
1007 6
        $css = [];
1008
1009
        // Create CSS
1010 6
        if ($pStyle->getBold()) {
1011 5
            $css['font-weight'] = 'bold';
1012
        }
1013 6
        if ($pStyle->getUnderline() != \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) {
1014
            $css['text-decoration'] = 'underline line-through';
1015 6
        } elseif ($pStyle->getUnderline() != \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE) {
1016 4
            $css['text-decoration'] = 'underline';
1017 6
        } elseif ($pStyle->getStrikethrough()) {
1018
            $css['text-decoration'] = 'line-through';
1019
        }
1020 6
        if ($pStyle->getItalic()) {
1021 4
            $css['font-style'] = 'italic';
1022
        }
1023
1024 6
        $css['color'] = '#' . $pStyle->getColor()->getRGB();
1025 6
        $css['font-family'] = '\'' . $pStyle->getName() . '\'';
1026 6
        $css['font-size'] = $pStyle->getSize() . 'pt';
1027
1028 6
        return $css;
1029
    }
1030
1031
    /**
1032
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Borders).
1033
     *
1034
     * @param    \PhpOffice\PhpSpreadsheet\Style\Borders        $pStyle            \PhpOffice\PhpSpreadsheet\Style\Borders
1035
     *
1036
     * @return    array
1037
     */
1038 6
    private function createCSSStyleBorders(\PhpOffice\PhpSpreadsheet\Style\Borders $pStyle)
1039
    {
1040
        // Construct CSS
1041 6
        $css = [];
1042
1043
        // Create CSS
1044 6
        $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom());
1045 6
        $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop());
1046 6
        $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft());
1047 6
        $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight());
1048
1049 6
        return $css;
1050
    }
1051
1052
    /**
1053
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Border).
1054
     *
1055
     * @param    \PhpOffice\PhpSpreadsheet\Style\Border        $pStyle            \PhpOffice\PhpSpreadsheet\Style\Border
1056
     *
1057
     * @return    string
1058
     */
1059 6
    private function createCSSStyleBorder(\PhpOffice\PhpSpreadsheet\Style\Border $pStyle)
1060
    {
1061
        //    Create CSS - add !important to non-none border styles for merged cells
1062 6
        $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle());
1063 6
        $css = $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
1064
1065 6
        return $css;
1066
    }
1067
1068
    /**
1069
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Fill).
1070
     *
1071
     * @param    \PhpOffice\PhpSpreadsheet\Style\Fill        $pStyle            \PhpOffice\PhpSpreadsheet\Style\Fill
1072
     *
1073
     * @return    array
1074
     */
1075 6
    private function createCSSStyleFill(\PhpOffice\PhpSpreadsheet\Style\Fill $pStyle)
1076
    {
1077
        // Construct HTML
1078 6
        $css = [];
1079
1080
        // Create CSS
1081 6
        $value = $pStyle->getFillType() == \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_NONE ?
1082 6
            'white' : '#' . $pStyle->getStartColor()->getRGB();
1083 6
        $css['background-color'] = $value;
1084
1085 6
        return $css;
1086
    }
1087
1088
    /**
1089
     * Generate HTML footer.
1090
     */
1091 6
    public function generateHTMLFooter()
1092
    {
1093
        // Construct HTML
1094 6
        $html = '';
1095 6
        $html .= '  </body>' . PHP_EOL;
1096 6
        $html .= '</html>' . PHP_EOL;
1097
1098 6
        return $html;
1099
    }
1100
1101
    /**
1102
     * Generate table header.
1103
     *
1104
     * @param    \PhpOffice\PhpSpreadsheet\Worksheet    $pSheet        The worksheet for the table we are writing
1105
     *
1106
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
1107
     *
1108
     * @return    string
1109
     */
1110 6
    private function generateTableHeader($pSheet)
1111
    {
1112 6
        $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
1113
1114
        // Construct HTML
1115 6
        $html = '';
1116 6
        $html .= $this->setMargins($pSheet);
1117
1118 6
        if (!$this->useInlineCss) {
1119 3
            $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : '';
1120 3
            $html .= '    <table border="0" cellpadding="0" cellspacing="0" id="sheet' . $sheetIndex . '" class="sheet' . $sheetIndex . $gridlines . '">' . PHP_EOL;
1121
        } else {
1122 4
            $style = isset($this->cssStyles['table']) ?
1123 4
                $this->assembleCSS($this->cssStyles['table']) : '';
1124
1125 4
            if ($this->isPdf && $pSheet->getShowGridlines()) {
1126 1
                $html .= '    <table border="1" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="1" style="' . $style . '">' . PHP_EOL;
1127
            } else {
1128 3
                $html .= '    <table border="0" cellpadding="1" id="sheet' . $sheetIndex . '" cellspacing="0" style="' . $style . '">' . PHP_EOL;
1129
            }
1130
        }
1131
1132
        // Write <col> elements
1133 6
        $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($pSheet->getHighestColumn()) - 1;
1134 6
        $i = -1;
1135 6
        while ($i++ < $highestColumnIndex) {
1136 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...
1137 3
                if (!$this->useInlineCss) {
1138 3
                    $html .= '        <col class="col' . $i . '">' . PHP_EOL;
1139
                } else {
1140
                    $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
1141
                        $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
1142
                    $html .= '        <col style="' . $style . '">' . PHP_EOL;
1143
                }
1144
            }
1145
        }
1146
1147 6
        return $html;
1148
    }
1149
1150
    /**
1151
     * Generate table footer.
1152
     *
1153
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
1154
     */
1155 6
    private function generateTableFooter()
1156
    {
1157 6
        $html = '    </table>' . PHP_EOL;
1158
1159 6
        return $html;
1160
    }
1161
1162
    /**
1163
     * Generate row.
1164
     *
1165
     * @param    \PhpOffice\PhpSpreadsheet\Worksheet    $pSheet            \PhpOffice\PhpSpreadsheet\Worksheet
1166
     * @param    array                $pValues        Array containing cells in a row
1167
     * @param    int                    $pRow            Row number (0-based)
1168
     * @param mixed $cellType
1169
     *
1170
     * @throws    \PhpOffice\PhpSpreadsheet\Writer\Exception
1171
     *
1172
     * @return    string
1173
     */
1174 6
    private function generateRow(\PhpOffice\PhpSpreadsheet\Worksheet $pSheet, $pValues = null, $pRow = 0, $cellType = 'td')
1175
    {
1176 6
        if (is_array($pValues)) {
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 = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($colNum) . ($pRow + 1);
1215 6
                if (!$this->useInlineCss) {
1216 3
                    $cssClass = '';
0 ignored issues
show
Unused Code introduced by
$cssClass 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...
1217 3
                    $cssClass = 'column' . $colNum;
1218
                } else {
1219 4
                    $cssClass = [];
1220 4
                    if ($cellType == 'th') {
1221 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...
1222
                            $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
1223
                        }
1224 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...
1225 4
                        if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
1226
                            $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
1227
                        }
1228
                    }
1229
                }
1230 6
                $colSpan = 1;
1231 6
                $rowSpan = 1;
1232
1233
                // initialize
1234 6
                $cellData = '&nbsp;';
1235
1236
                // \PhpOffice\PhpSpreadsheet\Cell
1237 6
                if ($cell instanceof \PhpOffice\PhpSpreadsheet\Cell) {
1238 6
                    $cellData = '';
1239 6
                    if (is_null($cell->getParent())) {
1240
                        $cell->attach($pSheet);
0 ignored issues
show
Documentation introduced by
$pSheet is of type object<PhpOffice\PhpSpreadsheet\Worksheet>, but the function expects a object<PhpOffice\PhpSpre...bjectStorage\CacheBase>.

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...
1241
                    }
1242
                    // Value
1243 6
                    if ($cell->getValue() instanceof \PhpOffice\PhpSpreadsheet\RichText) {
1244
                        // Loop through rich text elements
1245 5
                        $elements = $cell->getValue()->getRichTextElements();
1246 5
                        foreach ($elements as $element) {
1247
                            // Rich text start?
1248 5
                            if ($element instanceof \PhpOffice\PhpSpreadsheet\RichText\Run) {
1249 5
                                $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">';
1250
1251 5
                                if ($element->getFont()->getSuperScript()) {
1252
                                    $cellData .= '<sup>';
1253 5
                                } elseif ($element->getFont()->getSubScript()) {
1254
                                    $cellData .= '<sub>';
1255
                                }
1256
                            }
1257
1258
                            // Convert UTF8 data to PCDATA
1259 5
                            $cellText = $element->getText();
1260 5
                            $cellData .= htmlspecialchars($cellText);
1261
1262 5
                            if ($element instanceof \PhpOffice\PhpSpreadsheet\RichText\Run) {
1263 5
                                if ($element->getFont()->getSuperScript()) {
1264
                                    $cellData .= '</sup>';
1265 5
                                } elseif ($element->getFont()->getSubScript()) {
1266
                                    $cellData .= '</sub>';
1267
                                }
1268
1269 5
                                $cellData .= '</span>';
1270
                            }
1271
                        }
1272
                    } else {
1273 6
                        if ($this->preCalculateFormulas) {
1274 6
                            $cellData = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::toFormattedString(
1275 6
                                $cell->getCalculatedValue(),
1276 6
                                $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
1277 6
                                [$this, 'formatColor']
1278
                            );
1279
                        } else {
1280
                            $cellData = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::toFormattedString(
1281
                                $cell->getValue(),
1282
                                $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
1283
                                [$this, 'formatColor']
1284
                            );
1285
                        }
1286 6
                        $cellData = htmlspecialchars($cellData);
1287 6
                        if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperScript()) {
1288
                            $cellData = '<sup>' . $cellData . '</sup>';
1289 6
                        } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubScript()) {
1290
                            $cellData = '<sub>' . $cellData . '</sub>';
1291
                        }
1292
                    }
1293
1294
                    // Converts the cell content so that spaces occuring at beginning of each new line are replaced by &nbsp;
1295
                    // Example: "  Hello\n to the world" is converted to "&nbsp;&nbsp;Hello\n&nbsp;to the world"
1296 6
                    $cellData = preg_replace('/(?m)(?:^|\\G) /', '&nbsp;', $cellData);
1297
1298
                    // convert newline "\n" to '<br>'
1299 6
                    $cellData = nl2br($cellData);
1300
1301
                    // Extend CSS class?
1302 6
                    if (!$this->useInlineCss) {
1303 3
                        $cssClass .= ' style' . $cell->getXfIndex();
1304 3
                        $cssClass .= ' ' . $cell->getDataType();
1305
                    } else {
1306 4
                        if ($cellType == 'th') {
1307 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...
1308
                                $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
1309
                            }
1310 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...
1311 4
                            if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
1312 4
                                $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
1313
                            }
1314
                        }
1315
1316
                        // General horizontal alignment: Actual horizontal alignment depends on dataType
1317 4
                        $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex());
1318 4
                        if ($sharedStyle->getAlignment()->getHorizontal() == \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_GENERAL
1319 4
                            && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])) {
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 = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($colNum + $colSpan - 1) . ($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
        throw new \PhpOffice\PhpSpreadsheet\Writer\Exception('Invalid parameters passed.');
1414
    }
1415
1416
    /**
1417
     * Takes array where of CSS properties / values and converts to CSS string.
1418
     *
1419
     * @param array
1420
     * @param mixed $pValue
1421
     *
1422
     * @return string
1423
     */
1424 6
    private function assembleCSS($pValue = [])
1425
    {
1426 6
        $pairs = [];
1427 6
        foreach ($pValue as $property => $value) {
1428 6
            $pairs[] = $property . ':' . $value;
1429
        }
1430 6
        $string = implode('; ', $pairs);
1431
1432 6
        return $string;
1433
    }
1434
1435
    /**
1436
     * Get images root.
1437
     *
1438
     * @return string
1439
     */
1440 4
    public function getImagesRoot()
1441
    {
1442 4
        return $this->imagesRoot;
1443
    }
1444
1445
    /**
1446
     * Set images root.
1447
     *
1448
     * @param string $pValue
1449
     *
1450
     * @return HTML
1451
     */
1452
    public function setImagesRoot($pValue = '.')
1453
    {
1454
        $this->imagesRoot = $pValue;
1455
1456
        return $this;
1457
    }
1458
1459
    /**
1460
     * Get embed images.
1461
     *
1462
     * @return bool
1463
     */
1464
    public function getEmbedImages()
1465
    {
1466
        return $this->embedImages;
1467
    }
1468
1469
    /**
1470
     * Set embed images.
1471
     *
1472
     * @param bool $pValue
1473
     *
1474
     * @return HTML
1475
     */
1476
    public function setEmbedImages($pValue = true)
1477
    {
1478
        $this->embedImages = $pValue;
1479
1480
        return $this;
1481
    }
1482
1483
    /**
1484
     * Get use inline CSS?
1485
     *
1486
     * @return bool
1487
     */
1488
    public function getUseInlineCss()
1489
    {
1490
        return $this->useInlineCss;
1491
    }
1492
1493
    /**
1494
     * Set use inline CSS?
1495
     *
1496
     * @param bool $pValue
1497
     *
1498
     * @return HTML
1499
     */
1500 4
    public function setUseInlineCss($pValue = false)
1501
    {
1502 4
        $this->useInlineCss = $pValue;
1503
1504 4
        return $this;
1505
    }
1506
1507
    /**
1508
     * Add color to formatted string as inline style.
1509
     *
1510
     * @param string $pValue Plain formatted value without color
1511
     * @param string $pFormat Format code
1512
     *
1513
     * @return string
1514
     */
1515 4
    public function formatColor($pValue, $pFormat)
1516
    {
1517
        // Color information, e.g. [Red] is always at the beginning
1518 4
        $color = null; // initialize
1519 4
        $matches = [];
1520
1521 4
        $color_regex = '/^\\[[a-zA-Z]+\\]/';
1522 4
        if (preg_match($color_regex, $pFormat, $matches)) {
1523
            $color = str_replace('[', '', $matches[0]);
1524
            $color = str_replace(']', '', $color);
1525
            $color = strtolower($color);
1526
        }
1527
1528
        // convert to PCDATA
1529 4
        $value = htmlspecialchars($pValue);
1530
1531
        // color span tag
1532 4
        if ($color !== null) {
1533
            $value = '<span style="color:' . $color . '">' . $value . '</span>';
1534
        }
1535
1536 4
        return $value;
1537
    }
1538
1539
    /**
1540
     * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
1541
     */
1542 6
    private function calculateSpans()
1543
    {
1544
        // Identify all cells that should be omitted in HTML due to cell merge.
1545
        // In HTML only the upper-left cell should be written and it should have
1546
        //   appropriate rowspan / colspan attribute
1547 6
        $sheetIndexes = $this->sheetIndex !== null ?
1548 6
            [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
1549
1550 6
        foreach ($sheetIndexes as $sheetIndex) {
1551 6
            $sheet = $this->spreadsheet->getSheet($sheetIndex);
1552
1553 6
            $candidateSpannedRow = [];
1554
1555
            // loop through all Excel merged cells
1556 6
            foreach ($sheet->getMergeCells() as $cells) {
1557 5
                list($cells) = \PhpOffice\PhpSpreadsheet\Cell::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...
1558 5
                $first = $cells[0];
1559 5
                $last = $cells[1];
1560
1561 5
                list($fc, $fr) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($first);
1562 5
                $fc = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($fc) - 1;
1563
1564 5
                list($lc, $lr) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($last);
1565 5
                $lc = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($lc) - 1;
1566
1567
                // loop through the individual cells in the individual merge
1568 5
                $r = $fr - 1;
1569 5
                while ($r++ < $lr) {
1570
                    // also, flag this row as a HTML row that is candidate to be omitted
1571 5
                    $candidateSpannedRow[$r] = $r;
1572
1573 5
                    $c = $fc - 1;
1574 5
                    while ($c++ < $lc) {
1575 5
                        if (!($c == $fc && $r == $fr)) {
1576
                            // not the upper-left cell (should not be written in HTML)
1577 5
                            $this->isSpannedCell[$sheetIndex][$r][$c] = [
1578 5
                                'baseCell' => [$fr, $fc],
1579
                            ];
1580
                        } else {
1581
                            // upper-left is the base cell that should hold the colspan/rowspan attribute
1582 5
                            $this->isBaseCell[$sheetIndex][$r][$c] = [
1583 5
                                'xlrowspan' => $lr - $fr + 1, // Excel rowspan
1584 5
                                'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
1585 5
                                'xlcolspan' => $lc - $fc + 1, // Excel colspan
1586 5
                                'colspan' => $lc - $fc + 1, // HTML colspan, value may change
1587
                            ];
1588
                        }
1589
                    }
1590
                }
1591
            }
1592
1593
            // Identify which rows should be omitted in HTML. These are the rows where all the cells
1594
            //   participate in a merge and the where base cells are somewhere above.
1595 6
            $countColumns = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($sheet->getHighestColumn());
1596 6
            foreach ($candidateSpannedRow as $rowIndex) {
1597 5
                if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
1598 5
                    if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
1599 5
                        $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
1600
                    }
1601
                }
1602
            }
1603
1604
            // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
1605 6
            if (isset($this->isSpannedRow[$sheetIndex])) {
1606 4
                foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
1607 4
                    $adjustedBaseCells = [];
1608 4
                    $c = -1;
1609 4
                    $e = $countColumns - 1;
1610 6
                    while ($c++ < $e) {
1611 4
                        $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
1612
1613 4
                        if (!in_array($baseCell, $adjustedBaseCells)) {
1614
                            // subtract rowspan by 1
1615 4
                            --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
1616 4
                            $adjustedBaseCells[] = $baseCell;
1617
                        }
1618
                    }
1619
                }
1620
            }
1621
1622
            // TODO: Same for columns
1623
        }
1624
1625
        // We have calculated the spans
1626 6
        $this->spansAreCalculated = true;
1627 6
    }
1628
1629 6
    private function setMargins(\PhpOffice\PhpSpreadsheet\Worksheet $pSheet)
1630
    {
1631 6
        $htmlPage = '@page { ';
1632 6
        $htmlBody = 'body { ';
1633
1634 6
        $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; ';
1635 6
        $htmlPage .= 'margin-left: ' . $left;
1636 6
        $htmlBody .= 'margin-left: ' . $left;
1637 6
        $right = StringHelper::formatNumber($pSheet->getPageMargins()->getRight()) . 'in; ';
1638 6
        $htmlPage .= 'margin-right: ' . $right;
1639 6
        $htmlBody .= 'margin-right: ' . $right;
1640 6
        $top = StringHelper::formatNumber($pSheet->getPageMargins()->getTop()) . 'in; ';
1641 6
        $htmlPage .= 'margin-top: ' . $top;
1642 6
        $htmlBody .= 'margin-top: ' . $top;
1643 6
        $bottom = StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()) . 'in; ';
1644 6
        $htmlPage .= 'margin-bottom: ' . $bottom;
1645 6
        $htmlBody .= 'margin-bottom: ' . $bottom;
1646
1647 6
        $htmlPage .= "}\n";
1648 6
        $htmlBody .= "}\n";
1649
1650 6
        return "<style>\n" . $htmlPage . $htmlBody . "</style>\n";
1651
    }
1652
}
1653