Failed Conditions
Push — master ( f73478...9fa45f )
by Adrien
29:19
created

Xlsx::parseRichText()   D

Complexity

Conditions 26
Paths 3

Size

Total Lines 55
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 34.0756

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 26
eloc 36
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 55
ccs 27
cts 35
cp 0.7714
crap 34.0756
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

568
                    $styleReader = new Styles(/** @scrutinizer ignore-type */ $xmlStyles);
Loading history...
569 48
                    $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
570 48
                    $dxfs = $styleReader->dxfs($this->readDataOnly);
571 48
                    $styles = $styleReader->styles();
572
573
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
574 48
                    $xmlWorkbook = simplexml_load_string(
575 48
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
576 48
                        'SimpleXMLElement',
577 48
                        Settings::getLibXmlLoaderOptions()
578
                    );
579
580
                    // Set base date
581 48
                    if ($xmlWorkbook->workbookPr) {
582 47
                        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
583 47
                        if (isset($xmlWorkbook->workbookPr['date1904'])) {
584
                            if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) {
585
                                Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
586
                            }
587
                        }
588
                    }
589
590
                    // Set protection
591 48
                    $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

591
                    $this->readProtection($excel, /** @scrutinizer ignore-type */ $xmlWorkbook);
Loading history...
592
593 48
                    $sheetId = 0; // keep track of new sheet id in final workbook
594 48
                    $oldSheetId = -1; // keep track of old sheet id in final workbook
595 48
                    $countSkippedSheets = 0; // keep track of number of skipped sheets
596 48
                    $mapSheetId = []; // mapping of sheet ids from old to new
597
598 48
                    $charts = $chartDetails = [];
599
600 48
                    if ($xmlWorkbook->sheets) {
601
                        /** @var SimpleXMLElement $eleSheet */
602 48
                        foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
603 48
                            ++$oldSheetId;
604
605
                            // Check if sheet should be skipped
606 48
                            if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) {
607 1
                                ++$countSkippedSheets;
608 1
                                $mapSheetId[$oldSheetId] = null;
609
610 1
                                continue;
611
                            }
612
613
                            // Map old sheet id in original workbook to new sheet id.
614
                            // They will differ if loadSheetsOnly() is being used
615 48
                            $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
616
617
                            // Load sheet
618 48
                            $docSheet = $excel->createSheet();
619
                            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
620
                            //        references in formula cells... during the load, all formulae should be correct,
621
                            //        and we're simply bringing the worksheet name in line with the formula, not the
622
                            //        reverse
623 48
                            $docSheet->setTitle((string) $eleSheet['name'], false, false);
624 48
                            $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
625
                            //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
626 48
                            $xmlSheet = simplexml_load_string(
627 48
                                $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")),
628 48
                                'SimpleXMLElement',
629 48
                                Settings::getLibXmlLoaderOptions()
630
                            );
631
632 48
                            $sharedFormulas = [];
633
634 48
                            if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') {
635 2
                                $docSheet->setSheetState((string) $eleSheet['state']);
636
                            }
637
638 48
                            if ($xmlSheet) {
639 48
                                if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) {
640 48
                                    $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);
641 48
                                    $sheetViews->load();
642
                                }
643
644 48
                                $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

644
                                $sheetViewOptions = new SheetViewOptions($docSheet, /** @scrutinizer ignore-type */ $xmlSheet);
Loading history...
645 48
                                $sheetViewOptions->load($this->getReadDataOnly());
646
647 48
                                (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

647
                                (new ColumnAndRowAttributes($docSheet, /** @scrutinizer ignore-type */ $xmlSheet))
Loading history...
648 48
                                    ->load($this->getReadFilter(), $this->getReadDataOnly());
649
                            }
650
651 48
                            if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
652 41
                                $cIndex = 1; // Cell Start from 1
