Completed
Push — master ( c434e9...dcf3b9 )
by Adrien
09:01
created

Xlsx::parseRichText()   D

Complexity

Conditions 26

Size

Total Lines 55
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 34.8066

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 26
eloc 36
c 2
b 0
f 0
nop 1
dl 0
loc 55
rs 4.1666
ccs 26
cts 34
cp 0.7647
crap 34.8066

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\Hyperlink;
7
use PhpOffice\PhpSpreadsheet\NamedRange;
8
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
9
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter;
10
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
11
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes;
12
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles;
13
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations;
14
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
15
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
16
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
17
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
18
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
19
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
20
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
21
use PhpOffice\PhpSpreadsheet\RichText\RichText;
22
use PhpOffice\PhpSpreadsheet\Settings;
23
use PhpOffice\PhpSpreadsheet\Shared\Date;
24
use PhpOffice\PhpSpreadsheet\Shared\Drawing;
25
use PhpOffice\PhpSpreadsheet\Shared\File;
26
use PhpOffice\PhpSpreadsheet\Shared\Font;
27
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
28
use PhpOffice\PhpSpreadsheet\Spreadsheet;
29
use PhpOffice\PhpSpreadsheet\Style\Border;
30
use PhpOffice\PhpSpreadsheet\Style\Borders;
31
use PhpOffice\PhpSpreadsheet\Style\Color;
32
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
33
use PhpOffice\PhpSpreadsheet\Style\Protection;
34
use PhpOffice\PhpSpreadsheet\Style\Style;
35
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
36
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
37
use SimpleXMLElement;
38
use stdClass;
39
use Throwable;
40
use XMLReader;
41
use ZipArchive;
42
43
class Xlsx extends BaseReader
44
{
45
    /**
46
     * ReferenceHelper instance.
47
     *
48
     * @var ReferenceHelper
49
     */
50
    private $referenceHelper;
51
52
    /**
53
     * Xlsx\Theme instance.
54
     *
55
     * @var Xlsx\Theme
56
     */
57
    private static $theme = null;
58
59
    /**
60 76
     * Create a new Xlsx Reader instance.
61
     */
62 76
    public function __construct()
63 76
    {
64 76
        parent::__construct();
65 76
        $this->referenceHelper = ReferenceHelper::getInstance();
66
        $this->securityScanner = XmlScanner::getInstance($this);
67
    }
68
69
    /**
70
     * Can the current IReader read the file?
71
     *
72
     * @param string $pFilename
73
     *
74 7
     * @return bool
75
     */
76 7
    public function canRead($pFilename)
77
    {
78 7
        File::assertFile($pFilename);
79 7
80
        $result = false;
81 7
        $zip = new ZipArchive();
82 7
83 7
        if ($zip->open($pFilename) === true) {
84
            $workbookBasename = $this->getWorkbookBaseName($zip);
85 7
            $result = !empty($workbookBasename);
86
87
            $zip->close();
88 7
        }
89
90
        return $result;
91
    }
92
93
    /**
94
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
95
     *
96
     * @param string $pFilename
97
     *
98 1
     * @return array
99
     */
100 1
    public function listWorksheetNames($pFilename)
101
    {
102 1
        File::assertFile($pFilename);
103
104 1
        $worksheetNames = [];
105 1
106
        $zip = new ZipArchive();
107
        $zip->open($pFilename);
108
109 1
        //    The files we're looking at here are small enough that simpleXML is more efficient than XMLReader
110 1
        //~ http://schemas.openxmlformats.org/package/2006/relationships");
111
        $rels = simplexml_load_string(
112 1
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels'))
113 1
        );
114 1
        foreach ($rels->Relationship as $rel) {
115
            switch ($rel['Type']) {
116 1
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
117 1
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
118
                    $xmlWorkbook = simplexml_load_string(
119
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}"))
120 1
                    );
121 1
122
                    if ($xmlWorkbook->sheets) {
123 1
                        foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
124
                            // Check if sheet should be skipped
125
                            $worksheetNames[] = (string) $eleSheet['name'];
126
                        }
127
                    }
128
            }
129 1
        }
130
131 1
        $zip->close();
132
133
        return $worksheetNames;
134
    }
135
136
    /**
137
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
138
     *
139
     * @param string $pFilename
140
     *
141 1
     * @return array
142
     */
143 1
    public function listWorksheetInfo($pFilename)
144
    {
145 1
        File::assertFile($pFilename);
146
147 1
        $worksheetInfo = [];
148 1
149
        $zip = new ZipArchive();
150
        $zip->open($pFilename);
151 1
152 1
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
153 1
        $rels = simplexml_load_string(
154 1
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
155
            'SimpleXMLElement',
156 1
            Settings::getLibXmlLoaderOptions()
157 1
        );
158 1
        foreach ($rels->Relationship as $rel) {
159
            if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') {
160
                $dir = dirname($rel['Target']);
161 1
162 1
                //~ http://schemas.openxmlformats.org/package/2006/relationships"
163 1
                $relsWorkbook = simplexml_load_string(
164
                    $this->securityScanner->scan(
165 1
                        $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')
166 1
                    ),
167
                    'SimpleXMLElement',
168 1
                    Settings::getLibXmlLoaderOptions()
169
                );
170 1
                $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
171 1
172 1
                $worksheets = [];
173 1
                foreach ($relsWorkbook->Relationship as $ele) {
174
                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') {
175
                        $worksheets[(string) $ele['Id']] = $ele['Target'];
176
                    }
177
                }
178 1
179 1
                //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
180 1
                $xmlWorkbook = simplexml_load_string(
181
                    $this->securityScanner->scan(
182 1
                        $this->getFromZipArchive($zip, "{$rel['Target']}")
183 1
                    ),
184
                    'SimpleXMLElement',
185 1
                    Settings::getLibXmlLoaderOptions()
186 1
                );
187
                if ($xmlWorkbook->sheets) {
188 1
                    $dir = dirname($rel['Target']);
189
                    /** @var SimpleXMLElement $eleSheet */
190 1
                    foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
191 1
                        $tmpInfo = [
192 1
                            'worksheetName' => (string) $eleSheet['name'],
193 1
                            'lastColumnLetter' => 'A',
194 1
                            'lastColumnIndex' => 0,
195
                            'totalRows' => 0,
196
                            'totalColumns' => 0,
197 1
                        ];
198
199 1
                        $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
200 1
201 1
                        $xml = new XMLReader();
202 1
                        $xml->xml(
203
                            $this->securityScanner->scanFile(
204 1
                                'zip://' . File::realpath($pFilename) . '#' . "$dir/$fileWorksheet"
205 1
                            ),
206
                            null,
207 1
                            Settings::getLibXmlLoaderOptions()
208
                        );
209 1
                        $xml->setParserProperty(2, true);
210 1
211 1
                        $currCells = 0;
212 1
                        while ($xml->read()) {
213 1
                            if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) {
214 1
                                $row = $xml->getAttribute('r');
215 1
                                $tmpInfo['totalRows'] = $row;
216 1
                                $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
217 1
                                $currCells = 0;
218
                            } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) {
219
                                ++$currCells;
220 1
                            }
221 1
                        }
222
                        $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
223 1
                        $xml->close();
224 1
225
                        $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
226 1
                        $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
227
228
                        $worksheetInfo[] = $tmpInfo;
229
                    }
230
                }
231
            }
232 1
        }
233
234 1
        $zip->close();
235
236
        return $worksheetInfo;
237 5
    }
238
239 5
    private static function castToBoolean($c)
240 5
    {
241
        $value = isset($c->v) ? (string) $c->v : null;
242 5
        if ($value == '0') {
243 5
            return false;
244
        } elseif ($value == '1') {
245
            return true;
246
        }
247
248
        return (bool) $c->v;
249
    }
250
251
    private static function castToError($c)
252
    {
253
        return isset($c->v) ? (string) $c->v : null;
254 45
    }
255
256 45
    private static function castToString($c)
257
    {
258
        return isset($c->v) ? (string) $c->v : null;
259 8
    }
260
261 8
    private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType): void
