Failed Conditions
Push — master ( a2bb82...a189d9 )
by Adrien
10:27 queued 01:00
created

Xlsx::parseRichText()   D

Complexity

Conditions 26

Size

Total Lines 61
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 34.0756

Importance

Changes 0
Metric Value
cc 26
eloc 39
c 0
b 0
f 0
nop 1
dl 0
loc 61
ccs 27
cts 35
cp 0.7714
crap 34.0756
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6
use PhpOffice\PhpSpreadsheet\Cell\DataType;
7
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
8
use PhpOffice\PhpSpreadsheet\DefinedName;
9
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
10
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter;
11
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
12
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes;
13
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles;
14
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations;
15
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
16
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
17
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
18
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
19
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
20
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
21
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
22
use PhpOffice\PhpSpreadsheet\RichText\RichText;
23
use PhpOffice\PhpSpreadsheet\Settings;
24
use PhpOffice\PhpSpreadsheet\Shared\Date;
25
use PhpOffice\PhpSpreadsheet\Shared\Drawing;
26
use PhpOffice\PhpSpreadsheet\Shared\File;
27
use PhpOffice\PhpSpreadsheet\Shared\Font;
28
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
29
use PhpOffice\PhpSpreadsheet\Spreadsheet;
30
use PhpOffice\PhpSpreadsheet\Style\Border;
31
use PhpOffice\PhpSpreadsheet\Style\Borders;
32
use PhpOffice\PhpSpreadsheet\Style\Color;
33
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
34
use PhpOffice\PhpSpreadsheet\Style\Protection;
35
use PhpOffice\PhpSpreadsheet\Style\Style;
36
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
37
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
38
use SimpleXMLElement;
39
use stdClass;
40
use Throwable;
41
use XMLReader;
42
use ZipArchive;
43
44
class Xlsx extends BaseReader
45
{
46
    /**
47
     * ReferenceHelper instance.
48
     *
49
     * @var ReferenceHelper
50
     */
51
    private $referenceHelper;
52
53
    /**
54
     * Xlsx\Theme instance.
55
     *
56
     * @var Xlsx\Theme
57
     */
58
    private static $theme = null;
59
60
    /**
61
     * Create a new Xlsx Reader instance.
62
     */
63 135
    public function __construct()
64
    {
65 135
        parent::__construct();
66 135
        $this->referenceHelper = ReferenceHelper::getInstance();
67 135
        $this->securityScanner = XmlScanner::getInstance($this);
68 135
    }
69
70
    /**
71
     * Can the current IReader read the file?
72
     *
73
     * @param string $pFilename
74
     *
75
     * @return bool
76
     */
77 11
    public function canRead($pFilename)
78
    {
79 11
        File::assertFile($pFilename);
80
81 11
        $result = false;
82 11
        $zip = new ZipArchive();
83
84 11
        if ($zip->open($pFilename) === true) {
85 7
            $workbookBasename = $this->getWorkbookBaseName($zip);
86 7
            $result = !empty($workbookBasename);
87
88 7
            $zip->close();
89
        }
90
91 11
        return $result;
92
    }
93
94
    /**
95
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
96
     *
97
     * @param string $pFilename
98
     *
99
     * @return array
100
     */
101 1
    public function listWorksheetNames($pFilename)
102
    {
103 1
        File::assertFile($pFilename);
104
105 1
        $worksheetNames = [];
106
107 1
        $zip = new ZipArchive();
108 1
        $zip->open($pFilename);
109
110
        //    The files we're looking at here are small enough that simpleXML is more efficient than XMLReader
111
        //~ http://schemas.openxmlformats.org/package/2006/relationships");
112 1
        $rels = simplexml_load_string(
113 1
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels'))
114
        );
115 1
        foreach ($rels->Relationship as $rel) {
116 1
            switch ($rel['Type']) {
117 1
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
118
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
119 1
                    $xmlWorkbook = simplexml_load_string(
120 1
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}"))
121
                    );
122
123 1
                    if ($xmlWorkbook->sheets) {
124 1
                        foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
125
                            // Check if sheet should be skipped
126 1
                            $worksheetNames[] = (string) $eleSheet['name'];
127
                        }
128
                    }
129
            }
130
        }
131
132 1
        $zip->close();
133
134 1
        return $worksheetNames;
135
    }
136
137
    /**
138
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
139
     *
140
     * @param string $pFilename
141
     *
142
     * @return array
143
     */
144 2
    public function listWorksheetInfo($pFilename)
145
    {
146 2
        File::assertFile($pFilename);
147
148 2
        $worksheetInfo = [];
149
150 2
        $zip = new ZipArchive();
151 2
        $zip->open($pFilename);
152
153
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
154 2
        $rels = simplexml_load_string(
155 2
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
156 2
            'SimpleXMLElement',
157 2
            Settings::getLibXmlLoaderOptions()
158
        );
159 2
        foreach ($rels->Relationship as $rel) {
160 2
            if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') {
161 2
                $dir = dirname($rel['Target']);
162
163
                //~ http://schemas.openxmlformats.org/package/2006/relationships"
164 2
                $relsWorkbook = simplexml_load_string(
165 2
                    $this->securityScanner->scan(
166 2
                        $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')
167
                    ),
168 2
                    'SimpleXMLElement',
169 2
                    Settings::getLibXmlLoaderOptions()
170
                );
171 2
                $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
172
173 2
                $worksheets = [];
174 2
                foreach ($relsWorkbook->Relationship as $ele) {
175 2
                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') {
176 2
                        $worksheets[(string) $ele['Id']] = $ele['Target'];
177
                    }
178
                }
179
180
                //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
181 2
                $xmlWorkbook = simplexml_load_string(
182 2
                    $this->securityScanner->scan(
183 2
                        $this->getFromZipArchive($zip, "{$rel['Target']}")
184
                    ),
185 2
                    'SimpleXMLElement',
186 2
                    Settings::getLibXmlLoaderOptions()
187
                );
188 2
                if ($xmlWorkbook->sheets) {
189 2
                    $dir = dirname($rel['Target']);
190
                    /** @var SimpleXMLElement $eleSheet */
191 2
                    foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
192
                        $tmpInfo = [
193 2
                            'worksheetName' => (string) $eleSheet['name'],
194 2
                            'lastColumnLetter' => 'A',
195 2
                            'lastColumnIndex' => 0,
196 2
                            'totalRows' => 0,
197 2
                            'totalColumns' => 0,
198
                        ];
199
200 2
                        $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
1 ignored issue
show
Bug introduced by
The method attributes() does not exist on null. ( Ignorable by Annotation )

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

200
                        $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->/** @scrutinizer ignore-call */ attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
201
202 2
                        $xml = new XMLReader();
203 2
                        $xml->xml(
204 2
                            $this->securityScanner->scanFile(
205 2
                                'zip://' . File::realpath($pFilename) . '#' . "$dir/$fileWorksheet"
206
                            ),
207 2
                            null,
208 2
                            Settings::getLibXmlLoaderOptions()
209
                        );
210 2
                        $xml->setParserProperty(2, true);
211
212 2
                        $currCells = 0;
213 2
                        while ($xml->read()) {
214 2
                            if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) {
215 2
                                $row = $xml->getAttribute('r');
216 2
                                $tmpInfo['totalRows'] = $row;
217 2
                                $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
218 2
                                $currCells = 0;
219 2
                            } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) {
220 2
                                ++$currCells;
221
                            }
222
                        }
223 2
                        $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
224 2
                        $xml->close();
225
226 2
                        $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
227 2
                        $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
228
229 2
                        $worksheetInfo[] = $tmpInfo;
230
                    }
231
                }
232
            }
233
        }
234
235 2
        $zip->close();
236
237 2
        return $worksheetInfo;
238
    }
239
240 5
    private static function castToBoolean($c)
241
    {
242 5
        $value = isset($c->v) ? (string) $c->v : null;
243 5
        if ($value == '0') {
244
            return false;
245 5
        } elseif ($value == '1') {
246 5
            return true;
247
        }
248
249
        return (bool) $c->v;
250
    }
251
252 5
    private static function castToError($c)
253
    {
254 5
        return isset($c->v) ? (string) $c->v : null;
255
    }
256
257 98
    private static function castToString($c)
258
    {
259 98
        return isset($c->v) ? (string) $c->v : null;
260
    }
261
262 54
    private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType): void
263
    {
264 54
        $cellDataType = 'f';
265 54
        $value = "={$c->f}";
266 54
        $calculatedValue = self::$castBaseType($c);
267
268
        // Shared formula?
269 54
        if (isset($c->f['t']) && strtolower((string) $c->f['t']) == 'shared') {
270 4
            $instance = (string) $c->f['si'];
271
272 4
            if (!isset($sharedFormulas[(string) $c->f['si']])) {
273 4
                $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value];
274
            } else {
275 4
                $master = Coordinate::coordinateFromString($sharedFormulas[$instance]['master']);
276 4
                $current = Coordinate::coordinateFromString($r);
277
278 4
                $difference = [0, 0];
279 4
                $difference[0] = Coordinate::columnIndexFromString($current[0]) - Coordinate::columnIndexFromString($master[0]);
280 4
                $difference[1] = $current[1] - $master[1];
281
282 4
                $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]);
283
            }
284
        }
285 54
    }
286
287
    /**
288
     * @param string $fileName
289
     *
290
     * @return string
291
     */
292 127
    private function getFromZipArchive(ZipArchive $archive, $fileName = '')
293
    {
294
        // Root-relative paths
295 127
        if (strpos($fileName, '//') !== false) {
296
            $fileName = substr($fileName, strpos($fileName, '//') + 1);
297
        }
298 127
        $fileName = File::realpath($fileName);
299
300
        // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
301
        //    so we need to load case-insensitively from the zip file
302
303
        // Apache POI fixes
304 127
        $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
305 127
        if ($contents === false) {
306 2
            $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
307
        }
308
309 127
        return $contents;
310
    }