653 41
                                foreach ($xmlSheet->sheetData->row as $row) {
654 41
                                    $rowIndex = 1;
655 41
                                    foreach ($row->c as $c) {
656 41
                                        $r = (string) $c['r'];
657 41
                                        if ($r == '') {
658 1
                                            $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
659
                                        }
660 41
                                        $cellDataType = (string) $c['t'];
661 41
                                        $value = null;
662 41
                                        $calculatedValue = null;
663
664
                                        // Read cell?
665 41
                                        if ($this->getReadFilter() !== null) {
666 41
                                            $coordinates = Coordinate::coordinateFromString($r);
667
668 41
                                            if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
669 3
                                                $rowIndex += 1;
670
671 3
                                                continue;
672
                                            }
673
                                        }
674
675
                                        // Read cell!
676 41
                                        switch ($cellDataType) {
677 41
                                            case 's':
678 27
                                                if ((string) $c->v != '') {
679 27
                                                    $value = $sharedStrings[(int) ($c->v)];
680
681 27
                                                    if ($value instanceof RichText) {
682 27
                                                        $value = clone $value;
683
                                                    }
684
                                                } else {
685
                                                    $value = '';
686
                                                }
687
688 27
                                                break;
689 31
                                            case 'b':
690 5
                                                if (!isset($c->f)) {
691 3
                                                    $value = self::castToBoolean($c);
692
                                                } else {
693
                                                    // Formula
694 2
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean');
695 2
                                                    if (isset($c->f['t'])) {
696
                                                        $att = $c->f;
697
                                                        $docSheet->getCell($r)->setFormulaAttributes($att);
698
                                                    }
699
                                                }
700
701 5
                                                break;
702 27
                                            case 'inlineStr':
703 2
                                                if (isset($c->f)) {
704
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
705
                                                } else {
706 2
                                                    $value = $this->parseRichText($c->is);
707
                                                }
708
709 2
                                                break;
710 27
                                            case 'e':
711
                                                if (!isset($c->f)) {
712
                                                    $value = self::castToError($c);
713
                                                } else {
714
                                                    // Formula
715
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
716
                                                }
717
718
                                                break;
719
                                            default:
720 27
                                                if (!isset($c->f)) {
721 25
                                                    $value = self::castToString($c);
722
                                                } else {
723
                                                    // Formula
724 7
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
725
                                                }
726
727 27
                                                break;
728
                                        }
729
730
                                        // read empty cells or the cells are not empty
731 41
                                        if ($this->readEmptyCells || ($value !== null && $value !== '')) {
732
                                            // Check for numeric values
733 41
                                            if (is_numeric($value) && $cellDataType != 's') {
734 22
                                                if ($value == (int) $value) {
735 21
                                                    $value = (int) $value;
736 3
                                                } elseif ($value == (float) $value) {
737 3
                                                    $value = (float) $value;
738
                                                }
739
                                            }
740
741
                                            // Rich text?
742 41
                                            if ($value instanceof RichText && $this->readDataOnly) {
743
                                                $value = $value->getPlainText();
744
                                            }
745
746 41
                                            $cell = $docSheet->getCell($r);
747
                                            // Assign value
748 41
                                            if ($cellDataType != '') {
749 31
                                                $cell->setValueExplicit($value, $cellDataType);
750
                                            } else {
751 25
                                                $cell->setValue($value);
752
                                            }
753 41
                                            if ($calculatedValue !== null) {
754 8
                                                $cell->setCalculatedValue($calculatedValue);
755
                                            }
756
757
                                            // Style information?
758 41
                                            if ($c['s'] && !$this->readDataOnly) {
759
                                                // no style index means 0, it seems
760 13
                                                $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ?
761 13
                                                    (int) ($c['s']) : 0);
762
                                            }
763
                                        }
764 41
                                        $rowIndex += 1;
765
                                    }
766 41
                                    $cIndex += 1;
767
                                }
768
                            }
769
770 48
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) {
771 2
                                (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
772
                            }
773
774 48
                            $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells'];
775 48
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
776 39
                                foreach ($aKeys as $key) {
777 39
                                    $method = 'set' . ucfirst($key);
778 39
                                    $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key]));
779
                                }
780
                            }
781
782 48
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
783 39
                                $docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true);
784 39
                                if ($xmlSheet->protectedRanges->protectedRange) {
785 2
                                    foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
786 2
                                        $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
787
                                    }
788
                                }
789
                            }
790
791 48
                            if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
792 1
                                (new AutoFilter($docSheet, $xmlSheet))->load();
793
                            }
794
795 48
                            if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