262 8
    {
263 8
        $cellDataType = 'f';
264
        $value = "={$c->f}";
265
        $calculatedValue = self::$castBaseType($c);
266 8
267 2
        // Shared formula?
268
        if (isset($c->f['t']) && strtolower((string) $c->f['t']) == 'shared') {
269 2
            $instance = (string) $c->f['si'];
270 2
271
            if (!isset($sharedFormulas[(string) $c->f['si']])) {
272 2
                $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value];
273 2
            } else {
274
                $master = Coordinate::coordinateFromString($sharedFormulas[$instance]['master']);
275 2
                $current = Coordinate::coordinateFromString($r);
276 2
277 2
                $difference = [0, 0];
278
                $difference[0] = Coordinate::columnIndexFromString($current[0]) - Coordinate::columnIndexFromString($master[0]);
279 2
                $difference[1] = $current[1] - $master[1];
280
281
                $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]);
282 8
            }
283
        }
284
    }
285
286
    /**
287
     * @param string $fileName
288
     *
289
     * @return string
290 72
     */
291
    private function getFromZipArchive(ZipArchive $archive, $fileName = '')
292
    {
293 72
        // Root-relative paths
294
        if (strpos($fileName, '//') !== false) {
295
            $fileName = substr($fileName, strpos($fileName, '//') + 1);
296 72
        }
297
        $fileName = File::realpath($fileName);
298
299
        // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
300
        //    so we need to load case-insensitively from the zip file
301
302 72
        // Apache POI fixes
303 72
        $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
304 2
        if ($contents === false) {
305
            $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
306
        }
307 72
308
        return $contents;
309
    }
310
311
    /**
312
     * Loads Spreadsheet from file.
313
     *
314
     * @param string $pFilename
315
     *
316
     * @return Spreadsheet
317 69
     */
318
    public function load($pFilename)
319 69
    {
320
        File::assertFile($pFilename);
321
322 69
        // Initialisations
323 69
        $excel = new Spreadsheet();
324 69
        $excel->removeSheetByIndex(0);
325 69
        if (!$this->readDataOnly) {
326 69
            $excel->removeCellStyleXfByIndex(0); // remove the default style
327
            $excel->removeCellXfByIndex(0); // remove the default style
328 69
        }
329
        $unparsedLoadedData = [];
330 69
331 69
        $zip = new ZipArchive();
332
        $zip->open($pFilename);
333
334
        //    Read the theme first, because we need the colour scheme when reading the styles
335 69
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
336 69
        $workbookBasename = $this->getWorkbookBaseName($zip);
337 69
        $wbRels = simplexml_load_string(
338 69
            $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")),
339 69
            'SimpleXMLElement',
340
            Settings::getLibXmlLoaderOptions()
341 69
        );
342 69
        foreach ($wbRels->Relationship as $rel) {
343 69
            switch ($rel['Type']) {
344 69
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
345 69
                    $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
346
                    $themeOrderAdditional = count($themeOrderArray);
347 69
348 69
                    $xmlTheme = simplexml_load_string(
349 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")),
350 69
                        'SimpleXMLElement',
351
                        Settings::getLibXmlLoaderOptions()
352 69
                    );
353 69
                    if (is_object($xmlTheme)) {
354 69
                        $xmlThemeName = $xmlTheme->attributes();
355 69
                        $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
356
                        $themeName = (string) $xmlThemeName['name'];
357 69
358 69
                        $colourScheme = $xmlTheme->themeElements->clrScheme->attributes();
359 69
                        $colourSchemeName = (string) $colourScheme['name'];
360
                        $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
361 69
362 69
                        $themeColours = [];
363 69
                        foreach ($colourScheme as $k => $xmlColour) {
364 69
                            $themePos = array_search($k, $themeOrderArray);
365 69
                            if ($themePos === false) {
366
                                $themePos = $themeOrderAdditional++;
367 69
                            }
368 69
                            if (isset($xmlColour->sysClr)) {
369 69
                                $xmlColourData = $xmlColour->sysClr->attributes();
370 69
                                $themeColours[$themePos] = $xmlColourData['lastClr'];
371 69
                            } elseif (isset($xmlColour->srgbClr)) {
372 69
                                $xmlColourData = $xmlColour->srgbClr->attributes();
373
                                $themeColours[$themePos] = $xmlColourData['val'];
374
                            }
375 69
                        }
376
                        self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours);
377
                    }
378 69
379
                    break;
380
            }
381
        }
382
383 69
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
384 69
        $rels = simplexml_load_string(
385 69
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
386 69
            'SimpleXMLElement',
387
            Settings::getLibXmlLoaderOptions()
388
        );
389 69
390 69
        $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties());
391 69
        foreach ($rels->Relationship as $rel) {
392 69
            switch ($rel['Type']) {
393 68
                case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties':
394
                    $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
395 68
396 69
                    break;
397 68
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties':
398
                    $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
399 68
400 69
                    break;
401 4
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties':
402
                    $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
403 4
404
                    break;
405 69
                //Ribbon
406
                case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility':
407
                    $customUI = $rel['Target'];
408
                    if ($customUI !== null) {
409
                        $this->readRibbon($excel, $customUI, $zip);
410
                    }
411
412 69
                    break;
413 69
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
414
                    $dir = dirname($rel['Target']);
415 69
                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
416 69
                    $relsWorkbook = simplexml_load_string(
417 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')),
418 69
                        'SimpleXMLElement',
419
                        Settings::getLibXmlLoaderOptions()
420 69
                    );
421
                    $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
422 69
423 69
                    $sharedStrings = [];
424 69
                    $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));
425
                    if ($xpath) {
426 66
                        //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
427 66
                        $xmlStrings = simplexml_load_string(
428 66
                            $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
429 66
                            'SimpleXMLElement',
430
                            Settings::getLibXmlLoaderOptions()
431 66
                        );
432 32
                        if (isset($xmlStrings, $xmlStrings->si)) {
433 32
                            foreach ($xmlStrings->si as $val) {
434 32
                                if (isset($val->t)) {
435 4
                                    $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
436 4
                                } elseif (isset($val->r)) {
437
                                    $sharedStrings[] = $this->parseRichText($val);
438
                                }
439
                            }
440
                        }
441
                    }
442 69
443 69
                    $worksheets = [];
444 69
                    $macros = $customUI = null;
445 69
                    foreach ($relsWorkbook->Relationship as $ele) {
446 69
                        switch ($ele['Type']) {
447 69
                            case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':
448
                                $worksheets[(string) $ele['Id']] = $ele['Target'];
449 69
450
                                break;
451 69
                            // a vbaProject ? (: some macros)
452 1
                            case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject':
453
                                $macros = $ele['Target'];
454 1
455
                                break;
456
                        }
457
                    }
458 69
459 1
                    if ($macros !== null) {
460 1
                        $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin
461 1
                        if ($macrosCode !== false) {
462 1
                            $excel->setMacrosCode($macrosCode);
463
                            $excel->setHasMacros(true);
464 1
                            //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir
465 1
                            $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin');
466
                            if ($Certificate !== false) {
467
                                $excel->setMacrosCertificate($Certificate);
468
                            }
469
                        }
470
                    }
471 69
472
                    $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']"));
473 69
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
474 69
                    $xmlStyles = simplexml_load_string(
475 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
476 69
                        'SimpleXMLElement',
477
                        Settings::getLibXmlLoaderOptions()
478
                    );
479 69
480 69
                    $styles = [];
481 69
                    $cellStyles = [];
482 69
                    $numFmts = null;
483 55
                    if ($xmlStyles && $xmlStyles->numFmts[0]) {
484
                        $numFmts = $xmlStyles->numFmts[0];
485 69
                    }
486 55
                    if (isset($numFmts) && ($numFmts !== null)) {
487
                        $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
488 69
                    }