311
312
    /**
313
     * Loads Spreadsheet from file.
314
     *
315
     * @param string $pFilename
316
     *
317
     * @return Spreadsheet
318
     */
319 123
    public function load($pFilename)
320
    {
321 123
        File::assertFile($pFilename);
322
323
        // Initialisations
324 123
        $excel = new Spreadsheet();
325 123
        $excel->removeSheetByIndex(0);
326 123
        if (!$this->readDataOnly) {
327 123
            $excel->removeCellStyleXfByIndex(0); // remove the default style
328 123
            $excel->removeCellXfByIndex(0); // remove the default style
329
        }
330 123
        $unparsedLoadedData = [];
331
332 123
        $zip = new ZipArchive();
333 123
        $zip->open($pFilename);
334
335
        //    Read the theme first, because we need the colour scheme when reading the styles
336
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
337 123
        $workbookBasename = $this->getWorkbookBaseName($zip);
338 123
        $wbRels = simplexml_load_string(
339 123
            $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")),
340 123
            'SimpleXMLElement',
341 123
            Settings::getLibXmlLoaderOptions()
342
        );
343 123
        foreach ($wbRels->Relationship as $rel) {
344 123
            switch ($rel['Type']) {
345 123
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
346 123
                    $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
347 123
                    $themeOrderAdditional = count($themeOrderArray);
348
349 123
                    $xmlTheme = simplexml_load_string(
350 123
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")),
351 123
                        'SimpleXMLElement',
352 123
                        Settings::getLibXmlLoaderOptions()
353
                    );
354 123
                    if (is_object($xmlTheme)) {
355 123
                        $xmlThemeName = $xmlTheme->attributes();
356 123
                        $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
357 123
                        $themeName = (string) $xmlThemeName['name'];
358
359 123
                        $colourScheme = $xmlTheme->themeElements->clrScheme->attributes();
360 123
                        $colourSchemeName = (string) $colourScheme['name'];
361 123
                        $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
362
363 123
                        $themeColours = [];
364 123
                        foreach ($colourScheme as $k => $xmlColour) {
365 123
                            $themePos = array_search($k, $themeOrderArray);
366 123
                            if ($themePos === false) {
367 123
                                $themePos = $themeOrderAdditional++;
368
                            }
369 123
                            if (isset($xmlColour->sysClr)) {
370 122
                                $xmlColourData = $xmlColour->sysClr->attributes();
371 122
                                $themeColours[$themePos] = $xmlColourData['lastClr'];
372 123
                            } elseif (isset($xmlColour->srgbClr)) {
373 123
                                $xmlColourData = $xmlColour->srgbClr->attributes();
374 123
                                $themeColours[$themePos] = $xmlColourData['val'];
375
                            }
376
                        }
377 123
                        self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours);
378
                    }
379
380 123
                    break;
381
            }
382
        }
383
384
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
385 123
        $rels = simplexml_load_string(
386 123
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
387 123
            'SimpleXMLElement',
388 123
            Settings::getLibXmlLoaderOptions()
389
        );
390
391 123
        $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties());
392 123
        foreach ($rels->Relationship as $rel) {
393 123
            switch ($rel['Type']) {
394 123
                case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties':
395 121
                    $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
396
397 121
                    break;
398 123
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties':
399 121
                    $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
400
401 121
                    break;
402 123
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties':
403 4
                    $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
404
405 4
                    break;
406
                //Ribbon
407 123
                case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility':
408
                    $customUI = $rel['Target'];
409
                    if ($customUI !== null) {
410
                        $this->readRibbon($excel, $customUI, $zip);
411
                    }
412
413
                    break;
414 123
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
415 123
                    $dir = dirname($rel['Target']);
416
                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
417 123
                    $relsWorkbook = simplexml_load_string(
418 123
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')),
419 123
                        'SimpleXMLElement',
420 123
                        Settings::getLibXmlLoaderOptions()
421
                    );
422 123
                    $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
423
424 123
                    $sharedStrings = [];
425 123
                    $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));
426 123
                    if ($xpath) {
427
                        //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
428 116
                        $xmlStrings = simplexml_load_string(
429 116
                            $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
430 116
                            'SimpleXMLElement',
431 116
                            Settings::getLibXmlLoaderOptions()
432
                        );
433 116
                        if (isset($xmlStrings->si)) {
434 80
                            foreach ($xmlStrings->si as $val) {
435 80
                                if (isset($val->t)) {
436 80
                                    $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
437 4
                                } elseif (isset($val->r)) {
438 4
                                    $sharedStrings[] = $this->parseRichText($val);
439
                                }
440
                            }
441
                        }
442
                    }
443
444 123
                    $worksheets = [];
445 123
                    $macros = $customUI = null;
446 123
                    foreach ($relsWorkbook->Relationship as $ele) {
447 123
                        switch ($ele['Type']) {
448 123
                            case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':
449 123
                                $worksheets[(string) $ele['Id']] = $ele['Target'];
450
451 123
                                break;
452
                            // a vbaProject ? (: some macros)
453 123
                            case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject':
454 1
                                $macros = $ele['Target'];
455
456 1
                                break;
457
                        }
458
                    }
459
460 123
                    if ($macros !== null) {
461 1
                        $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin
462 1
                        if ($macrosCode !== false) {
463 1
                            $excel->setMacrosCode($macrosCode);
464 1
                            $excel->setHasMacros(true);
465
                            //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir
466 1
                            $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin');
467 1
                            if ($Certificate !== false) {
468
                                $excel->setMacrosCertificate($Certificate);
469
                            }
470
                        }
471
                    }
472
473 123
                    $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']"));
474
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
475 123
                    $xmlStyles = simplexml_load_string(
476 123
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
477 123
                        'SimpleXMLElement',
478 123
                        Settings::getLibXmlLoaderOptions()
479
                    );
480
481 123
                    $styles = [];
482 123
                    $cellStyles = [];
483 123
                    $numFmts = null;
484 123
                    if ($xmlStyles && $xmlStyles->numFmts[0]) {
485 65
                        $numFmts = $xmlStyles->numFmts[0];
486
                    }
487 123
                    if (isset($numFmts) && ($numFmts !== null)) {
488 65
                        $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
489
                    }
490 123
                    if (!$this->readDataOnly && $xmlStyles) {
491 123
                        foreach ($xmlStyles->cellXfs->xf as $xf) {
492 123
                            $numFmt = null;
493
494 123
                            if ($xf['numFmtId']) {
495 123
                                if (isset($numFmts)) {
496 65
                                    $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
497
498 65
                                    if (isset($tmpNumFmt['formatCode'])) {
499 9
                                        $numFmt = (string) $tmpNumFmt['formatCode'];
500
                                    }
501
                                }
502
503
                                // We shouldn't override any of the built-in MS Excel values (values below id 164)
504
                                //  But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used
505
                                //  So we make allowance for them rather than lose formatting masks
506
                                if (
507 123
                                    $numFmt === null &&
508 123
                                    (int) $xf['numFmtId'] < 164 &&
509 123
                                    NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== ''
510
                                ) {
511 123
                                    $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
512
                                }
513
                            }
514 123
                            $quotePrefix = (bool) ($xf['quotePrefix'] ?? false);
515
516
                            $style = (object) [
517 123
                                'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL,
518 123
                                'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
519 123
                                'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
520 123
                                'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
521 123
                                'alignment' => $xf->alignment,
522 123
                                'protection' => $xf->protection,
523 123
                                'quotePrefix' => $quotePrefix,
524
                            ];
525 123
                            $styles[] = $style;
526
527
                            // add style to cellXf collection
528 123
                            $objStyle = new Style();
529 123
                            self::readStyle($objStyle, $style);
530 123
                            $excel->addCellXf($objStyle);
531
                        }
532
533 123
                        foreach ($xmlStyles->cellStyleXfs->xf ?? [] as $xf) {
534 123
                            $numFmt = NumberFormat::FORMAT_GENERAL;
535 123
                            if ($numFmts && $xf['numFmtId']) {
536 65
                                $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
537 65
                                if (isset($tmpNumFmt['formatCode'])) {
538 1
                                    $numFmt = (string) $tmpNumFmt['formatCode'];
539 65
                                } elseif ((int) $xf['numFmtId'] < 165) {
540 65
                                    $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
541
                                }
542
                            }
543
544 123
                            $quotePrefix = (bool) ($xf['quotePrefix'] ?? false);
545
546
                            $cellStyle = (object) [
547 123
                                'numFmt' => $numFmt,
548 123
                                'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
549 123
                                'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
550 123
                                'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
551 123
                                'alignment' => $xf->alignment,
552 123
                                'protection' => $xf->protection,
553 123
                                'quotePrefix' => $quotePrefix,
554
                            ];
555 123
                            $cellStyles[] = $cellStyle;
556
557
                            // add style to cellStyleXf collection
558 123
                            $objStyle = new Style();
559 123
                            self::readStyle($objStyle, $cellStyle);
560 123
                            $excel->addCellStyleXf($objStyle);
561
                        }
562
                    }
563
564 123
                    $styleReader = new Styles($xmlStyles);
565 123
                    $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
566 123
                    $dxfs = $styleReader->dxfs($this->readDataOnly);
567 123
                    $styles = $styleReader->styles();
568
569
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
570 123
                    $xmlWorkbook = simplexml_load_string(
571 123
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
572 123
                        'SimpleXMLElement',
573 123
                        Settings::getLibXmlLoaderOptions()
574
                    );
575
576
                    // Set base date
577 123
                    if ($xmlWorkbook->workbookPr) {
578 122
                        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
579 122
                        if (isset($xmlWorkbook->workbookPr['date1904'])) {
580
                            if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) {
581
                                Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
582
                            }
583
                        }