796 8
                                foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) {
797 8
                                    $mergeRef = (string) $mergeCell['ref'];
798 8
                                    if (strpos($mergeRef, ':') !== false) {
799 8
                                        $docSheet->mergeCells((string) $mergeCell['ref']);
800
                                    }
801
                                }
802
                            }
803
804 48
                            if ($xmlSheet && !$this->readDataOnly) {
805 48
                                $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
806
                            }
807
808 48
                            if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
809 1
                                (new DataValidations($docSheet, $xmlSheet))->load();
810
                            }
811
812
                            // unparsed sheet AlternateContent
813 48
                            if ($xmlSheet && !$this->readDataOnly) {
814 48
                                $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
815 48
                                if ($mc->AlternateContent) {
816 1
                                    foreach ($mc->AlternateContent as $alternateContent) {
817 1
                                        $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
818
                                    }
819
                                }
820
                            }
821
822
                            // Add hyperlinks
823 48
                            if (!$this->readDataOnly) {
824 48
                                $hyperlinkReader = new Hyperlinks($docSheet);
825
                                // Locate hyperlink relations
826 48
                                $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
827 48
                                if ($zip->locateName($relationsFileName)) {
828
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
829 43
                                    $relsWorksheet = simplexml_load_string(
830 43
                                        $this->securityScanner->scan(
831 43
                                            $this->getFromZipArchive($zip, $relationsFileName)
832
                                        ),
833 43
                                        'SimpleXMLElement',
834 43
                                        Settings::getLibXmlLoaderOptions()
835
                                    );
836 43
                                    $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

836
                                    $hyperlinkReader->readHyperlinks(/** @scrutinizer ignore-type */ $relsWorksheet);
Loading history...
837
                                }
838
839
                                // Loop through hyperlinks
840 48
                                if ($xmlSheet && $xmlSheet->hyperlinks) {
841 2
                                    $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);
842
                                }
843
                            }
844
845
                            // Add comments
846 48
                            $comments = [];
847 48
                            $vmlComments = [];
848 48
                            if (!$this->readDataOnly) {
849
                                // Locate comment relations
850 48
                                if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
851
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
852 43
                                    $relsWorksheet = simplexml_load_string(
853 43
                                        $this->securityScanner->scan(
854 43
                                            $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
855
                                        ),
856 43
                                        'SimpleXMLElement',
857 43
                                        Settings::getLibXmlLoaderOptions()
858
                                    );
859 43
                                    foreach ($relsWorksheet->Relationship as $ele) {
860 16
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') {
861 3
                                            $comments[(string) $ele['Id']] = (string) $ele['Target'];
862
                                        }
863 16
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
864 4
                                            $vmlComments[(string) $ele['Id']] = (string) $ele['Target'];
865
                                        }
866
                                    }
867
                                }
868
869
                                // Loop through comments
870 48
                                foreach ($comments as $relName => $relPath) {
871
                                    // Load comments file
872 3
                                    $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
873 3
                                    $commentsFile = simplexml_load_string(
874 3
                                        $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
875 3
                                        'SimpleXMLElement',
876 3
                                        Settings::getLibXmlLoaderOptions()
877
                                    );
878
879
                                    // Utility variables
880 3
                                    $authors = [];
881
882
                                    // Loop through authors
883 3
                                    foreach ($commentsFile->authors->author as $author) {
884 3
                                        $authors[] = (string) $author;
885
                                    }
886
887
                                    // Loop through contents
888 3
                                    foreach ($commentsFile->commentList->comment as $comment) {
889 3
                                        if (!empty($comment['authorId'])) {
890
                                            $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]);
891
                                        }
892 3
                                        $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text));
893
                                    }
894
                                }
895
896
                                // later we will remove from it real vmlComments
897 48
                                $unparsedVmlDrawings = $vmlComments;
898
899
                                // Loop through VML comments