489 69
                    if (!$this->readDataOnly && $xmlStyles) {
490 69
                        foreach ($xmlStyles->cellXfs->xf as $xf) {
491
                            $numFmt = NumberFormat::FORMAT_GENERAL;
492 69
493 69
                            if ($xf['numFmtId']) {
494 55
                                if (isset($numFmts)) {
495
                                    $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
496 55
497 5
                                    if (isset($tmpNumFmt['formatCode'])) {
498
                                        $numFmt = (string) $tmpNumFmt['formatCode'];
499
                                    }
500
                                }
501
502
                                // We shouldn't override any of the built-in MS Excel values (values below id 164)
503
                                //  But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used
504 69
                                //  So we make allowance for them rather than lose formatting masks
505 69
                                if ((int) $xf['numFmtId'] < 164 &&
506 69
                                    NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '') {
507
                                    $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
508
                                }
509 69
                            }
510 69
                            $quotePrefix = false;
511
                            if (isset($xf['quotePrefix'])) {
512
                                $quotePrefix = (bool) $xf['quotePrefix'];
513
                            }
514
515 69
                            $style = (object) [
516 69
                                'numFmt' => $numFmt,
517 69
                                'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
518 69
                                'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
519 69
                                'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
520 69
                                'alignment' => $xf->alignment,
521 69
                                'protection' => $xf->protection,
522
                                'quotePrefix' => $quotePrefix,
523 69
                            ];
524
                            $styles[] = $style;
525
526 69
                            // add style to cellXf collection
527 69
                            $objStyle = new Style();
528 69
                            self::readStyle($objStyle, $style);
529
                            $excel->addCellXf($objStyle);
530
                        }
531 69
532 69
                        foreach (isset($xmlStyles->cellStyleXfs->xf) ? $xmlStyles->cellStyleXfs->xf : [] as $xf) {
533 69
                            $numFmt = NumberFormat::FORMAT_GENERAL;
534 55
                            if ($numFmts && $xf['numFmtId']) {
535 55
                                $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
536 1
                                if (isset($tmpNumFmt['formatCode'])) {
537 55
                                    $numFmt = (string) $tmpNumFmt['formatCode'];
538 55
                                } elseif ((int) $xf['numFmtId'] < 165) {
539
                                    $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
540
                                }
541
                            }
542
543 69
                            $cellStyle = (object) [
544 69
                                'numFmt' => $numFmt,
545 69
                                'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
546 69
                                'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
547 69
                                'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
548 69
                                'alignment' => $xf->alignment,
549 69
                                'protection' => $xf->protection,
550
                                'quotePrefix' => $quotePrefix,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $quotePrefix does not seem to be defined for all execution paths leading up to this point.
Loading history...
551 69
                            ];
552
                            $cellStyles[] = $cellStyle;
553
554 69
                            // add style to cellStyleXf collection
555 69
                            $objStyle = new Style();
556 69
                            self::readStyle($objStyle, $cellStyle);
557
                            $excel->addCellStyleXf($objStyle);
558
                        }
559
                    }
560 69
561 69
                    $styleReader = new Styles($xmlStyles);
1 ignored issue
show
Bug introduced by
It seems like $xmlStyles can also be of type false; however, parameter $styleXml of PhpOffice\PhpSpreadsheet...x\Styles::__construct() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

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

561
                    $styleReader = new Styles(/** @scrutinizer ignore-type */ $xmlStyles);
Loading history...
562 69
                    $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
563 69
                    $dxfs = $styleReader->dxfs($this->readDataOnly);
564
                    $styles = $styleReader->styles();
565
566 69
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
567 69
                    $xmlWorkbook = simplexml_load_string(
568 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
569 69
                        'SimpleXMLElement',
570
                        Settings::getLibXmlLoaderOptions()
571
                    );
572
573 69
                    // Set base date
574 68
                    if ($xmlWorkbook->workbookPr) {
575 68
                        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
576
                        if (isset($xmlWorkbook->workbookPr['date1904'])) {
577
                            if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) {
578
                                Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
579
                            }
580
                        }
581
                    }
582
583 69
                    // Set protection
584
                    $this->readProtection($excel, $xmlWorkbook);
1 ignored issue
show
Bug introduced by
It seems like $xmlWorkbook can also be of type false; however, parameter $xmlWorkbook of PhpOffice\PhpSpreadsheet...\Xlsx::readProtection() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

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

584
                    $this->readProtection($excel, /** @scrutinizer ignore-type */ $xmlWorkbook);
Loading history...
585 69
586 69
                    $sheetId = 0; // keep track of new sheet id in final workbook
587 69
                    $oldSheetId = -1; // keep track of old sheet id in final workbook
588 69
                    $countSkippedSheets = 0; // keep track of number of skipped sheets
589
                    $mapSheetId = []; // mapping of sheet ids from old to new
590 69
591
                    $charts = $chartDetails = [];
592 69
593
                    if ($xmlWorkbook->sheets) {
594 69
                        /** @var SimpleXMLElement $eleSheet */
595 69
                        foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
596
                            ++$oldSheetId;
597
598 69
                            // Check if sheet should be skipped
599 1
                            if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) {
600 1
                                ++$countSkippedSheets;
601
                                $mapSheetId[$oldSheetId] = null;
602 1
603
                                continue;
604
                            }
605
606
                            // Map old sheet id in original workbook to new sheet id.
607 69
                            // They will differ if loadSheetsOnly() is being used
608
                            $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
609
610 69
                            // Load sheet
611
                            $docSheet = $excel->createSheet();
612
                            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
613
                            //        references in formula cells... during the load, all formulae should be correct,
614
                            //        and we're simply bringing the worksheet name in line with the formula, not the
615 69
                            //        reverse
616 69
                            $docSheet->setTitle((string) $eleSheet['name'], false, false);
617
                            $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
618 69
                            //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
619 69
                            $xmlSheet = simplexml_load_string(
620 69
                                $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")),
621 69
                                'SimpleXMLElement',
622
                                Settings::getLibXmlLoaderOptions()
623
                            );
624 69
625
                            $sharedFormulas = [];
626 69
627 2
                            if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') {
628
                                $docSheet->setSheetState((string) $eleSheet['state']);
629
                            }
630 69
631 69
                            if ($xmlSheet) {
632 69
                                if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) {
633 69
                                    $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);
634
                                    $sheetViews->load();
635
                                }
636 69
637 69
                                $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);
1 ignored issue
show
Bug introduced by
It seems like $xmlSheet can also be of type false; however, parameter $worksheetXml of PhpOffice\PhpSpreadsheet...wOptions::__construct() does only seem to accept SimpleXMLElement|null, maybe add an additional type check? ( Ignorable by Annotation )

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

637
                                $sheetViewOptions = new SheetViewOptions($docSheet, /** @scrutinizer ignore-type */ $xmlSheet);
Loading history...
638
                                $sheetViewOptions->load($this->getReadDataOnly());
639 69
640 69
                                (new ColumnAndRowAttributes($docSheet, $xmlSheet))
1 ignored issue
show
Bug introduced by
It seems like $xmlSheet can also be of type false; however, parameter $worksheetXml of PhpOffice\PhpSpreadsheet...tributes::__construct() does only seem to accept SimpleXMLElement|null, maybe add an additional type check? ( Ignorable by Annotation )

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

640
                                (new ColumnAndRowAttributes($docSheet, /** @scrutinizer ignore-type */ $xmlSheet))
Loading history...
641
                                    ->load($this->getReadFilter(), $this->getReadDataOnly());
642
                            }
643 69
644 62
                            if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
645 62
                                $cIndex = 1; // Cell Start from 1