584
                    }
585
586
                    // Set protection
587 123
                    $this->readProtection($excel, $xmlWorkbook);
588
589 123
                    $sheetId = 0; // keep track of new sheet id in final workbook
590 123
                    $oldSheetId = -1; // keep track of old sheet id in final workbook
591 123
                    $countSkippedSheets = 0; // keep track of number of skipped sheets
592 123
                    $mapSheetId = []; // mapping of sheet ids from old to new
593
594 123
                    $charts = $chartDetails = [];
595
596 123
                    if ($xmlWorkbook->sheets) {
597
                        /** @var SimpleXMLElement $eleSheet */
598 123
                        foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
599 123
                            ++$oldSheetId;
600
601
                            // Check if sheet should be skipped
602 123
                            if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) {
603 1
                                ++$countSkippedSheets;
604 1
                                $mapSheetId[$oldSheetId] = null;
605
606 1
                                continue;
607
                            }
608
609
                            // Map old sheet id in original workbook to new sheet id.
610
                            // They will differ if loadSheetsOnly() is being used
611 123
                            $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
612
613
                            // Load sheet
614 123
                            $docSheet = $excel->createSheet();
615
                            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
616
                            //        references in formula cells... during the load, all formulae should be correct,
617
                            //        and we're simply bringing the worksheet name in line with the formula, not the
618
                            //        reverse
619 123
                            $docSheet->setTitle((string) $eleSheet['name'], false, false);
620 123
                            $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
621
                            //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
622 123
                            $xmlSheet = simplexml_load_string(
623 123
                                $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")),
624 123
                                'SimpleXMLElement',
625 123
                                Settings::getLibXmlLoaderOptions()
626
                            );
627
628 123
                            $sharedFormulas = [];
629
630 123
                            if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') {
631 3
                                $docSheet->setSheetState((string) $eleSheet['state']);
632
                            }
633
634 123
                            if ($xmlSheet) {
635 123
                                if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) {
636 123
                                    $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);
637 123
                                    $sheetViews->load();
638
                                }
639
640 123
                                $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);
641 123
                                $sheetViewOptions->load($this->getReadDataOnly());
642
643 123
                                (new ColumnAndRowAttributes($docSheet, $xmlSheet))
644 123
                                    ->load($this->getReadFilter(), $this->getReadDataOnly());
645
                            }
646
647 123
                            if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
648 116
                                $cIndex = 1; // Cell Start from 1
649 116
                                foreach ($xmlSheet->sheetData->row as $row) {
650 116
                                    $rowIndex = 1;
651 116
                                    foreach ($row->c as $c) {
652 116
                                        $r = (string) $c['r'];
653 116
                                        if ($r == '') {
654 1
                                            $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
655
                                        }
656 116
                                        $cellDataType = (string) $c['t'];
657 116
                                        $value = null;
658 116
                                        $calculatedValue = null;
659
660
                                        // Read cell?
661 116
                                        if ($this->getReadFilter() !== null) {
662 116
                                            $coordinates = Coordinate::coordinateFromString($r);
663
664 116
                                            if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
665 3
                                                if (isset($c->f)) {
666
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
667
                                                }
668 3
                                                ++$rowIndex;
669
670 3
                                                continue;
671
                                            }
672
                                        }
673
674
                                        // Read cell!
675
                                        switch ($cellDataType) {
676 116
                                            case 's':
677 80
                                                if ((string) $c->v != '') {
678 80
                                                    $value = $sharedStrings[(int) ($c->v)];
679
680 80
                                                    if ($value instanceof RichText) {
681 80
                                                        $value = clone $value;
682
                                                    }
683
                                                } else {
684
                                                    $value = '';
685
                                                }
686
687 80
                                                break;
688 102
                                            case 'b':
689 5
                                                if (!isset($c->f)) {
690 3
                                                    $value = self::castToBoolean($c);
691
                                                } else {
692
                                                    // Formula
693 2
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean');
694 2
                                                    if (isset($c->f['t'])) {
695
                                                        $att = $c->f;
696
                                                        $docSheet->getCell($r)->setFormulaAttributes($att);
697
                                                    }
698
                                                }
699
700 5
                                                break;
701 98
                                            case 'inlineStr':
702 2
                                                if (isset($c->f)) {
703
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
704
                                                } else {
705 2
                                                    $value = $this->parseRichText($c->is);
706
                                                }
707
708 2
                                                break;
709 98
                                            case 'e':
710 5
                                                if (!isset($c->f)) {
711
                                                    $value = self::castToError($c);
712
                                                } else {
713
                                                    // Formula
714 5
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
715
                                                }
716
717 5
                                                break;
718
                                            default:
719 98
                                                if (!isset($c->f)) {
720 94
                                                    $value = self::castToString($c);
721
                                                } else {
722
                                                    // Formula
723 53
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
724 53
                                                    if (isset($c->f['t'])) {
725 4
                                                        $attributes = $c->f['t'];
726 4
                                                        $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]);
727
                                                    }
728
                                                }
729
730 98
                                                break;
731
                                        }
732
733
                                        // read empty cells or the cells are not empty
734 116
                                        if ($this->readEmptyCells || ($value !== null && $value !== '')) {
735
                                            // Rich text?
736 116
                                            if ($value instanceof RichText && $this->readDataOnly) {
737
                                                $value = $value->getPlainText();
738
                                            }
739
740 116
                                            $cell = $docSheet->getCell($r);
741
                                            // Assign value
742 116
                                            if ($cellDataType != '') {
743
                                                // it is possible, that datatype is numeric but with an empty string, which result in an error
744 88
                                                if ($cellDataType === DataType::TYPE_NUMERIC && $value === '') {
745
                                                    $cellDataType = DataType::TYPE_STRING;
746
                                                }
747 88
                                                $cell->setValueExplicit($value, $cellDataType);
748
                                            } else {
749 94
                                                $cell->setValue($value);
750
                                            }
751 116
                                            if ($calculatedValue !== null) {
752 54
                                                $cell->setCalculatedValue($calculatedValue);
753
                                            }
754
755
                                            // Style information?
756 116
                                            if ($c['s'] && !$this->readDataOnly) {
757
                                                // no style index means 0, it seems
758 35
                                                $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ?
759 35
                                                    (int) ($c['s']) : 0);
760
                                            }
761
                                        }
762 116
                                        ++$rowIndex;
763
                                    }
764 116
                                    ++$cIndex;
765
                                }
766
                            }
767
768 123
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) {
769 11
                                (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
770
                            }
771
772 123
                            $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells'];
773 123
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
774 93
                                foreach ($aKeys as $key) {
775 93
                                    $method = 'set' . ucfirst($key);
776 93
                                    $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key]));
777
                                }
778
                            }
779
780 123
                            if ($xmlSheet) {
781 123
                                $this->readSheetProtection($docSheet, $xmlSheet);
782
                            }
783
784 123
                            if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
785 1
                                (new AutoFilter($docSheet, $xmlSheet))->load();
786
                            }
787
788 123
                            if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
789 8
                                foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) {
790 8
                                    $mergeRef = (string) $mergeCell['ref'];
791 8
                                    if (strpos($mergeRef, ':') !== false) {
792 8
                                        $docSheet->mergeCells((string) $mergeCell['ref']);
793
                                    }
794
                                }
795
                            }
796
797 123
                            if ($xmlSheet && !$this->readDataOnly) {
798 123
                                $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
799
                            }
800
801 123
                            if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
802 1
                                (new DataValidations($docSheet, $xmlSheet))->load();
803
                            }
804
805
                            // unparsed sheet AlternateContent
806 123
                            if ($xmlSheet && !$this->readDataOnly) {
807 123
                                $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
808 123
                                if ($mc->AlternateContent) {
809 1
                                    foreach ($mc->AlternateContent as $alternateContent) {
810 1
                                        $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
811
                                    }
812
                                }
813
                            }
814
815
                            // Add hyperlinks
816 123
                            if (!$this->readDataOnly) {
817 123
                                $hyperlinkReader = new Hyperlinks($docSheet);
818
                                // Locate hyperlink relations
819 123
                                $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
820 123
                                if ($zip->locateName($relationsFileName)) {
821
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
822 77
                                    $relsWorksheet = simplexml_load_string(
823 77
                                        $this->securityScanner->scan(
824 77
                                            $this->getFromZipArchive($zip, $relationsFileName)
825
                                        ),
826 77
                                        'SimpleXMLElement',
827 77
                                        Settings::getLibXmlLoaderOptions()
828
                                    );
829 77
                                    $hyperlinkReader->readHyperlinks($relsWorksheet);
830
                                }
831
832
                                // Loop through hyperlinks
833 123
                                if ($xmlSheet && $xmlSheet->hyperlinks) {
834 2
                                    $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);
835
                                }
836
                            }
837
838
                            // Add comments
839 123
                            $comments = [];
840 123
                            $vmlComments = [];
841 123
                            if (!$this->readDataOnly) {
842
                                // Locate comment relations
843 123
                                if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
844
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
845 77
                                    $relsWorksheet = simplexml_load_string(
846 77
                                        $this->securityScanner->scan(
847 77
                                            $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
848
                                        ),
849 77
                                        'SimpleXMLElement',
850 77
                                        Settings::getLibXmlLoaderOptions()
851
                                    );
852 77
                                    foreach ($relsWorksheet->Relationship as $ele) {
853 25
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') {
854 3
                                            $comments[(string) $ele['Id']] = (string) $ele['Target'];
855
                                        }
856 25
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
857 4
                                            $vmlComments[(string) $ele['Id']] = (string) $ele['Target'];
858
                                        }
859
                                    }
860
                                }
861
862
                                // Loop through comments
