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

582
                    $styleReader = new Styles(/** @scrutinizer ignore-type */ $xmlStyles);
Loading history...
583 47
                    $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
584 47
                    $dxfs = $styleReader->dxfs($this->readDataOnly);
585 47
                    $styles = $styleReader->styles();
586
587
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
588 47
                    $xmlWorkbook = simplexml_load_string(
589 47
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
590 47
                        'SimpleXMLElement',
591 47
                        Settings::getLibXmlLoaderOptions()
592
                    );
593
594
                    // Set base date
595 47
                    if ($xmlWorkbook->workbookPr) {
596 46
                        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
597 46
                        if (isset($xmlWorkbook->workbookPr['date1904'])) {
598
                            if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) {
599
                                Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
600
                            }
601
                        }
602
                    }
603
604
                    // Set protection
605 47
                    $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

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

658
                                $sheetViewOptions = new SheetViewOptions($docSheet, /** @scrutinizer ignore-type */ $xmlSheet);
Loading history...
659 47
                                $sheetViewOptions->load($this->getReadDataOnly());
660
661 47
                                (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

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

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

1170
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(/** @scrutinizer ignore-type */ self::getArrayItem($xfrm->attributes(), 'rot')));
Loading history...
1171
                                                    }
1172 4
                                                    if ($outerShdw) {
1173 2
                                                        $shadow = $objDrawing->getShadow();
1174 2
                                                        $shadow->setVisible(true);
1175 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

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

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

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

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

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