646 62
                                foreach ($xmlSheet->sheetData->row as $row) {
647 62
                                    $rowIndex = 1;
648 62
                                    foreach ($row->c as $c) {
649 62
                                        $r = (string) $c['r'];
650 1
                                        if ($r == '') {
651
                                            $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
652 62
                                        }
653 62
                                        $cellDataType = (string) $c['t'];
654 62
                                        $value = null;
655
                                        $calculatedValue = null;
656
657 62
                                        // Read cell?
658 62
                                        if ($this->getReadFilter() !== null) {
659
                                            $coordinates = Coordinate::coordinateFromString($r);
660 62
661 3
                                            if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
662
                                                ++$rowIndex;
663 3
664
                                                continue;
665
                                            }
666
                                        }
667
668
                                        // Read cell!
669 62
                                        switch ($cellDataType) {
670 32
                                            case 's':
671 32
                                                if ((string) $c->v != '') {
672
                                                    $value = $sharedStrings[(int) ($c->v)];
673 32
674 32
                                                    if ($value instanceof RichText) {
675
                                                        $value = clone $value;
676
                                                    }
677
                                                } else {
678
                                                    $value = '';
679
                                                }
680 32
681 49
                                                break;
682 5
                                            case 'b':
683 3
                                                if (!isset($c->f)) {
684
                                                    $value = self::castToBoolean($c);
685
                                                } else {
686 2
                                                    // Formula
687 2
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean');
688
                                                    if (isset($c->f['t'])) {
689
                                                        $att = $c->f;
690
                                                        $docSheet->getCell($r)->setFormulaAttributes($att);
691
                                                    }
692
                                                }
693 5
694 45
                                                break;
695 2
                                            case 'inlineStr':
696
                                                if (isset($c->f)) {
697
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
698 2
                                                } else {
699
                                                    $value = $this->parseRichText($c->is);
700
                                                }
701 2
702 45
                                                break;
703
                                            case 'e':
704
                                                if (!isset($c->f)) {
705
                                                    $value = self::castToError($c);
706
                                                } else {
707
                                                    // Formula
708
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
709
                                                }
710
711
                                                break;
712 45
                                            default:
713 43
                                                if (!isset($c->f)) {
714
                                                    $value = self::castToString($c);
715
                                                } else {
716 7
                                                    // Formula
717
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
718
                                                }
719 45
720
                                                break;
721
                                        }
722
723 62
                                        // read empty cells or the cells are not empty
724
                                        if ($this->readEmptyCells || ($value !== null && $value !== '')) {
725 62
                                            // Rich text?
726
                                            if ($value instanceof RichText && $this->readDataOnly) {
727
                                                $value = $value->getPlainText();
728
                                            }
729 62
730
                                            $cell = $docSheet->getCell($r);
731 62
                                            // Assign value
732 36
                                            if ($cellDataType != '') {
733
                                                $cell->setValueExplicit($value, $cellDataType);
734 43
                                            } else {
735
                                                $cell->setValue($value);
736 62
                                            }
737 8
                                            if ($calculatedValue !== null) {
738
                                                $cell->setCalculatedValue($calculatedValue);
739
                                            }
740
741 62
                                            // Style information?
742
                                            if ($c['s'] && !$this->readDataOnly) {
743 14
                                                // no style index means 0, it seems
744 14
                                                $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ?
745
                                                    (int) ($c['s']) : 0);
746
                                            }
747 62
                                        }
748
                                        ++$rowIndex;
749 62
                                    }
750
                                    ++$cIndex;
751
                                }
752
                            }
753 69
754 6
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) {
755
                                (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
756
                            }
757 69
758 69
                            $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells'];
759 58
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
760 58
                                foreach ($aKeys as $key) {
761 58
                                    $method = 'set' . ucfirst($key);
762
                                    $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key]));
763
                                }
764
                            }
765 69
766 58
                            if ($xmlSheet) {
767 58
                                $this->readSheetProtection($docSheet, $xmlSheet);
768 2
                            }
769 2
770
                            if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
771
                                (new AutoFilter($docSheet, $xmlSheet))->load();
772
                            }
773
774 69
                            if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
775 1
                                foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) {
776
                                    $mergeRef = (string) $mergeCell['ref'];
777
                                    if (strpos($mergeRef, ':') !== false) {
778 69
                                        $docSheet->mergeCells((string) $mergeCell['ref']);
779 8
                                    }
780 8
                                }
781 8
                            }
782 8
783
                            if ($xmlSheet && !$this->readDataOnly) {
784
                                $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
785
                            }
786
787 69
                            if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
788 69
                                (new DataValidations($docSheet, $xmlSheet))->load();
789
                            }
790
791 69
                            // unparsed sheet AlternateContent
792 1
                            if ($xmlSheet && !$this->readDataOnly) {
793
                                $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
794
                                if ($mc->AlternateContent) {
795
                                    foreach ($mc->AlternateContent as $alternateContent) {
796 69
                                        $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
797 69
                                    }
798 69
                                }
799 1
                            }
800 1
801
                            // Add hyperlinks
802
                            if (!$this->readDataOnly) {
803
                                $hyperlinkReader = new Hyperlinks($docSheet);
804
                                // Locate hyperlink relations
805
                                $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
806 69
                                if ($zip->locateName($relationsFileName)) {
807 69
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
808
                                    $relsWorksheet = simplexml_load_string(
809 69
                                        $this->securityScanner->scan(
810 69
                                            $this->getFromZipArchive($zip, $relationsFileName)
811
                                        ),
812 63
                                        'SimpleXMLElement',
813 63
                                        Settings::getLibXmlLoaderOptions()
814 63
                                    );
815
                                    $hyperlinkReader->readHyperlinks($relsWorksheet);
1 ignored issue
show
Bug introduced by
It seems like $relsWorksheet can also be of type false; however, parameter $relsWorksheet of PhpOffice\PhpSpreadsheet...links::readHyperlinks() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

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

815
                                    $hyperlinkReader->readHyperlinks(/** @scrutinizer ignore-type */ $relsWorksheet);
Loading history...
816 63
                                }
817 63
818
                                // Loop through hyperlinks
819 63
                                if ($xmlSheet && $xmlSheet->hyperlinks) {
820
                                    $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);
821
                                }
822
                            }
823 69
824 2
                            // Add comments
825
                            $comments = [];
826
                            $vmlComments = [];
827
                            if (!$this->readDataOnly) {
828
                                // Locate comment relations
829 69
                                if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
830 69
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
831 69
                                    $relsWorksheet = simplexml_load_string(
832
                                        $this->securityScanner->scan(
833 69
                                            $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
834
                                        ),
835 63
                                        'SimpleXMLElement',
836 63
                                        Settings::getLibXmlLoaderOptions()
837 63
                                    );
838
                                    foreach ($relsWorksheet->Relationship as $ele) {
839 63
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') {
840 63
                                            $comments[(string) $ele['Id']] = (string) $ele['Target'];
841
                                        }
842 63
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
843 17
                                            $vmlComments[(string) $ele['Id']] = (string) $ele['Target'];
844 3
                                        }
845
                                    }
846 17
                                }
847 4
848
                                // Loop through comments
849
                                foreach ($comments as $relName => $relPath) {
850
                                    // Load comments file
851
                                    $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
852
                                    $commentsFile = simplexml_load_string(
853 69
                                        $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
854
                                        'SimpleXMLElement',
855 3
                                        Settings::getLibXmlLoaderOptions()
856 3
                                    );
857 3
858 3
                                    // Utility variables
859 3
                                    $authors = [];
860
861
                                    // Loop through authors
862
                                    foreach ($commentsFile->authors->author as $author) {
863 3
                                        $authors[] = (string) $author;
864
                                    }
865
866 3
                                    // Loop through contents
867 3
                                    foreach ($commentsFile->commentList->comment as $comment) {
868
                                        if (!empty($comment['authorId'])) {
869
                                            $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]);
870
                                        }
871 3
                                        $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text));
872 3
                                    }
873
                                }
874
875 3
                                // later we will remove from it real vmlComments
876
                                $unparsedVmlDrawings = $vmlComments;
877
878
                                // Loop through VML comments
879
                                foreach ($vmlComments as $relName => $relPath) {
880 69
                                    // Load VML comments file
881
                                    $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
882
883 69
                                    try {
884
                                        $vmlCommentsFile = simplexml_load_string(
885 4
                                            $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
886
                                            'SimpleXMLElement',
887
                                            Settings::getLibXmlLoaderOptions()
888 4
                                        );
889 4
                                        $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
890 4
                                    } catch (Throwable $ex) {
891 4
                                        //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
892
                                        continue;
893 4
                                    }
894
895
                                    $shapes = $vmlCommentsFile->xpath('//v:shape');
896
                                    foreach ($shapes as $shape) {
897
                                        $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
898
899 4
                                        if (isset($shape['style'])) {
900 4
                                            $style = (string) $shape['style'];
901 4
                                            $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
902
                                            $column = null;
903 4
                                            $row = null;
904 4
905 4
                                            $clientData = $shape->xpath('.//x:ClientData');
906 4
                                            if (is_array($clientData) && !empty($clientData)) {
907 4
                                                $clientData = $clientData[0];
908
909 4
                                                if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
910 4
                                                    $temp = $clientData->xpath('.//x:Row');
911 4
                                                    if (is_array($temp)) {
912
                                                        $row = $temp[0];
913 4
                                                    }
914 3
915 3
                                                    $temp = $clientData->xpath('.//x:Column');
916 3
                                                    if (is_array($temp)) {
917
                                                        $column = $temp[0];
918
                                                    }
919 3
                                                }
920 3
                                            }
921 3
922
                                            if (($column !== null) && ($row !== null)) {
923
                                                // Set comment properties
924
                                                $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1);
925
                                                $comment->getFillColor()->setRGB($fillColor);
926 4
927
                                                // Parse style
928 3
                                                $styleArray = explode(';', str_replace(' ', '', $style));
929 3
                                                foreach ($styleArray as $stylePair) {
930
                                                    $stylePair = explode(':', $stylePair);
931
932 3
                                                    if ($stylePair[0] == 'margin-left') {
933 3
                                                        $comment->setMarginLeft($stylePair[1]);
934 3
                                                    }
935
                                                    if ($stylePair[0] == 'margin-top') {
936 3
                                                        $comment->setMarginTop($stylePair[1]);
937 3
                                                    }
938
                                                    if ($stylePair[0] == 'width') {
939 3
                                                        $comment->setWidth($stylePair[1]);
940 3
                                                    }
941
                                                    if ($stylePair[0] == 'height') {
942 3
                                                        $comment->setHeight($stylePair[1]);
943 3
                                                    }
944
                                                    if ($stylePair[0] == 'visibility') {
945 3
                                                        $comment->setVisible($stylePair[1] == 'visible');
946 3
                                                    }
947
                                                }
948 3
949 3
                                                unset($unparsedVmlDrawings[$relName]);
950
                                            }
951
                                        }