863 123
                                foreach ($comments as $relName => $relPath) {
864
                                    // Load comments file
865 3
                                    $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
866 3
                                    $commentsFile = simplexml_load_string(
867 3
                                        $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
868 3
                                        'SimpleXMLElement',
869 3
                                        Settings::getLibXmlLoaderOptions()
870
                                    );
871
872
                                    // Utility variables
873 3
                                    $authors = [];
874
875
                                    // Loop through authors
876 3
                                    foreach ($commentsFile->authors->author as $author) {
877 3
                                        $authors[] = (string) $author;
878
                                    }
879
880
                                    // Loop through contents
881 3
                                    foreach ($commentsFile->commentList->comment as $comment) {
882 3
                                        if (!empty($comment['authorId'])) {
883
                                            $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]);
884
                                        }
885 3
                                        $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text));
886
                                    }
887
                                }
888
889
                                // later we will remove from it real vmlComments
890 123
                                $unparsedVmlDrawings = $vmlComments;
891
892
                                // Loop through VML comments
893 123
                                foreach ($vmlComments as $relName => $relPath) {
894
                                    // Load VML comments file
895 4
                                    $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
896
897
                                    try {
898 4
                                        $vmlCommentsFile = simplexml_load_string(
899 4
                                            $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
900 4
                                            'SimpleXMLElement',
901 4
                                            Settings::getLibXmlLoaderOptions()
902
                                        );
903 4
                                        $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
904
                                    } catch (Throwable $ex) {
905
                                        //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
906
                                        continue;
907
                                    }
908
909 4
                                    $shapes = $vmlCommentsFile->xpath('//v:shape');
910 4
                                    foreach ($shapes as $shape) {
911 4
                                        $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
912
913 4
                                        if (isset($shape['style'])) {
914 4
                                            $style = (string) $shape['style'];
915 4
                                            $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
916 4
                                            $column = null;
917 4
                                            $row = null;
918
919 4
                                            $clientData = $shape->xpath('.//x:ClientData');
920 4
                                            if (is_array($clientData) && !empty($clientData)) {
921 4
                                                $clientData = $clientData[0];
922
923 4
                                                if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
924 3
                                                    $temp = $clientData->xpath('.//x:Row');
925 3
                                                    if (is_array($temp)) {
926 3
                                                        $row = $temp[0];
927
                                                    }
928
929 3
                                                    $temp = $clientData->xpath('.//x:Column');
930 3
                                                    if (is_array($temp)) {
931 3
                                                        $column = $temp[0];
932
                                                    }
933
                                                }
934
                                            }
935
936 4
                                            if (($column !== null) && ($row !== null)) {
937
                                                // Set comment properties
938 3
                                                $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1);
939 3
                                                $comment->getFillColor()->setRGB($fillColor);
940
941
                                                // Parse style
942 3
                                                $styleArray = explode(';', str_replace(' ', '', $style));
943 3
                                                foreach ($styleArray as $stylePair) {
944 3
                                                    $stylePair = explode(':', $stylePair);
945
946 3
                                                    if ($stylePair[0] == 'margin-left') {
947 3
                                                        $comment->setMarginLeft($stylePair[1]);
948
                                                    }
949 3
                                                    if ($stylePair[0] == 'margin-top') {
950 3
                                                        $comment->setMarginTop($stylePair[1]);
951
                                                    }
952 3
                                                    if ($stylePair[0] == 'width') {
953 3
                                                        $comment->setWidth($stylePair[1]);
954
                                                    }
955 3
                                                    if ($stylePair[0] == 'height') {
956 3
                                                        $comment->setHeight($stylePair[1]);
957
                                                    }
958 3
                                                    if ($stylePair[0] == 'visibility') {
959 3
                                                        $comment->setVisible($stylePair[1] == 'visible');
960
                                                    }
961
                                                }
962
963 3
                                                unset($unparsedVmlDrawings[$relName]);
964
                                            }
965
                                        }
966
                                    }
967
                                }
968
969
                                // unparsed vmlDrawing
970 123
                                if ($unparsedVmlDrawings) {
971 1
                                    foreach ($unparsedVmlDrawings as $rId => $relPath) {
972 1
                                        $rId = substr($rId, 3); // rIdXXX
973 1
                                        $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
974 1
                                        $unparsedVmlDrawing[$rId] = [];
975 1
                                        $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
976 1
                                        $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
977 1
                                        $unparsedVmlDrawing[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
978 1
                                        unset($unparsedVmlDrawing);
979
                                    }
980
                                }
981
982
                                // Header/footer images
983 123
                                if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) {
984
                                    if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
985
                                        //~ http://schemas.openxmlformats.org/package/2006/relationships"
986
                                        $relsWorksheet = simplexml_load_string(
987
                                            $this->securityScanner->scan(
988
                                                $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
989
                                            ),
990
                                            'SimpleXMLElement',
991
                                            Settings::getLibXmlLoaderOptions()
992
                                        );
993
                                        $vmlRelationship = '';
994
995
                                        foreach ($relsWorksheet->Relationship as $ele) {
996
                                            if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
997
                                                $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
998
                                            }
999
                                        }
1000
1001
                                        if ($vmlRelationship != '') {
1002
                                            // Fetch linked images
1003
                                            //~ http://schemas.openxmlformats.org/package/2006/relationships"
1004
                                            $relsVML = simplexml_load_string(
1005
                                                $this->securityScanner->scan(
1006
                                                    $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels')
1007
                                                ),
1008
                                                'SimpleXMLElement',
1009
                                                Settings::getLibXmlLoaderOptions()
1010
                                            );
1011
                                            $drawings = [];
1012
                                            if (isset($relsVML->Relationship)) {
1013
                                                foreach ($relsVML->Relationship as $ele) {
1014
                                                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1015
                                                        $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']);
1016
                                                    }
1017
                                                }
1018
                                            }
1019
                                            // Fetch VML document
1020
                                            $vmlDrawing = simplexml_load_string(
1021
                                                $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)),
1022
                                                'SimpleXMLElement',
1023
                                                Settings::getLibXmlLoaderOptions()
1024
                                            );
1025
                                            $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
1026
1027
                                            $hfImages = [];
1028
1029
                                            $shapes = $vmlDrawing->xpath('//v:shape');
1030
                                            foreach ($shapes as $idx => $shape) {
1031
                                                $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
1032
                                                $imageData = $shape->xpath('//v:imagedata');
1033
1034
                                                if (!$imageData) {
1035
                                                    continue;
1036
                                                }
1037
1038
                                                $imageData = $imageData[$idx];
1039
1040
                                                $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office');
1041
                                                $style = self::toCSSArray((string) $shape['style']);
1042
1043
                                                $hfImages[(string) $shape['id']] = new HeaderFooterDrawing();
1044
                                                if (isset($imageData['title'])) {
1045
                                                    $hfImages[(string) $shape['id']]->setName((string) $imageData['title']);
1046
                                                }
1047
1048
                                                $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($pFilename) . '#' . $drawings[(string) $imageData['relid']], false);
1049
                                                $hfImages[(string) $shape['id']]->setResizeProportional(false);
1050
                                                $hfImages[(string) $shape['id']]->setWidth($style['width']);
1051
                                                $hfImages[(string) $shape['id']]->setHeight($style['height']);
1052
                                                if (isset($style['margin-left'])) {
1053
                                                    $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']);
1054
                                                }
1055
                                                $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']);
1056
                                                $hfImages[(string) $shape['id']]->setResizeProportional(true);
1057
                                            }
1058
1059
                                            $docSheet->getHeaderFooter()->setImages($hfImages);
1060
                                        }
1061
                                    }
1062
                                }
1063
                            }
1064
1065
                            // TODO: Autoshapes from twoCellAnchors!
1066 123
                            if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
1067
                                //~ http://schemas.openxmlformats.org/package/2006/relationships"
1068 77
                                $relsWorksheet = simplexml_load_string(
1069 77
                                    $this->securityScanner->scan(
1070 77
                                        $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
1071
                                    ),
1072 77
                                    'SimpleXMLElement',
1073 77
                                    Settings::getLibXmlLoaderOptions()
1074
                                );
1075 77
                                $drawings = [];
1076 77
                                foreach ($relsWorksheet->Relationship as $ele) {
1077 25
                                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
1078 14
                                        $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
1079
                                    }
1080
                                }
1081 77
                                if ($xmlSheet->drawing && !$this->readDataOnly) {
1082 14
                                    $unparsedDrawings = [];
1083 14
                                    $fileDrawing = null;
1084 14
                                    foreach ($xmlSheet->drawing as $drawing) {
1085 14
                                        $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');
1086 14
                                        $fileDrawing = $drawings[$drawingRelId];
1087
                                        //~ http://schemas.openxmlformats.org/package/2006/relationships"
1088 14
                                        $relsDrawing = simplexml_load_string(
1089 14
                                            $this->securityScanner->scan(
1090 14
                                                $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels')
1091
                                            ),
1092 14
                                            'SimpleXMLElement',
1093 14
                                            Settings::getLibXmlLoaderOptions()
1094
                                        );
1095 14
                                        $images = [];
1096 14
                                        $hyperlinks = [];
1097 14
                                        if ($relsDrawing && $relsDrawing->Relationship) {
1098 12
                                            foreach ($relsDrawing->Relationship as $ele) {
1099 12
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
1100 2
                                                    $hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
1101
                                                }
1102 12
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1103 9
                                                    $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
1104 7
                                                } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {
1105 5
                                                    if ($this->includeCharts) {
1106 5
                                                        $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [
1107 5
                                                            'id' => (string) $ele['Id'],
1108 5
                                                            'sheet' => $docSheet->getTitle(),
1109
                                                        ];
1110
                                                    }
1111
                                                }
1112
                                            }
1113
                                        }