900 48
                                foreach ($vmlComments as $relName => $relPath) {
901
                                    // Load VML comments file
902 4
                                    $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
903
904
                                    try {
905 4
                                        $vmlCommentsFile = simplexml_load_string(
906 4
                                            $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
907 4
                                            'SimpleXMLElement',
908 4
                                            Settings::getLibXmlLoaderOptions()
909
                                        );
910 4
                                        $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
911
                                    } catch (\Throwable $ex) {
912
                                        //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
913
                                        continue;
914
                                    }
915
916 4
                                    $shapes = $vmlCommentsFile->xpath('//v:shape');
917 4
                                    foreach ($shapes as $shape) {
918 4
                                        $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
919
920 4
                                        if (isset($shape['style'])) {
921 4
                                            $style = (string) $shape['style'];
922 4
                                            $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
923 4
                                            $column = null;
924 4
                                            $row = null;
925
926 4
                                            $clientData = $shape->xpath('.//x:ClientData');
927 4
                                            if (is_array($clientData) && !empty($clientData)) {
928 4
                                                $clientData = $clientData[0];
929
930 4
                                                if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
931 3
                                                    $temp = $clientData->xpath('.//x:Row');
932 3
                                                    if (is_array($temp)) {
933 3
                                                        $row = $temp[0];
934
                                                    }
935
936 3
                                                    $temp = $clientData->xpath('.//x:Column');
937 3
                                                    if (is_array($temp)) {
938 3
                                                        $column = $temp[0];
939
                                                    }
940
                                                }
941
                                            }
942
943 4
                                            if (($column !== null) && ($row !== null)) {
944
                                                // Set comment properties
945 3
                                                $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1);
946 3
                                                $comment->getFillColor()->setRGB($fillColor);
947
948
                                                // Parse style
949 3
                                                $styleArray = explode(';', str_replace(' ', '', $style));
950 3
                                                foreach ($styleArray as $stylePair) {
951 3
                                                    $stylePair = explode(':', $stylePair);
952
953 3
                                                    if ($stylePair[0] == 'margin-left') {
954 3
                                                        $comment->setMarginLeft($stylePair[1]);
955
                                                    }
956 3
                                                    if ($stylePair[0] == 'margin-top') {
957 3
                                                        $comment->setMarginTop($stylePair[1]);
958
                                                    }
959 3
                                                    if ($stylePair[0] == 'width') {
960 3
                                                        $comment->setWidth($stylePair[1]);
961
                                                    }
962 3
                                                    if ($stylePair[0] == 'height') {
963 3
                                                        $comment->setHeight($stylePair[1]);
964
                                                    }
965 3
                                                    if ($stylePair[0] == 'visibility') {
966 3
                                                        $comment->setVisible($stylePair[1] == 'visible');
967
                                                    }
968
                                                }
969
970 3
                                                unset($unparsedVmlDrawings[$relName]);
971
                                            }
972
                                        }
973
                                    }
974
                                }
975
976
                                // unparsed vmlDrawing
977 48
                                if ($unparsedVmlDrawings) {
978 1
                                    foreach ($unparsedVmlDrawings as $rId => $relPath) {
979 1
                                        $rId = substr($rId, 3); // rIdXXX
980 1
                                        $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
981 1
                                        $unparsedVmlDrawing[$rId] = [];
982 1
                                        $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
983 1
                                        $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
984 1
                                        $unparsedVmlDrawing[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
985 1
                                        unset($unparsedVmlDrawing);
986
                                    }
987
                                }
988
989
                                // Header/footer images
990 48
                                if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) {
991
                                    if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
992
                                        //~ http://schemas.openxmlformats.org/package/2006/relationships"
993
                                        $relsWorksheet = simplexml_load_string(
994
                                            $this->securityScanner->scan(
995
                                                $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
996
                                            ),
997
                                            'SimpleXMLElement',
998
                                            Settings::getLibXmlLoaderOptions()
999
                                        );
1000
                                        $vmlRelationship = '';
1001
1002
                                        foreach ($relsWorksheet->Relationship as $ele) {
1003
                                            if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
1004
                                                $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
1005
                                            }
1006
                                        }
1007
1008
                                        if ($vmlRelationship != '') {
1009
                                            // Fetch linked images
1010
                                            //~ http://schemas.openxmlformats.org/package/2006/relationships"
1011
                                            $relsVML = simplexml_load_string(
1012
                                                $this->securityScanner->scan(
1013
                                                    $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels')
1014
                                                ),
1015
                                                'SimpleXMLElement',
1016
                                                Settings::getLibXmlLoaderOptions()
1017
                                            );
1018
                                            $drawings = [];
1019
                                            foreach ($relsVML->Relationship as $ele) {
1020
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1021
                                                    $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']);
1022
                                                }
1023
                                            }