952
                                    }
953 3
                                }
954
955
                                // unparsed vmlDrawing
956
                                if ($unparsedVmlDrawings) {
957
                                    foreach ($unparsedVmlDrawings as $rId => $relPath) {
958
                                        $rId = substr($rId, 3); // rIdXXX
959
                                        $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
960 69
                                        $unparsedVmlDrawing[$rId] = [];
961 1
                                        $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
962 1
                                        $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
963 1
                                        $unparsedVmlDrawing[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
964 1
                                        unset($unparsedVmlDrawing);
965 1
                                    }
966 1
                                }
967 1
968 1
                                // Header/footer images
969
                                if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) {
970
                                    if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
971
                                        //~ http://schemas.openxmlformats.org/package/2006/relationships"
972
                                        $relsWorksheet = simplexml_load_string(
973 69
                                            $this->securityScanner->scan(
974
                                                $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
975
                                            ),
976
                                            'SimpleXMLElement',
977
                                            Settings::getLibXmlLoaderOptions()
978
                                        );
979
                                        $vmlRelationship = '';
980
981
                                        foreach ($relsWorksheet->Relationship as $ele) {
982
                                            if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
983
                                                $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
984
                                            }
985
                                        }
986
987
                                        if ($vmlRelationship != '') {
988
                                            // Fetch linked images
989
                                            //~ http://schemas.openxmlformats.org/package/2006/relationships"
990
                                            $relsVML = simplexml_load_string(
991
                                                $this->securityScanner->scan(
992
                                                    $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels')
993
                                                ),
994
                                                'SimpleXMLElement',
995
                                                Settings::getLibXmlLoaderOptions()
996
                                            );
997
                                            $drawings = [];
998
                                            if (isset($relsVML->Relationship)) {
999
                                                foreach ($relsVML->Relationship as $ele) {
1000
                                                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1001
                                                        $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']);
1002
                                                    }
1003
                                                }
1004
                                            }
1005
                                            // Fetch VML document
1006
                                            $vmlDrawing = simplexml_load_string(
1007
                                                $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)),
1008
                                                'SimpleXMLElement',
1009
                                                Settings::getLibXmlLoaderOptions()
1010
                                            );
1011
                                            $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
1012
1013
                                            $hfImages = [];
1014
1015
                                            $shapes = $vmlDrawing->xpath('//v:shape');
1016
                                            foreach ($shapes as $idx => $shape) {
1017
                                                $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
1018
                                                $imageData = $shape->xpath('//v:imagedata');
1019
1020
                                                if (!$imageData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $imageData of type SimpleXMLElement[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1021
                                                    continue;
1022
                                                }
1023
1024
                                                $imageData = $imageData[$idx];
1025
1026
                                                $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office');
1027
                                                $style = self::toCSSArray((string) $shape['style']);
1028
1029
                                                $hfImages[(string) $shape['id']] = new HeaderFooterDrawing();
1030
                                                if (isset($imageData['title'])) {
1031
                                                    $hfImages[(string) $shape['id']]->setName((string) $imageData['title']);
1032
                                                }
1033
1034
                                                $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($pFilename) . '#' . $drawings[(string) $imageData['relid']], false);
1035
                                                $hfImages[(string) $shape['id']]->setResizeProportional(false);
1036
                                                $hfImages[(string) $shape['id']]->setWidth($style['width']);
1037
                                                $hfImages[(string) $shape['id']]->setHeight($style['height']);
1038
                                                if (isset($style['margin-left'])) {
1039
                                                    $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']);
1040
                                                }
1041
                                                $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']);
1042
                                                $hfImages[(string) $shape['id']]->setResizeProportional(true);
1043
                                            }
1044
1045
                                            $docSheet->getHeaderFooter()->setImages($hfImages);
1046
                                        }
1047
                                    }
1048
                                }
1049
                            }
1050
1051
                            // TODO: Autoshapes from twoCellAnchors!
1052
                            if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
1053
                                //~ http://schemas.openxmlformats.org/package/2006/relationships"
1054
                                $relsWorksheet = simplexml_load_string(
1055 69
                                    $this->securityScanner->scan(
1056
                                        $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
1057 63
                                    ),
1058 63
                                    'SimpleXMLElement',
1059 63
                                    Settings::getLibXmlLoaderOptions()
1060
                                );
1061 63
                                $drawings = [];
1062 63
                                foreach ($relsWorksheet->Relationship as $ele) {
1063
                                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
1064 63
                                        $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
1065 63
                                    }
1066 17
                                }
1067 8
                                if ($xmlSheet->drawing && !$this->readDataOnly) {
1068
                                    $unparsedDrawings = [];
1069
                                    foreach ($xmlSheet->drawing as $drawing) {
1070 63
                                        $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');
1071 8
                                        $fileDrawing = $drawings[$drawingRelId];
1072 8
                                        //~ http://schemas.openxmlformats.org/package/2006/relationships"
1073 8
                                        $relsDrawing = simplexml_load_string(
1074 8
                                            $this->securityScanner->scan(
1075
                                                $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels')
1076 8
                                            ),
1077 8
                                            'SimpleXMLElement',
1078 8
                                            Settings::getLibXmlLoaderOptions()
1079
                                        );
1080 8
                                        $images = [];
1081 8
                                        $hyperlinks = [];
1082
                                        if ($relsDrawing && $relsDrawing->Relationship) {
1083 8
                                            foreach ($relsDrawing->Relationship as $ele) {
1084 8
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
1085 8
                                                    $hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
1086 6
                                                }
1087 6
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1088 2
                                                    $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
1089
                                                } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {
1090 6
                                                    if ($this->includeCharts) {
1091 6
                                                        $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [
1092 4
                                                            'id' => (string) $ele['Id'],
1093 2
                                                            'sheet' => $docSheet->getTitle(),
1094 2
                                                        ];
1095 2
                                                    }
1096 2
                                                }
1097
                                            }
1098
                                        }
1099
                                        $xmlDrawing = simplexml_load_string(
1100
                                            $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
1101
                                            'SimpleXMLElement',
1102 8
                                            Settings::getLibXmlLoaderOptions()
1103 8
                                        );
1104 8
                                        $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
1105 8
1106
                                        if ($xmlDrawingChildren->oneCellAnchor) {
1107 8
                                            foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
1108
                                                if ($oneCellAnchor->pic->blipFill) {
1109 8
                                                    /** @var SimpleXMLElement $blip */
1110 4
                                                    $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
1111 4
                                                    /** @var SimpleXMLElement $xfrm */
1112
                                                    $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
1113 4
                                                    /** @var SimpleXMLElement $outerShdw */
1114
                                                    $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1115 4
                                                    /** @var SimpleXMLElement $hlinkClick */
1116
                                                    $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...
1117 4
1118
                                                    $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
1119 4
                                                    $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
1120
                                                    $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
1121 4
                                                    $objDrawing->setPath(
1122 4
                                                        'zip://' . File::realpath($pFilename) . '#' .
1123 4
                                                        $images[(string) self::getArrayItem(
1124 4
                                                            $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
1125 4
                                                            'embed'
1126 4
                                                        )],
1127 4
                                                        false
1128 4
                                                    );
1129
                                                    $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1));