1114 14
                                        $xmlDrawing = simplexml_load_string(
1115 14
                                            $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
1116 14
                                            'SimpleXMLElement',
1117 14
                                            Settings::getLibXmlLoaderOptions()
1118
                                        );
1119 14
                                        $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
1120
1121 14
                                        if ($xmlDrawingChildren->oneCellAnchor) {
1122 7
                                            foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
1123 7
                                                if ($oneCellAnchor->pic->blipFill) {
1124
                                                    /** @var SimpleXMLElement $blip */
1125 6
                                                    $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
1126
                                                    /** @var SimpleXMLElement $xfrm */
1127 6
                                                    $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
1128
                                                    /** @var SimpleXMLElement $outerShdw */
1129 6
                                                    $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1130
                                                    /** @var SimpleXMLElement $hlinkClick */
1131 6
                                                    $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
0 ignored issues
show
Unused Code introduced by
The assignment to $hlinkClick is dead and can be removed.
Loading history...
1132
1133 6
                                                    $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
1134 6
                                                    $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
1135 6
                                                    $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
1136 6
                                                    $objDrawing->setPath(
1137 6
                                                        'zip://' . File::realpath($pFilename) . '#' .
1138 6
                                                        $images[(string) self::getArrayItem(
1139 6
                                                            $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
1140 6
                                                            'embed'
1141
                                                        )],
1142 6
                                                        false
1143
                                                    );
1144 6
                                                    $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1));
1145 6
                                                    $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff));
1146 6
                                                    $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff));
1147 6
                                                    $objDrawing->setResizeProportional(false);
1148 6
                                                    $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')));
1149 6
                                                    $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')));
1150 6
                                                    if ($xfrm) {
1151 6
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));
1152
                                                    }
1153 6
                                                    if ($outerShdw) {
1154 2
                                                        $shadow = $objDrawing->getShadow();
1155 2
                                                        $shadow->setVisible(true);
1156 2
                                                        $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
1157 2
                                                        $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
1158 2
                                                        $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
1159 2
                                                        $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
1160 2
                                                        $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr;
1161 2
                                                        $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
1162 2
                                                        $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
1163
                                                    }
1164
1165 6
                                                    $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
1166
1167 6
                                                    $objDrawing->setWorksheet($docSheet);
1168 1
                                                } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) {
1169
                                                    // Exported XLSX from Google Sheets positions charts with a oneCellAnchor
1170 1
                                                    $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);
1171 1
                                                    $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff);
1172 1
                                                    $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);
1173 1
                                                    $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'));
1174 1
                                                    $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'));
1175
1176 1
                                                    $graphic = $oneCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;
1177
                                                    /** @var SimpleXMLElement $chartRef */
1178 1
                                                    $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart;
1179 1
                                                    $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
1180
1181 1
                                                    $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
1182 1
                                                        'fromCoordinate' => $coordinates,
1183 1
                                                        'fromOffsetX' => $offsetX,
1184 1
                                                        'fromOffsetY' => $offsetY,
1185 1
                                                        'width' => $width,
1186 1
                                                        'height' => $height,
1187 1
                                                        'worksheetTitle' => $docSheet->getTitle(),
1188
                                                    ];
1189
                                                }
1190
                                            }
1191
                                        }
1192 14
                                        if ($xmlDrawingChildren->twoCellAnchor) {
1193 7
                                            foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
1194 7
                                                if ($twoCellAnchor->pic->blipFill) {
1195 5
                                                    $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
1196 5
                                                    $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
1197 5
                                                    $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1198 5
                                                    $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
1199 5
                                                    $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
1200 5
                                                    $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
1201 5
                                                    $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
1202 5
                                                    $objDrawing->setPath(
1203 5
                                                        'zip://' . File::realpath($pFilename) . '#' .
1204 5
                                                        $images[(string) self::getArrayItem(
1205 5
                                                            $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
1206 5
                                                            'embed'
1207
                                                        )],
1208 5
                                                        false
1209
                                                    );
1210 5
                                                    $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1));
1211 5
                                                    $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff));
1212 5
                                                    $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff));
1213 5
                                                    $objDrawing->setResizeProportional(false);
1214
1215 5
                                                    if ($xfrm) {
1216 5
                                                        $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx')));
1217 5
                                                        $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy')));
1218 5
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));
1219
                                                    }
1220 5
                                                    if ($outerShdw) {
1221
                                                        $shadow = $objDrawing->getShadow();
1222
                                                        $shadow->setVisible(true);
1223
                                                        $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
1224
                                                        $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
1225
                                                        $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
1226
                                                        $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
1227
                                                        $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr;
1228
                                                        $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
1229
                                                        $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
1230
                                                    }
1231
1232 5
                                                    $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
1233
1234 5
                                                    $objDrawing->setWorksheet($docSheet);
1235 4
                                                } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
1236 4
                                                    $fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
1237 4
                                                    $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff);
1238 4
                                                    $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff);
1239 4
                                                    $toCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);
1240 4
                                                    $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
1241 4
                                                    $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
1242 4
                                                    $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;
1243
                                                    /** @var SimpleXMLElement $chartRef */
1244 4
                                                    $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart;
1245 4
                                                    $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
1246
1247 4
                                                    $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
1248 4
                                                        'fromCoordinate' => $fromCoordinate,
1249 4
                                                        'fromOffsetX' => $fromOffsetX,
1250 4
                                                        'fromOffsetY' => $fromOffsetY,
1251 4
                                                        'toCoordinate' => $toCoordinate,
1252 4
                                                        'toOffsetX' => $toOffsetX,
1253 4
                                                        'toOffsetY' => $toOffsetY,
1254 4
                                                        'worksheetTitle' => $docSheet->getTitle(),
1255
                                                    ];
1256
                                                }
1257
                                            }
1258
                                        }
1259 14
                                        if ($relsDrawing === false && $xmlDrawing->count() == 0) {
1260
                                            // Save Drawing without rels and children as unparsed
1261 2
                                            $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
1262
                                        }
1263
                                    }
1264
1265
                                    // store original rId of drawing files
1266 14
                                    $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
1267 14
                                    foreach ($relsWorksheet->Relationship as $ele) {
1268 14
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
1269 14
                                            $drawingRelId = (string) $ele['Id'];
1270 14
                                            $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
1271 14
                                            if (isset($unparsedDrawings[$drawingRelId])) {
1272 2
                                                $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
1273
                                            }
1274
                                        }
1275
                                    }
1276
1277
                                    // unparsed drawing AlternateContent
1278 14
                                    $xmlAltDrawing = simplexml_load_string(
1279 14
                                        $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
1280 14
                                        'SimpleXMLElement',
1281 14
                                        Settings::getLibXmlLoaderOptions()
1282 14
                                    )->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
1283
1284 14
                                    if ($xmlAltDrawing->AlternateContent) {
1285 1
                                        foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
1286 1
                                            $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML();
1287
                                        }
1288
                                    }
1289
                                }
1290
                            }
1291
1292 123
                            $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
1293 123
                            $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
1294
1295
                            // Loop through definedNames
1296 123
                            if ($xmlWorkbook->definedNames) {
1297 104
                                foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
1298
                                    // Extract range
1299 44
                                    $extractedRange = (string) $definedName;
1300 44
                                    if (($spos = strpos($extractedRange, '!')) !== false) {
1301 44
                                        $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
1302
                                    } else {
1303 13
                                        $extractedRange = str_replace('$', '', $extractedRange);
1304
                                    }
1305
1306
                                    // Valid range?
1307 44
                                    if ($extractedRange == '') {
1308
                                        continue;
1309
                                    }
1310
1311
                                    // Some definedNames are only applicable if we are on the same sheet...
1312 44
                                    if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) {
1313
                                        // Switch on type
1314 17
                                        switch ((string) $definedName['name']) {
1315 17
                                            case '_xlnm._FilterDatabase':
1316 1
                                                if ((string) $definedName['hidden'] !== '1') {
1317
                                                    $extractedRange = explode(',', $extractedRange);
1318
                                                    foreach ($extractedRange as $range) {
1319
                                                        $autoFilterRange = $range;
1320
                                                        if (strpos($autoFilterRange, ':') !== false) {
1321
                                                            $docSheet->getAutoFilter()->setRange($autoFilterRange);
1322
                                                        }
1323
                                                    }
1324
                                                }
1325
1326 1
                                                break;
1327 16
                                            case '_xlnm.Print_Titles':
1328
                                                // Split $extractedRange
1329 1
                                                $extractedRange = explode(',', $extractedRange);
1330
1331
                                                // Set print titles
1332 1
                                                foreach ($extractedRange as $range) {
1333 1
                                                    $matches = [];
1334 1
                                                    $range = str_replace('$', '', $range);
1335
1336
                                                    // check for repeating columns, e g. 'A:A' or 'A:D'
1337 1
                                                    if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) {
1338
                                                        $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]);
1339 1
                                                    } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) {
1340
                                                        // check for repeating rows, e.g. '1:1' or '1:5'
1341 1
                                                        $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]);
1342
                                                    }
1343
                                                }
1344
1345 1
                                                break;
1346 15
                                            case '_xlnm.Print_Area':
1347 3
                                                $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
1348 3
                                                $newRangeSets = [];
1349 3
                                                foreach ($rangeSets as $rangeSet) {
1350 3
                                                    [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
1351 3
                                                    if (strpos($rangeSet, ':') === false) {
1352
                                                        $rangeSet = $rangeSet . ':' . $rangeSet;
1353
                                                    }
1354 3
                                                    $newRangeSets[] = str_replace('$', '', $rangeSet);
1355
                                                }
1356 3
                                                $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
1357
1358 3
                                                break;
1359
                                            default:
1360 12
                                                break;
1361
                                        }
1362
                                    }
1363
                                }
1364
                            }
1365
1366
                            // Next sheet id
1367 123
                            ++$sheetId;
1368
                        }
