Failed Conditions
Push — master ( 02f37d...f95322 )
by Adrien
09:26
created

Xlsx::parseRichText()   D

Complexity

Conditions 26

Size

Total Lines 63
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
nop 1
dl 0
loc 63
ccs 27
cts 35
cp 0.7714
crap 34.0756
rs 4.1666
c 0
b 0
f 0

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

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

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