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

Xlsx::boolean()   A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.074

Importance

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

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

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

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

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

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

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

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

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

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