1369
1370
                        // Loop through definedNames
1371 123
                        if ($xmlWorkbook->definedNames) {
1372 104
                            foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
1373
                                // Extract range
1374 44
                                $extractedRange = (string) $definedName;
1375
1376
                                // Valid range?
1377 44
                                if ($extractedRange == '') {
1378
                                    continue;
1379
                                }
1380
1381
                                // Some definedNames are only applicable if we are on the same sheet...
1382 44
                                if ((string) $definedName['localSheetId'] != '') {
1383
                                    // Local defined name
1384
                                    // Switch on type
1385 17
                                    switch ((string) $definedName['name']) {
1386 17
                                        case '_xlnm._FilterDatabase':
1387 16
                                        case '_xlnm.Print_Titles':
1388 15
                                        case '_xlnm.Print_Area':
1389 5
                                            break;
1390
                                        default:
1391 12
                                            if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
1392 12
                                                $range = Worksheet::extractSheetTitle((string) $definedName, true);
1393 12
                                                $scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]);
1394 12
                                                if (strpos((string) $definedName, '!') !== false) {
1395 12
                                                    $range[0] = str_replace("''", "'", $range[0]);
1396 12
                                                    $range[0] = str_replace("'", '', $range[0]);
1397 12
                                                    if ($worksheet = $excel->getSheetByName($range[0])) {
1398 12
                                                        $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope));
1399
                                                    } else {
1400 12
                                                        $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope));
1401
                                                    }
1402
                                                } else {
1403
                                                    $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true));
1404
                                                }
1405
                                            }
1406
1407 17
                                            break;
1408
                                    }
1409 39
                                } elseif (!isset($definedName['localSheetId'])) {
1410 39
                                    $definedRange = (string) $definedName;
1411
                                    // "Global" definedNames
1412 39
                                    $locatedSheet = null;
1413 39
                                    if (strpos((string) $definedName, '!') !== false) {
1414
                                        // Modify range, and extract the first worksheet reference
1415
                                        // Need to split on a comma or a space if not in quotes, and extract the first part.
1416 39
                                        $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange);
1417
                                        // Extract sheet name
1418 39
                                        [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true);
1419 39
                                        $extractedSheetName = trim($extractedSheetName, "'");
1420
1421
                                        // Locate sheet
1422 39
                                        $locatedSheet = $excel->getSheetByName($extractedSheetName);
1423
                                    }
1424
1425 39
                                    if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) {
1426 1
                                        $definedRange = '#REF!';
1427
                                    }
1428 39
                                    $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false));
1429
                                }
1430
                            }
1431
                        }
1432
                    }
1433
1434 123
                    if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) {
1435 122
                        $workbookView = $xmlWorkbook->bookViews->workbookView;
1436
1437
                        // active sheet index
1438 122
                        $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index
1439
1440
                        // keep active sheet index if sheet is still loaded, else first sheet is set as the active
1441 122
                        if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
1442 122
                            $excel->setActiveSheetIndex($mapSheetId[$activeTab]);
1443
                        } else {
1444
                            if ($excel->getSheetCount() == 0) {
1445
                                $excel->createSheet();
1446
                            }
1447
                            $excel->setActiveSheetIndex(0);
1448
                        }
1449
1450 122
                        if (isset($workbookView['showHorizontalScroll'])) {
1451 61
                            $showHorizontalScroll = (string) $workbookView['showHorizontalScroll'];
1452 61
                            $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
1453
                        }
1454
1455 122
                        if (isset($workbookView['showVerticalScroll'])) {
1456 61
                            $showVerticalScroll = (string) $workbookView['showVerticalScroll'];
1457 61
                            $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
1458
                        }
1459
1460 122
                        if (isset($workbookView['showSheetTabs'])) {
1461 61
                            $showSheetTabs = (string) $workbookView['showSheetTabs'];
1462 61
                            $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
1463
                        }
1464
1465 122
                        if (isset($workbookView['minimized'])) {
1466 61
                            $minimized = (string) $workbookView['minimized'];
1467 61
                            $excel->setMinimized($this->castXsdBooleanToBool($minimized));
1468
                        }
1469
1470 122
                        if (isset($workbookView['autoFilterDateGrouping'])) {
1471 61
                            $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping'];
1472 61
                            $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
1473
                        }
1474
1475 122
                        if (isset($workbookView['firstSheet'])) {
1476 61
                            $firstSheet = (string) $workbookView['firstSheet'];
1477 61
                            $excel->setFirstSheetIndex((int) $firstSheet);
1478
                        }
1479
1480 122
                        if (isset($workbookView['visibility'])) {
1481 61
                            $visibility = (string) $workbookView['visibility'];
1482 61
                            $excel->setVisibility($visibility);
1483
                        }
1484
1485 122
                        if (isset($workbookView['tabRatio'])) {
1486 61
                            $tabRatio = (string) $workbookView['tabRatio'];
1487 61
                            $excel->setTabRatio((int) $tabRatio);
1488
                        }
1489
                    }
1490
1491 123
                    break;
1492
            }
1493
        }
1494
1495 123
        if (!$this->readDataOnly) {
1496 123
            $contentTypes = simplexml_load_string(
1497 123
                $this->securityScanner->scan(
1498 123
                    $this->getFromZipArchive($zip, '[Content_Types].xml')
1499
                ),
1500 123
                'SimpleXMLElement',
1501 123
                Settings::getLibXmlLoaderOptions()
1502
            );
1503
1504
            // Default content types
1505 123
            foreach ($contentTypes->Default as $contentType) {
1506 123
                switch ($contentType['ContentType']) {
1507 123
                    case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
1508 17
                        $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
1509
1510 17
                        break;
1511
                }
1512
            }
1513
1514
            // Override content types
1515 123
            foreach ($contentTypes->Override as $contentType) {
1516 123
                switch ($contentType['ContentType']) {
1517 123
                    case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml':
1518 5
                        if ($this->includeCharts) {
1519 5
                            $chartEntryRef = ltrim($contentType['PartName'], '/');
1520 5
                            $chartElements = simplexml_load_string(
1521 5
                                $this->securityScanner->scan(
1522 5
                                    $this->getFromZipArchive($zip, $chartEntryRef)
1523
                                ),
1524 5
                                'SimpleXMLElement',
1525 5
                                Settings::getLibXmlLoaderOptions()
1526
                            );
1527 5
                            $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml'));
1528
1529 5
                            if (isset($charts[$chartEntryRef])) {
1530 5
                                $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
1531 5
                                if (isset($chartDetails[$chartPositionRef])) {
1532 5
                                    $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);
1533 5
                                    $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
1534 5
                                    $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
1535 5
                                    if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $chartDetails does not seem to be defined for all execution paths leading up to this point.
Loading history...
1536
                                        // For oneCellAnchor positioned charts, toCoordinate is not in the data. Does it need to be calculated?
1537 4
                                        $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
1538
                                    }
1539
                                }
1540
                            }
1541
                        }
1542
1543 5
                        break;
1544
1545
                    // unparsed
1546 123
                    case 'application/vnd.ms-excel.controlproperties+xml':
1547 1
                        $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
1548
1549 1
                        break;
1550
                }
1551
            }
1552
        }
1553
1554 123
        $excel->setUnparsedLoadedData($unparsedLoadedData);
1555
1556 123
        $zip->close();
1557
1558 123
        return $excel;
1559
    }
1560
1561 123
    private static function readColor($color, $background = false)
1562
    {
1563 123
        if (isset($color['rgb'])) {
1564 99
            return (string) $color['rgb'];
1565 39
        } elseif (isset($color['indexed'])) {
1566 11
            return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
1567 36
        } elseif (isset($color['theme'])) {
1568 32
            if (self::$theme !== null) {
1569 32
                $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
1570 32
                if (isset($color['tint'])) {
1571 3
                    $tintAdjust = (float) $color['tint'];
1572 3
                    $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
1573
                }
1574
1575 32
                return 'FF' . $returnColour;
1576
            }
1577
        }
1578
1579 5
        if ($background) {
1580
            return 'FFFFFFFF';
1581
        }
1582
1583 5
        return 'FF000000';
1584
    }
1585
1586
    /**
1587
     * @param SimpleXMLElement|stdClass $style
1588
     */
1589 123
    private static function readStyle(Style $docStyle, $style): void