1130 4
                                                    $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff));
1131
                                                    $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff));
1132 4
                                                    $objDrawing->setResizeProportional(false);
1133 4
                                                    $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')));
1134 4
                                                    $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')));
1135 4
                                                    if ($xfrm) {
1136 4
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));
1 ignored issue
show
Bug introduced by
It seems like self::getArrayItem($xfrm->attributes(), 'rot') can also be of type SimpleXMLElement; however, parameter $pValue of PhpOffice\PhpSpreadsheet...awing::angleToDegrees() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1136
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(/** @scrutinizer ignore-type */ self::getArrayItem($xfrm->attributes(), 'rot')));
Loading history...
1137 4
                                                    }
1138 4
                                                    if ($outerShdw) {
1139 4
                                                        $shadow = $objDrawing->getShadow();
1140
                                                        $shadow->setVisible(true);
1141 4
                                                        $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
1 ignored issue
show
Bug introduced by
It seems like self::getArrayItem($oute...ttributes(), 'blurRad') can also be of type SimpleXMLElement; however, parameter $pValue of PhpOffice\PhpSpreadsheet...\Drawing::EMUToPixels() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1141
                                                        $shadow->setBlurRadius(Drawing::EMUToPixels(/** @scrutinizer ignore-type */ self::getArrayItem($outerShdw->attributes(), 'blurRad')));
Loading history...
1142 2
                                                        $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
1143 2
                                                        $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
1144 2
                                                        $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
0 ignored issues
show
Bug introduced by
(string)self::getArrayIt...->attributes(), 'algn') of type string is incompatible with the type integer expected by parameter $pValue of PhpOffice\PhpSpreadsheet...\Shadow::setAlignment(). ( Ignorable by Annotation )

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

1144
                                                        $shadow->setAlignment(/** @scrutinizer ignore-type */ (string) self::getArrayItem($outerShdw->attributes(), 'algn'));
Loading history...
1145 2
                                                        $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr;
1146 2
                                                        $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
1147 2
                                                        $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
1148 2
                                                    }
1149 2
1150 2
                                                    $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
1151
1152
                                                    $objDrawing->setWorksheet($docSheet);
1153 4
                                                } else {
1154
                                                    //    ? Can charts be positioned with a oneCellAnchor ?
1155 4
                                                    $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);
0 ignored issues
show
Unused Code introduced by
The assignment to $coordinates is dead and can be removed.
Loading history...
1156
                                                    $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff);
0 ignored issues
show
Unused Code introduced by
The assignment to $offsetX is dead and can be removed.
Loading history...
1157
                                                    $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);
0 ignored issues
show
Unused Code introduced by
The assignment to $offsetY is dead and can be removed.
Loading history...
1158
                                                    $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'));
0 ignored issues
show
Unused Code introduced by
The assignment to $width is dead and can be removed.
Loading history...
1159
                                                    $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'));
0 ignored issues
show
Unused Code introduced by
The assignment to $height is dead and can be removed.
Loading history...
1160
                                                }
1161
                                            }
1162
                                        }
1163
                                        if ($xmlDrawingChildren->twoCellAnchor) {
1164
                                            foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
1165
                                                if ($twoCellAnchor->pic->blipFill) {
1166 8
                                                    $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
1167 2
                                                    $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
1168 2
                                                    $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1169 2
                                                    $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
1170 2
                                                    $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
1171 2
                                                    $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
1172 2
                                                    $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
1173 2
                                                    $objDrawing->setPath(
1174 2
                                                        'zip://' . File::realpath($pFilename) . '#' .
1175 2
                                                        $images[(string) self::getArrayItem(
1176 2
                                                            $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
1177 2
                                                            'embed'
1178 2
                                                        )],
1179 2
                                                        false
1180 2
                                                    );
1181
                                                    $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1));
1182 2
                                                    $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff));
1183
                                                    $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff));
1184 2
                                                    $objDrawing->setResizeProportional(false);
1185 2
1186 2
                                                    if ($xfrm) {
1187 2
                                                        $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx')));
1188
                                                        $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy')));
1189 2
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));
1190 2
                                                    }
1191 2
                                                    if ($outerShdw) {
1192 2
                                                        $shadow = $objDrawing->getShadow();
1193
                                                        $shadow->setVisible(true);
1194 2
                                                        $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
1195
                                                        $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
1196
                                                        $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
1197
                                                        $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
1198
                                                        $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr;
1199
                                                        $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
1200
                                                        $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
1201
                                                    }
1202
1203
                                                    $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
1204
1205
                                                    $objDrawing->setWorksheet($docSheet);
1206 2
                                                } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
1207
                                                    $fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
1208 2
                                                    $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff);
1209 2
                                                    $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff);
1210 2
                                                    $toCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);
1211 2
                                                    $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
1212 2
                                                    $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
1213 2
                                                    $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;
1214 2
                                                    /** @var SimpleXMLElement $chartRef */
1215 2
                                                    $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart;
1216 2
                                                    $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
1217
1218 2
                                                    $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
1219 2
                                                        'fromCoordinate' => $fromCoordinate,
1220
                                                        'fromOffsetX' => $fromOffsetX,
1221 2
                                                        'fromOffsetY' => $fromOffsetY,
1222 2
                                                        'toCoordinate' => $toCoordinate,
1223 2
                                                        'toOffsetX' => $toOffsetX,
1224 2
                                                        'toOffsetY' => $toOffsetY,
1225 2
                                                        'worksheetTitle' => $docSheet->getTitle(),
1226 2
                                                    ];
1227 2
                                                }
1228 2
                                            }
1229
                                        }
1230
                                        if ($relsDrawing === false && $xmlDrawing->count() == 0) {
1231
                                            // Save Drawing without rels and children as unparsed
1232
                                            $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
1233 8
                                        }
1234
                                    }
1235 2
1236
                                    // store original rId of drawing files
1237
                                    $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
1238
                                    foreach ($relsWorksheet->Relationship as $ele) {
1239
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
1240 8
                                            $drawingRelId = (string) $ele['Id'];
1241 8
                                            $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
1242 8
                                            if (isset($unparsedDrawings[$drawingRelId])) {
1243 8
                                                $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
1244 8
                                            }
1245 8
                                        }
1246 2
                                    }
1247
1248
                                    // unparsed drawing AlternateContent
1249
                                    $xmlAltDrawing = simplexml_load_string(
1250
                                        $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fileDrawing does not seem to be defined for all execution paths leading up to this point.
Loading history...
1251
                                        'SimpleXMLElement',
1252 8
                                        Settings::getLibXmlLoaderOptions()
1253 8
                                    )->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
1254 8
1255 8
                                    if ($xmlAltDrawing->AlternateContent) {
1256 8
                                        foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
1257
                                            $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML();
1258 8
                                        }
1259 1
                                    }
1260 1
                                }
1261
                            }
1262
1263
                            $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
1264
                            $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
1265
1266 69
                            // Loop through definedNames
1267 69
                            if ($xmlWorkbook->definedNames) {
1268
                                foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
1269
                                    // Extract range
1270 69
                                    $extractedRange = (string) $definedName;
1271 53
                                    if (($spos = strpos($extractedRange, '!')) !== false) {
1272
                                        $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
1273 3
                                    } else {
1274 3
                                        $extractedRange = str_replace('$', '', $extractedRange);
1275 3
                                    }
1276
1277
                                    // Valid range?
1278
                                    if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') {
1279
                                        continue;
1280
                                    }
1281 3
1282
                                    // Some definedNames are only applicable if we are on the same sheet...