1024
1025
                                            // Fetch VML document
1026
                                            $vmlDrawing = simplexml_load_string(
1027
                                                $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)),
1028
                                                'SimpleXMLElement',
1029
                                                Settings::getLibXmlLoaderOptions()
1030
                                            );
1031
                                            $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
1032
1033
                                            $hfImages = [];
1034
1035
                                            $shapes = $vmlDrawing->xpath('//v:shape');
1036
                                            foreach ($shapes as $idx => $shape) {
1037
                                                $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
1038
                                                $imageData = $shape->xpath('//v:imagedata');
1039
1040
                                                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...
1041
                                                    continue;
1042
                                                }
1043
1044
                                                $imageData = $imageData[$idx];
1045
1046
                                                $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office');
1047
                                                $style = self::toCSSArray((string) $shape['style']);
1048
1049
                                                $hfImages[(string) $shape['id']] = new HeaderFooterDrawing();
1050
                                                if (isset($imageData['title'])) {
1051
                                                    $hfImages[(string) $shape['id']]->setName((string) $imageData['title']);
1052
                                                }
1053
1054
                                                $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($pFilename) . '#' . $drawings[(string) $imageData['relid']], false);
1055
                                                $hfImages[(string) $shape['id']]->setResizeProportional(false);
1056
                                                $hfImages[(string) $shape['id']]->setWidth($style['width']);
1057
                                                $hfImages[(string) $shape['id']]->setHeight($style['height']);
1058
                                                if (isset($style['margin-left'])) {
1059
                                                    $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']);
1060
                                                }
1061
                                                $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']);
1062
                                                $hfImages[(string) $shape['id']]->setResizeProportional(true);
1063
                                            }
1064
1065
                                            $docSheet->getHeaderFooter()->setImages($hfImages);
1066
                                        }
1067
                                    }
1068
                                }
1069
                            }
1070
1071
                            // TODO: Autoshapes from twoCellAnchors!
1072 48
                            if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
1073
                                //~ http://schemas.openxmlformats.org/package/2006/relationships"
1074 43
                                $relsWorksheet = simplexml_load_string(
1075 43
                                    $this->securityScanner->scan(
1076 43
                                        $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
1077
                                    ),
1078 43
                                    'SimpleXMLElement',
1079 43
                                    Settings::getLibXmlLoaderOptions()
1080
                                );
1081 43
                                $drawings = [];
1082 43
                                foreach ($relsWorksheet->Relationship as $ele) {
1083 16
                                    if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
1084 8
                                        $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
1085
                                    }
1086
                                }
1087 43
                                if ($xmlSheet->drawing && !$this->readDataOnly) {
1088 8
                                    $unparsedDrawings = [];
1089 8
                                    foreach ($xmlSheet->drawing as $drawing) {
1090 8
                                        $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');
1091 8
                                        $fileDrawing = $drawings[$drawingRelId];
1092
                                        //~ http://schemas.openxmlformats.org/package/2006/relationships"
1093 8
                                        $relsDrawing = simplexml_load_string(
1094 8
                                            $this->securityScanner->scan(
1095 8
                                                $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels')
1096
                                            ),
1097 8
                                            'SimpleXMLElement',
1098 8
                                            Settings::getLibXmlLoaderOptions()
1099
                                        );
1100 8
                                        $images = [];
1101 8
                                        $hyperlinks = [];
1102 8
                                        if ($relsDrawing && $relsDrawing->Relationship) {
1103 6
                                            foreach ($relsDrawing->Relationship as $ele) {
1104 6
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
1105 2
                                                    $hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
1106
                                                }
1107 6
                                                if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
1108 6
                                                    $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
1109 4
                                                } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {
1110 2
                                                    if ($this->includeCharts) {
1111 2
                                                        $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [
1112 2
                                                            'id' => (string) $ele['Id'],
1113 2
                                                            'sheet' => $docSheet->getTitle(),
1114
                                                        ];
1115
                                                    }
1116
                                                }
1117
                                            }
1118
                                        }
1119 8
                                        $xmlDrawing = simplexml_load_string(
1120 8
                                            $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
1121 8
                                            'SimpleXMLElement',
1122 8
                                            Settings::getLibXmlLoaderOptions()
1123
                                        );