1590
    {
1591 123
        $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
1592
1593
        // font
1594 123
        if (isset($style->font)) {
1595 123
            $docStyle->getFont()->setName((string) $style->font->name['val']);
1596 123
            $docStyle->getFont()->setSize((string) $style->font->sz['val']);
0 ignored issues
show
Bug introduced by
(string)$style->font->sz['val'] of type string is incompatible with the type double expected by parameter $pValue of PhpOffice\PhpSpreadsheet\Style\Font::setSize(). ( Ignorable by Annotation )

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

1596
            $docStyle->getFont()->setSize(/** @scrutinizer ignore-type */ (string) $style->font->sz['val']);
Loading history...
1597 123
            if (isset($style->font->b)) {
1598 71
                $docStyle->getFont()->setBold(!isset($style->font->b['val']) || self::boolean((string) $style->font->b['val']));
1599
            }
1600 123
            if (isset($style->font->i)) {
1601 65
                $docStyle->getFont()->setItalic(!isset($style->font->i['val']) || self::boolean((string) $style->font->i['val']));
1602
            }
1603 123
            if (isset($style->font->strike)) {
1604 62
                $docStyle->getFont()->setStrikethrough(!isset($style->font->strike['val']) || self::boolean((string) $style->font->strike['val']));
1605
            }
1606 123
            $docStyle->getFont()->getColor()->setARGB(self::readColor($style->font->color));
1607
1608 123
            if (isset($style->font->u) && !isset($style->font->u['val'])) {
1609 1
                $docStyle->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
1610 123
            } elseif (isset($style->font->u, $style->font->u['val'])) {
1611 61
                $docStyle->getFont()->setUnderline((string) $style->font->u['val']);
1612
            }
1613
1614 123
            if (isset($style->font->vertAlign, $style->font->vertAlign['val'])) {
1615 1
                $vertAlign = strtolower((string) $style->font->vertAlign['val']);
1616 1
                if ($vertAlign == 'superscript') {
1617 1
                    $docStyle->getFont()->setSuperscript(true);
1618
                }
1619 1
                if ($vertAlign == 'subscript') {
1620 1
                    $docStyle->getFont()->setSubscript(true);
1621
                }
1622
            }
1623
        }
1624
1625
        // fill
1626 123
        if (isset($style->fill)) {
1627 123
            if ($style->fill->gradientFill) {
1628
                /** @var SimpleXMLElement $gradientFill */
1629 2
                $gradientFill = $style->fill->gradientFill[0];
1630 2
                if (!empty($gradientFill['type'])) {
1631 2
                    $docStyle->getFill()->setFillType((string) $gradientFill['type']);
1632
                }
1633 2
                $docStyle->getFill()->setRotation((float) ($gradientFill['degree']));
1634 2
                $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
1635 2
                $docStyle->getFill()->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
1636 2
                $docStyle->getFill()->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
1637 123
            } elseif ($style->fill->patternFill) {
1638 123
                $patternType = (string) $style->fill->patternFill['patternType'] != '' ? (string) $style->fill->patternFill['patternType'] : 'solid';
1639 123
                $docStyle->getFill()->setFillType($patternType);
1640 123
                if ($style->fill->patternFill->fgColor) {
1641 8
                    $docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true));
1642
                }
1643 123
                if ($style->fill->patternFill->bgColor) {
1644 8
                    $docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true));
1645
                }
1646
            }
1647
        }
1648
1649
        // border
1650 123
        if (isset($style->border)) {
1651 123
            $diagonalUp = self::boolean((string) $style->border['diagonalUp']);
1652 123
            $diagonalDown = self::boolean((string) $style->border['diagonalDown']);
1653 123
            if (!$diagonalUp && !$diagonalDown) {
1654 123
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
1655 1
            } elseif ($diagonalUp && !$diagonalDown) {
1656 1
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
1657 1
            } elseif (!$diagonalUp && $diagonalDown) {
1658 1
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
1659
            } else {
1660
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
1661
            }
1662 123
            self::readBorder($docStyle->getBorders()->getLeft(), $style->border->left);
1663 123
            self::readBorder($docStyle->getBorders()->getRight(), $style->border->right);
1664 123
            self::readBorder($docStyle->getBorders()->getTop(), $style->border->top);
1665 123
            self::readBorder($docStyle->getBorders()->getBottom(), $style->border->bottom);
1666 123
            self::readBorder($docStyle->getBorders()->getDiagonal(), $style->border->diagonal);
1667
        }
1668
1669
        // alignment
1670 123
        if (isset($style->alignment)) {
1671 123
            $docStyle->getAlignment()->setHorizontal((string) $style->alignment['horizontal']);
1672 123
            $docStyle->getAlignment()->setVertical((string) $style->alignment['vertical']);
1673
1674 123
            $textRotation = 0;
1675 123
            if ((int) $style->alignment['textRotation'] <= 90) {
1676 123
                $textRotation = (int) $style->alignment['textRotation'];
1677
            } elseif ((int) $style->alignment['textRotation'] > 90) {
1678
                $textRotation = 90 - (int) $style->alignment['textRotation'];
1679
            }
1680
1681 123
            $docStyle->getAlignment()->setTextRotation((int) $textRotation);
1682 123
            $docStyle->getAlignment()->setWrapText(self::boolean((string) $style->alignment['wrapText']));
1683 123
            $docStyle->getAlignment()->setShrinkToFit(self::boolean((string) $style->alignment['shrinkToFit']));
1684 123
            $docStyle->getAlignment()->setIndent((int) ((string) $style->alignment['indent']) > 0 ? (int) ((string) $style->alignment['indent']) : 0);
1685 123
            $docStyle->getAlignment()->setReadOrder((int) ((string) $style->alignment['readingOrder']) > 0 ? (int) ((string) $style->alignment['readingOrder']) : 0);
1686
        }
1687
1688
        // protection
1689 123
        if (isset($style->protection)) {
1690 123
            if (isset($style->protection['locked'])) {
1691 2
                if (self::boolean((string) $style->protection['locked'])) {
1692
                    $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
1693
                } else {
1694 2
                    $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
1695
                }
1696
            }
1697
1698 123
            if (isset($style->protection['hidden'])) {
1699 2
                if (self::boolean((string) $style->protection['hidden'])) {
1700
                    $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
1701
                } else {
1702 2
                    $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
1703
                }
1704
            }
1705
        }
1706
1707
        // top-level style settings
1708 123
        if (isset($style->quotePrefix)) {
1709 123
            $docStyle->setQuotePrefix($style->quotePrefix);
0 ignored issues
show
Bug introduced by
$style->quotePrefix of type SimpleXMLElement is incompatible with the type boolean expected by parameter $pValue of PhpOffice\PhpSpreadsheet...Style::setQuotePrefix(). ( Ignorable by Annotation )

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

1709
            $docStyle->setQuotePrefix(/** @scrutinizer ignore-type */ $style->quotePrefix);
Loading history...
1710
        }
1711 123
    }
1712
1713
    /**
1714
     * @param SimpleXMLElement $eleBorder
1715
     */
1716 123
    private static function readBorder(Border $docBorder, $eleBorder): void
1717
    {
1718 123
        if (isset($eleBorder['style'])) {
1719 8
            $docBorder->setBorderStyle((string) $eleBorder['style']);
1720
        }
1721 123
        if (isset($eleBorder->color)) {
1722 8
            $docBorder->getColor()->setARGB(self::readColor($eleBorder->color));
1723
        }
1724 123
    }
1725
1726
    /**
1727
     * @param SimpleXMLElement | null $is
1728
     *
1729
     * @return RichText
1730
     */
1731 5
    private function parseRichText($is)
1732
    {
1733 5
        $value = new RichText();
1734
1735 5
        if (isset($is->t)) {
1736
            $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t));
1737
        } else {
1738 5
            if (is_object($is->r)) {
1739 5
                foreach ($is->r as $run) {
1740 5
                    if (!isset($run->rPr)) {
1741 5
                        $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
1742
                    } else {
1743 4
                        $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
1744
1745 4
                        if (isset($run->rPr->rFont['val'])) {
1746 4
                            $objText->getFont()->setName((string) $run->rPr->rFont['val']);
1747
                        }
1748 4
                        if (isset($run->rPr->sz['val'])) {
1749 4
                            $objText->getFont()->setSize((float) $run->rPr->sz['val']);
1750
                        }
1751 4
                        if (isset($run->rPr->color)) {
1752 4
                            $objText->getFont()->setColor(new Color(self::readColor($run->rPr->color)));
1753
                        }
1754
                        if (
1755 4
                            (isset($run->rPr->b['val']) && self::boolean((string) $run->rPr->b['val'])) ||
1756 4
                            (isset($run->rPr->b) && !isset($run->rPr->b['val']))
1757
                        ) {
1758 4
                            $objText->getFont()->setBold(true);
1759
                        }
1760
                        if (
1761 4
                            (isset($run->rPr->i['val']) && self::boolean((string) $run->rPr->i['val'])) ||
1762 4
                            (isset($run->rPr->i) && !isset($run->rPr->i['val']))
1763
                        ) {
1764 2
                            $objText->getFont()->setItalic(true);
1765
                        }
1766 4
                        if (isset($run->rPr->vertAlign, $run->rPr->vertAlign['val'])) {
1767
                            $vertAlign = strtolower((string) $run->rPr->vertAlign['val']);
1768
                            if ($vertAlign == 'superscript') {
1769
                                $objText->getFont()->setSuperscript(true);
1770
                            }
1771
                            if ($vertAlign == 'subscript') {
1772
                                $objText->getFont()->setSubscript(true);
1773
                            }
1774
                        }
1775 4
                        if (isset($run->rPr->u) && !isset($run->rPr->u['val'])) {
1776
                            $objText->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
1777 4
                        } elseif (isset($run->rPr->u, $run->rPr->u['val'])) {
1778 2
                            $objText->getFont()->setUnderline((string) $run->rPr->u['val']);
1779
                        }
1780
                        if (
1781 4
                            (isset($run->rPr->strike['val']) && self::boolean((string) $run->rPr->strike['val'])) ||
1782 4
                            (isset($run->rPr->strike) && !isset($run->rPr->strike['val']))
1783
                        ) {
1784
                            $objText->getFont()->setStrikethrough(true);
1785
                        }
1786
                    }
1787
                }
1788
            }
1789
        }
1790
1791 5
        return $value;
1792
    }
1793
1794
    /**
1795
     * @param mixed $customUITarget
1796
     * @param mixed $zip
1797
     */
1798
    private function readRibbon(Spreadsheet $excel, $customUITarget, $zip): void
1799
    {
1800
        $baseDir = dirname($customUITarget);
1801
        $nameCustomUI = basename($customUITarget);
1802
        // get the xml file (ribbon)
1803
        $localRibbon = $this->getFromZipArchive($zip, $customUITarget);
1804
        $customUIImagesNames = [];
1805
        $customUIImagesBinaries = [];
1806
        // something like customUI/_rels/customUI.xml.rels
1807
        $pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels';
1808
        $dataRels = $this->getFromZipArchive($zip, $pathRels);
1809
        if ($dataRels) {
1810
            // exists and not empty if the ribbon have some pictures (other than internal MSO)
1811
            $UIRels = simplexml_load_string(
1812
                $this->securityScanner->scan($dataRels),
1813
                'SimpleXMLElement',
1814
                Settings::getLibXmlLoaderOptions()
1815
            );
1816
            if (false !== $UIRels) {
1817
                // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image
1818
                foreach ($UIRels->Relationship as $ele) {
1819
                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1820
                        // an image ?
1821
                        $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target'];
1822
                        $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']);
1823
                    }
1824
                }
1825
            }
1826
        }