1283
                                    if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) {
1284
                                        // Switch on type
1285
                                        switch ((string) $definedName['name']) {
1286 3
                                            case '_xlnm._FilterDatabase':
1287
                                                if ((string) $definedName['hidden'] !== '1') {
1288 3
                                                    $extractedRange = explode(',', $extractedRange);
1289 3
                                                    foreach ($extractedRange as $range) {
1290 1
                                                        $autoFilterRange = $range;
1291
                                                        if (strpos($autoFilterRange, ':') !== false) {
1292
                                                            $docSheet->getAutoFilter()->setRange($autoFilterRange);
1293
                                                        }
1294
                                                    }
1295
                                                }
1296
1297
                                                break;
1298
                                            case '_xlnm.Print_Titles':
1299
                                                // Split $extractedRange
1300 1
                                                $extractedRange = explode(',', $extractedRange);
1301 2
1302
                                                // Set print titles
1303 1
                                                foreach ($extractedRange as $range) {
1304
                                                    $matches = [];
1305
                                                    $range = str_replace('$', '', $range);
1306 1
1307 1
                                                    // check for repeating columns, e g. 'A:A' or 'A:D'
1308 1
                                                    if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) {
1309
                                                        $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]);
1310
                                                    } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) {
1311 1
                                                        // check for repeating rows, e.g. '1:1' or '1:5'
1312
                                                        $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]);
1313 1
                                                    }
1314
                                                }
1315 1
1316
                                                break;
1317
                                            case '_xlnm.Print_Area':
1318
                                                $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
1319 1
                                                $newRangeSets = [];
1320 1
                                                foreach ($rangeSets as $rangeSet) {
1321 1
                                                    [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
1322 1
                                                    if (strpos($rangeSet, ':') === false) {
1323 1
                                                        $rangeSet = $rangeSet . ':' . $rangeSet;
1324 1
                                                    }
1325 1
                                                    $newRangeSets[] = str_replace('$', '', $rangeSet);
1326
                                                }
1327
                                                $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
1328 1
1329
                                                break;
1330 1
                                            default:
1331
                                                break;
1332 1
                                        }
1333
                                    }
1334
                                }
1335
                            }
1336
1337
                            // Next sheet id
1338
                            ++$sheetId;
1339
                        }
1340
1341 69
                        // Loop through definedNames
1342
                        if ($xmlWorkbook->definedNames) {
1343
                            foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
1344
                                // Extract range
1345 69
                                $extractedRange = (string) $definedName;
1346 53
                                if (($spos = strpos($extractedRange, '!')) !== false) {
1347
                                    $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
1348 3
                                } else {
1349 3
                                    $extractedRange = str_replace('$', '', $extractedRange);
1350 3
                                }
1351
1352
                                // Valid range?
1353
                                if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') {
1354
                                    continue;
1355
                                }
1356 3
1357
                                // Some definedNames are only applicable if we are on the same sheet...
1358
                                if ((string) $definedName['localSheetId'] != '') {
1359
                                    // Local defined name
1360
                                    // Switch on type
1361 3
                                    switch ((string) $definedName['name']) {
1362
                                        case '_xlnm._FilterDatabase':
1363
                                        case '_xlnm.Print_Titles':
1364 3
                                        case '_xlnm.Print_Area':
1365 3
                                            break;
1366 2
                                        default:
1367 1
                                            if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
1368 3
                                                if (strpos((string) $definedName, '!') !== false) {
1369
                                                    $range = Worksheet::extractSheetTitle((string) $definedName, true);
1370
                                                    $range[0] = str_replace("''", "'", $range[0]);
1371
                                                    $range[0] = str_replace("'", '', $range[0]);
1372
                                                    if ($worksheet = $docSheet->getParent()->getSheetByName($range[0])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $docSheet does not seem to be defined for all execution paths leading up to this point.
Loading history...
1373
                                                        $extractedRange = str_replace('$', '', $range[1]);
1374
                                                        $scope = $docSheet->getParent()->getSheet($mapSheetId[(int) $definedName['localSheetId']]);
1375
                                                        $excel->addNamedRange(new NamedRange((string) $definedName['name'], $worksheet, $extractedRange, true, $scope));
1376
                                                    }
1377
                                                }
1378
                                            }
1379
1380
                                            break;
1381
                                    }
1382
                                } elseif (!isset($definedName['localSheetId'])) {
1383 3
                                    // "Global" definedNames
1384
                                    $locatedSheet = null;
1385
                                    $extractedSheetName = '';
1386
                                    if (strpos((string) $definedName, '!') !== false) {
1387
                                        // Extract sheet name
1388
                                        $extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true);
1389
                                        $extractedSheetName = trim($extractedSheetName[0], "'");
1390
1391
                                        // Locate sheet
1392
                                        $locatedSheet = $excel->getSheetByName($extractedSheetName);
1393
1394
                                        // Modify range
1395
                                        [$worksheetName, $extractedRange] = Worksheet::extractSheetTitle($extractedRange, true);
1396
                                    }
1397
1398
                                    if ($locatedSheet !== null) {
1399
                                        $excel->addNamedRange(new NamedRange((string) $definedName['name'], $locatedSheet, $extractedRange, false));
1400
                                    }
1401
                                }
1402
                            }
1403
                        }
1404
                    }
1405
1406
                    if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) {
1407
                        $workbookView = $xmlWorkbook->bookViews->workbookView;
1408
1409 69
                        // active sheet index
1410 69
                        $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index
1411
1412
                        // keep active sheet index if sheet is still loaded, else first sheet is set as the active
1413 69
                        if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
1414
                            $excel->setActiveSheetIndex($mapSheetId[$activeTab]);
1415
                        } else {
1416 69
                            if ($excel->getSheetCount() == 0) {
1417 69
                                $excel->createSheet();
1418
                            }
1419
                            $excel->setActiveSheetIndex(0);
1420
                        }
1421
1422
                        if (isset($workbookView['showHorizontalScroll'])) {
1423
                            $showHorizontalScroll = (string) $workbookView['showHorizontalScroll'];
1424
                            $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
1425 69
                        }
1426 52
1427 52
                        if (isset($workbookView['showVerticalScroll'])) {
1428
                            $showVerticalScroll = (string) $workbookView['showVerticalScroll'];
1429
                            $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
1430 69
                        }
1431 52
1432 52
                        if (isset($workbookView['showSheetTabs'])) {
1433
                            $showSheetTabs = (string) $workbookView['showSheetTabs'];
1434
                            $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
1435 69
                        }
1436 52
1437 52
                        if (isset($workbookView['minimized'])) {
1438
                            $minimized = (string) $workbookView['minimized'];
1439
                            $excel->setMinimized($this->castXsdBooleanToBool($minimized));
1440 69
                        }
1441 52
1442 52
                        if (isset($workbookView['autoFilterDateGrouping'])) {
1443
                            $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping'];
1444
                            $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
1445 69
                        }
1446 52
1447 52
                        if (isset($workbookView['firstSheet'])) {
1448
                            $firstSheet = (string) $workbookView['firstSheet'];
1449
                            $excel->setFirstSheetIndex((int) $firstSheet);
1450 69
                        }
1451 52
1452 52
                        if (isset($workbookView['visibility'])) {
1453
                            $visibility = (string) $workbookView['visibility'];
1454
                            $excel->setVisibility($visibility);
1455 69
                        }
1456 52
1457 52
                        if (isset($workbookView['tabRatio'])) {
1458
                            $tabRatio = (string) $workbookView['tabRatio'];
1459
                            $excel->setTabRatio((int) $tabRatio);
1460 69
                        }
1461 52
                    }
1462 52
1463
                    break;
1464
            }
1465
        }
1466 69
1467
        if (!$this->readDataOnly) {
1468
            $contentTypes = simplexml_load_string(
1469
                $this->securityScanner->scan(
1470 69
                    $this->getFromZipArchive($zip, '[Content_Types].xml')
1471 69
                ),
1472 69
                'SimpleXMLElement',
1473 69
                Settings::getLibXmlLoaderOptions()
1474
            );
1475 69
1476 69
            // Default content types
1477
            foreach ($contentTypes->Default as $contentType) {
1478
                switch ($contentType['ContentType']) {
1479
                    case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
1480 69
                        $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
1481 69
1482 69
                        break;
1483 12
                }
1484
            }
1485 12
1486
            // Override content types
1487
            foreach ($contentTypes->Override as $contentType) {
1488
                switch ($contentType['ContentType']) {
1489
                    case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml':
1490 69
                        if ($this->includeCharts) {
1491 69
                            $chartEntryRef = ltrim($contentType['PartName'], '/');
1492 69
                            $chartElements = simplexml_load_string(
1493 2
                                $this->securityScanner->scan(
1494 2
                                    $this->getFromZipArchive($zip, $chartEntryRef)
1495 2
                                ),
1496 2
                                'SimpleXMLElement',
1497 2
                                Settings::getLibXmlLoaderOptions()
1498
                            );
1499 2
                            $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml'));
1 ignored issue
show
Bug introduced by
It seems like $chartElements can also be of type false; however, parameter $chartElements of PhpOffice\PhpSpreadsheet...Xlsx\Chart::readChart() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

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

1499
                            $objChart = Chart::readChart(/** @scrutinizer ignore-type */ $chartElements, basename($chartEntryRef, '.xml'));
Loading history...
1500 2
1501
                            if (isset($charts[$chartEntryRef])) {
1502 2
                                $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
1503
                                if (isset($chartDetails[$chartPositionRef])) {
1504 2
                                    $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);
1505 2
                                    $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
1506 2
                                    $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
1507 2
                                    $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
1508 2
                                }
1509 2
                            }
1510 2
                        }
1511
1512
                        break;
1513
1514
                    // unparsed
1515 2
                    case 'application/vnd.ms-excel.controlproperties+xml':
1516
                        $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
1517
1518 69
                        break;
1519 1
                }
1520
            }