1124 8
                                        $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
1125
1126 8
                                        if ($xmlDrawingChildren->oneCellAnchor) {
1127 4
                                            foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
1128 4
                                                if ($oneCellAnchor->pic->blipFill) {
1129
                                                    /** @var SimpleXMLElement $blip */
1130 4
                                                    $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
1131
                                                    /** @var SimpleXMLElement $xfrm */
1132 4
                                                    $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
1133
                                                    /** @var SimpleXMLElement $outerShdw */
1134 4
                                                    $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1135
                                                    /** @var \SimpleXMLElement $hlinkClick */
1136 4
                                                    $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...
1137
1138 4
                                                    $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
1139 4
                                                    $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
1140 4
                                                    $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
1141 4
                                                    $objDrawing->setPath(
1142 4
                                                        'zip://' . File::realpath($pFilename) . '#' .
1143 4
                                                        $images[(string) self::getArrayItem(
1144 4
                                                            $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
1145 4
                                                            'embed'
1146
                                                        )],
1147 4
                                                        false
1148
                                                    );
1149 4
                                                    $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1));
1150 4
                                                    $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff));
1151 4
                                                    $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff));
1152 4
                                                    $objDrawing->setResizeProportional(false);
1153 4
                                                    $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')));
1154 4
                                                    $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')));
1155 4
                                                    if ($xfrm) {
1156 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

1156
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(/** @scrutinizer ignore-type */ self::getArrayItem($xfrm->attributes(), 'rot')));
Loading history...
1157
                                                    }
1158 4
                                                    if ($outerShdw) {
1159 2
                                                        $shadow = $objDrawing->getShadow();
1160 2
                                                        $shadow->setVisible(true);
1161 2
                                                        $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

1161
                                                        $shadow->setBlurRadius(Drawing::EMUToPixels(/** @scrutinizer ignore-type */ self::getArrayItem($outerShdw->attributes(), 'blurRad')));
Loading history...
1162 2
                                                        $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
1163 2
                                                        $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
1164 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

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

1519
                            $objChart = Chart::readChart(/** @scrutinizer ignore-type */ $chartElements, basename($chartEntryRef, '.xml'));
Loading history...
1520
1521 2
                            if (isset($charts[$chartEntryRef])) {
1522 2
                                $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
1523 2
                                if (isset($chartDetails[$chartPositionRef])) {
1524 2
                                    $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);
1525 2
                                    $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
1526 2
                                    $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
1527 2
                                    $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
1528
                                }
1529
                            }
1530
                        }
1531
1532 2
                        break;
1533
1534
                    // unparsed
1535 48
                    case 'application/vnd.ms-excel.controlproperties+xml':
1536 1
                        $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
1537
1538 1
                        break;
1539
                }
1540
            }
1541
        }
1542
1543 48
        $excel->setUnparsedLoadedData($unparsedLoadedData);
1544
1545 48
        $zip->close();
1546
1547 48
        return $excel;
1548
    }
1549
1550 48
    private static function readColor($color, $background = false)
1551
    {
1552 48
        if (isset($color['rgb'])) {
1553 41
            return (string) $color['rgb'];
1554 14
        } elseif (isset($color['indexed'])) {
1555 9
            return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
1556 11
        } elseif (isset($color['theme'])) {
1557 10
            if (self::$theme !== null) {
1558 10
                $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
1559 10
                if (isset($color['tint'])) {
1560 1
                    $tintAdjust = (float) $color['tint'];
1561 1
                    $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
1562
                }
1563
1564 10
                return 'FF' . $returnColour;
1565
            }
1566
        }
1567
1568 2
        if ($background) {
1569
            return 'FFFFFFFF';
1570
        }
1571
1572 2
        return 'FF000000';
1573
    }
1574
1575
    /**
1576
     * @param Style $docStyle
1577
     * @param SimpleXMLElement|\stdClass $style
1578
     */
1579 48
    private static function readStyle(Style $docStyle, $style)
1580
    {
1581 48
        $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
1582
1583
        // font
1584 48
        if (isset($style->font)) {
1585 48
            $docStyle->getFont()->setName((string) $style->font->name['val']);
1586 48
            $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

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

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