1827
        if ($localRibbon) {
1828
            $excel->setRibbonXMLData($customUITarget, $localRibbon);
1829
            if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) {
1830
                $excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries);
1831
            } else {
1832
                $excel->setRibbonBinObjects(null, null);
1833
            }
1834
        } else {
1835
            $excel->setRibbonXMLData(null, null);
1836
            $excel->setRibbonBinObjects(null, null);
1837
        }
1838
    }
1839
1840 125
    private static function getArrayItem($array, $key = 0)
1841
    {
1842 125
        return $array[$key] ?? null;
1843
    }
1844
1845 24
    private static function dirAdd($base, $add)
1846
    {
1847 24
        return preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
1848
    }
1849
1850
    private static function toCSSArray($style)
1851
    {
1852
        $style = self::stripWhiteSpaceFromStyleString($style);
1853
1854
        $temp = explode(';', $style);
1855
        $style = [];
1856
        foreach ($temp as $item) {
1857
            $item = explode(':', $item);
1858
1859
            if (strpos($item[1], 'px') !== false) {
1860
                $item[1] = str_replace('px', '', $item[1]);
1861
            }
1862
            if (strpos($item[1], 'pt') !== false) {
1863
                $item[1] = str_replace('pt', '', $item[1]);
1864
                $item[1] = Font::fontSizeToPixels($item[1]);
1865
            }
1866
            if (strpos($item[1], 'in') !== false) {
1867
                $item[1] = str_replace('in', '', $item[1]);
1868
                $item[1] = Font::inchSizeToPixels($item[1]);
1869
            }
1870
            if (strpos($item[1], 'cm') !== false) {
1871
                $item[1] = str_replace('cm', '', $item[1]);
1872
                $item[1] = Font::centimeterSizeToPixels($item[1]);
1873
            }
1874
1875
            $style[$item[0]] = $item[1];
1876
        }
1877
1878
        return $style;
1879
    }
1880
1881 3
    public static function stripWhiteSpaceFromStyleString($string)
1882
    {
1883 3
        return trim(str_replace(["\r", "\n", ' '], '', $string), ';');
1884
    }
1885
1886 123
    private static function boolean($value)
1887
    {
1888 123
        if (is_object($value)) {
1889
            $value = (string) $value;
1890
        }
1891 123
        if (is_numeric($value)) {
1892 98
            return (bool) $value;
1893
        }
1894
1895 123
        return $value === 'true' || $value === 'TRUE';
1896
    }
1897
1898
    /**
1899
     * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing
1900
     * @param SimpleXMLElement $cellAnchor
1901
     * @param array $hyperlinks
1902
     */
1903 9
    private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks): void
1904
    {
1905 9
        $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
1906
1907 9
        if ($hlinkClick->count() === 0) {
1908 7
            return;
1909
        }
1910
1911 2
        $hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id'];
1912 2
        $hyperlink = new Hyperlink(
1913 2
            $hyperlinks[$hlinkId],
1914 2
            (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')
1915
        );
1916 2
        $objDrawing->setHyperlink($hyperlink);
1917 2
    }
1918
1919 123
    private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void
1920
    {
1921 123
        if (!$xmlWorkbook->workbookProtection) {
1922 122
            return;
1923
        }
1924
1925 1
        if ($xmlWorkbook->workbookProtection['lockRevision']) {
1926
            $excel->getSecurity()->setLockRevision((bool) $xmlWorkbook->workbookProtection['lockRevision']);
1927
        }
1928
1929 1
        if ($xmlWorkbook->workbookProtection['lockStructure']) {
1930 1
            $excel->getSecurity()->setLockStructure((bool) $xmlWorkbook->workbookProtection['lockStructure']);
1931
        }
1932
1933 1
        if ($xmlWorkbook->workbookProtection['lockWindows']) {
1934
            $excel->getSecurity()->setLockWindows((bool) $xmlWorkbook->workbookProtection['lockWindows']);
1935
        }
1936
1937 1
        if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
1938
            $excel->getSecurity()->setRevisionsPassword((string) $xmlWorkbook->workbookProtection['revisionsPassword'], true);
1939
        }
1940
1941 1
        if ($xmlWorkbook->workbookProtection['workbookPassword']) {
1942 1
            $excel->getSecurity()->setWorkbookPassword((string) $xmlWorkbook->workbookProtection['workbookPassword'], true);
1943
        }
1944 1
    }
1945
1946 123
    private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void
1947
    {
1948 123
        if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
1949 53
            return;
1950
        }
1951
1952
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
1953 77
        $relsWorksheet = simplexml_load_string(
1954 77
            $this->securityScanner->scan(
1955 77
                $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
1956
            ),
1957 77
            'SimpleXMLElement',
1958 77
            Settings::getLibXmlLoaderOptions()
1959
        );
1960 77
        $ctrlProps = [];
1961 77
        foreach ($relsWorksheet->Relationship as $ele) {
1962 25
            if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') {
1963 1
                $ctrlProps[(string) $ele['Id']] = $ele;
1964
            }
1965
        }
1966
1967 77
        $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps'];
1968 77
        foreach ($ctrlProps as $rId => $ctrlProp) {
1969 1
            $rId = substr($rId, 3); // rIdXXX
1970 1
            $unparsedCtrlProps[$rId] = [];
1971 1
            $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']);
1972 1
            $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target'];
1973 1
            $unparsedCtrlProps[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath']));
1974
        }
1975 77
        unset($unparsedCtrlProps);
1976 77
    }
1977
1978 123
    private function readPrinterSettings(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void
1979
    {
1980 123
        if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
1981 53
            return;
1982
        }
1983
1984
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
1985 77
        $relsWorksheet = simplexml_load_string(
1986 77
            $this->securityScanner->scan(
1987 77
                $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
1988
            ),
1989 77
            'SimpleXMLElement',
1990 77
            Settings::getLibXmlLoaderOptions()
1991
        );
1992 77
        $sheetPrinterSettings = [];
1993 77
        foreach ($relsWorksheet->Relationship as $ele) {
1994 25
            if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') {
1995 16
                $sheetPrinterSettings[(string) $ele['Id']] = $ele;
1996
            }
1997
        }
1998
1999 77
        $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings'];
2000 77
        foreach ($sheetPrinterSettings as $rId => $printerSettings) {
2001 16
            $rId = substr($rId, 3) . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing
2002 16
            $unparsedPrinterSettings[$rId] = [];
2003 16
            $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']);
2004 16
            $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target'];
2005 16
            $unparsedPrinterSettings[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath']));
2006
        }
2007 77
        unset($unparsedPrinterSettings);
2008 77
    }
2009
2010
    /**
2011
     * Convert an 'xsd:boolean' XML value to a PHP boolean value.
2012
     * A valid 'xsd:boolean' XML value can be one of the following
2013
     * four values: 'true', 'false', '1', '0'.  It is case sensitive.
2014
     *
2015
     * Note that just doing '(bool) $xsdBoolean' is not safe,
2016
     * since '(bool) "false"' returns true.
2017
     *
2018
     * @see https://www.w3.org/TR/xmlschema11-2/#boolean
2019
     *
2020
     * @param string $xsdBoolean An XML string value of type 'xsd:boolean'
2021
     *
2022
     * @return bool  Boolean value
2023
     */
2024 61
    private function castXsdBooleanToBool($xsdBoolean)
2025
    {
2026 61
        if ($xsdBoolean === 'false') {
2027 61
            return false;
2028
        }
2029
2030 61
        return (bool) $xsdBoolean;
2031
    }
2032
2033
    /**
2034
     * @param ZipArchive $zip Opened zip archive
2035
     *
2036
     * @return string basename of the used excel workbook
2037
     */
2038 126
    private function getWorkbookBaseName(ZipArchive $zip)
2039
    {
2040 126
        $workbookBasename = '';
2041
2042
        // check if it is an OOXML archive
2043 126
        $rels = simplexml_load_string(
2044 126
            $this->securityScanner->scan(
2045 126
                $this->getFromZipArchive($zip, '_rels/.rels')
2046
            ),
2047 126
            'SimpleXMLElement',
2048 126
            Settings::getLibXmlLoaderOptions()
2049
        );
2050 126
        if ($rels !== false) {
2051 126
            foreach ($rels->Relationship as $rel) {
2052 126
                switch ($rel['Type']) {
2053 126
                    case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
2054 126
                        $basename = basename($rel['Target']);
2055 126
                        if (preg_match('/workbook.*\.xml/', $basename)) {
2056 126
                            $workbookBasename = $basename;
2057
                        }
2058
2059 126
                        break;
2060
                }
2061
            }
2062
        }
2063
2064 126
        return $workbookBasename;
2065
    }
2066
2067 123
    private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void
2068
    {
2069 123
        if ($this->readDataOnly || !$xmlSheet->sheetProtection) {
2070 36
            return;
2071
        }
2072
2073 93
        $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName'];
2074 93
        $protection = $docSheet->getProtection();
2075 93
        $protection->setAlgorithm($algorithmName);
2076
2077 93
        if ($algorithmName) {
2078
            $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true);
2079
            $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']);
2080
            $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
2081
        } else {
2082 93
            $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true);
2083
        }
2084
2085 93
        if ($xmlSheet->protectedRanges->protectedRange) {
2086 2
            foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
2087 2
                $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
2088
            }
2089
        }
2090 93
    }
2091
}
2092