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

Xlsx::load()   F

Complexity

Conditions 251

Size

Total Lines 1241
Code Lines 762

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 657
CRAP Score 354.776

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 251
eloc 762
c 7
b 0
f 0
nop 1
dl 0
loc 1241
rs 3.3333
ccs 657
cts 745
cp 0.8819
crap 354.776

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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