1521 1
        }
1522
1523
        $excel->setUnparsedLoadedData($unparsedLoadedData);
1524
1525
        $zip->close();
1526 69
1527
        return $excel;
1528 69
    }
1529
1530 69
    private static function readColor($color, $background = false)
1531
    {
1532
        if (isset($color['rgb'])) {
1533 69
            return (string) $color['rgb'];
1534
        } elseif (isset($color['indexed'])) {
1535 69
            return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
1536 60
        } elseif (isset($color['theme'])) {
1537 17
            if (self::$theme !== null) {
1538 9
                $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
1539 14
                if (isset($color['tint'])) {
1540 13
                    $tintAdjust = (float) $color['tint'];
1541 13
                    $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
1542 13
                }
1543 1
1544 1
                return 'FF' . $returnColour;
1545
            }
1546
        }
1547 13
1548
        if ($background) {
1549
            return 'FFFFFFFF';
1550
        }
1551 2
1552
        return 'FF000000';
1553
    }
1554
1555 2
    /**
1556
     * @param SimpleXMLElement|stdClass $style
1557
     */
1558
    private static function readStyle(Style $docStyle, $style): void
1559
    {
1560
        $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
1561
1562 69
        // font
1563
        if (isset($style->font)) {
1564 69
            $docStyle->getFont()->setName((string) $style->font->name['val']);
1565
            $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

1565
            $docStyle->getFont()->setSize(/** @scrutinizer ignore-type */ (string) $style->font->sz['val']);
Loading history...
1566
            if (isset($style->font->b)) {
1567 69
                $docStyle->getFont()->setBold(!isset($style->font->b['val']) || self::boolean((string) $style->font->b['val']));
1568 69
            }
1569 69
            if (isset($style->font->i)) {
1570 69
                $docStyle->getFont()->setItalic(!isset($style->font->i['val']) || self::boolean((string) $style->font->i['val']));
1571 60
            }
1572
            if (isset($style->font->strike)) {
1573 69
                $docStyle->getFont()->setStrikethrough(!isset($style->font->strike['val']) || self::boolean((string) $style->font->strike['val']));
1574 54
            }
1575
            $docStyle->getFont()->getColor()->setARGB(self::readColor($style->font->color));
1576 69
1577 53
            if (isset($style->font->u) && !isset($style->font->u['val'])) {
1578
                $docStyle->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
1579 69
            } elseif (isset($style->font->u, $style->font->u['val'])) {
1580
                $docStyle->getFont()->setUnderline((string) $style->font->u['val']);
1581 69
            }
1582 1
1583 69
            if (isset($style->font->vertAlign, $style->font->vertAlign['val'])) {
1584 52
                $vertAlign = strtolower((string) $style->font->vertAlign['val']);
1585
                if ($vertAlign == 'superscript') {
1586
                    $docStyle->getFont()->setSuperscript(true);
1587 69
                }
1588 1
                if ($vertAlign == 'subscript') {
1589 1
                    $docStyle->getFont()->setSubscript(true);
1590 1
                }
1591
            }
1592 1
        }
1593 1
1594
        // fill
1595
        if (isset($style->fill)) {
1596
            if ($style->fill->gradientFill) {
1597
                /** @var SimpleXMLElement $gradientFill */
1598
                $gradientFill = $style->fill->gradientFill[0];
1599 69
                if (!empty($gradientFill['type'])) {
1600 69
                    $docStyle->getFill()->setFillType((string) $gradientFill['type']);
1601
                }
1602 2
                $docStyle->getFill()->setRotation((float) ($gradientFill['degree']));
1603 2
                $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
1604 2
                $docStyle->getFill()->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
1605
                $docStyle->getFill()->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
1606 2
            } elseif ($style->fill->patternFill) {
1607 2
                $patternType = (string) $style->fill->patternFill['patternType'] != '' ? (string) $style->fill->patternFill['patternType'] : 'solid';
1608 2
                $docStyle->getFill()->setFillType($patternType);
1609 2
                if ($style->fill->patternFill->fgColor) {
1610 69
                    $docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true));
1611 69
                }
1612 69
                if ($style->fill->patternFill->bgColor) {
1613 69
                    $docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true));
1614 6
                }
1615
            }
1616 69
        }
1617 6
1618
        // border
1619
        if (isset($style->border)) {
1620
            $diagonalUp = self::boolean((string) $style->border['diagonalUp']);
1621
            $diagonalDown = self::boolean((string) $style->border['diagonalDown']);
1622
            if (!$diagonalUp && !$diagonalDown) {
1623 69
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
1624 69
            } elseif ($diagonalUp && !$diagonalDown) {
1625 69
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
1626 69
            } elseif (!$diagonalUp && $diagonalDown) {
1627 69
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
1628 1
            } else {
1629 1
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
1630 1
            }
1631 1
            self::readBorder($docStyle->getBorders()->getLeft(), $style->border->left);
1632
            self::readBorder($docStyle->getBorders()->getRight(), $style->border->right);
1633
            self::readBorder($docStyle->getBorders()->getTop(), $style->border->top);
1634
            self::readBorder($docStyle->getBorders()->getBottom(), $style->border->bottom);
1635 69
            self::readBorder($docStyle->getBorders()->getDiagonal(), $style->border->diagonal);
1636 69
        }
1637 69
1638 69
        // alignment
1639 69
        if (isset($style->alignment)) {
1640
            $docStyle->getAlignment()->setHorizontal((string) $style->alignment['horizontal']);
1641
            $docStyle->getAlignment()->setVertical((string) $style->alignment['vertical']);
1642
1643 69
            $textRotation = 0;
1644 69
            if ((int) $style->alignment['textRotation'] <= 90) {
1645 69
                $textRotation = (int) $style->alignment['textRotation'];
1646
            } elseif ((int) $style->alignment['textRotation'] > 90) {
1647 69
                $textRotation = 90 - (int) $style->alignment['textRotation'];
1648 69
            }
1649 69
1650
            $docStyle->getAlignment()->setTextRotation((int) $textRotation);
1651
            $docStyle->getAlignment()->setWrapText(self::boolean((string) $style->alignment['wrapText']));
1652
            $docStyle->getAlignment()->setShrinkToFit(self::boolean((string) $style->alignment['shrinkToFit']));
1653
            $docStyle->getAlignment()->setIndent((int) ((string) $style->alignment['indent']) > 0 ? (int) ((string) $style->alignment['indent']) : 0);
1654 69
            $docStyle->getAlignment()->setReadOrder((int) ((string) $style->alignment['readingOrder']) > 0 ? (int) ((string) $style->alignment['readingOrder']) : 0);
1655 69
        }
1656 69
1657 69
        // protection
1658 69
        if (isset($style->protection)) {
1659
            if (isset($style->protection['locked'])) {
1660
                if (self::boolean((string) $style->protection['locked'])) {
1661
                    $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
1662 69
                } else {
1663 69
                    $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
1664 2
                }
1665
            }
1666
1667 2
            if (isset($style->protection['hidden'])) {
1668
                if (self::boolean((string) $style->protection['hidden'])) {
1669
                    $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
1670
                } else {
1671 69
                    $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
1672
                }
1673
            }
1674
        }
1675
1676
        // top-level style settings
1677
        if (isset($style->quotePrefix)) {
1678
            $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

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