Passed
Push — master ( 955217...cf30c2 )
by Adrien
27:35 queued 19:31
created

Xlsx::parseRichText()   D

Complexity

Conditions 26
Paths 3

Size

Total Lines 55
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 34.0756

Importance

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

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

1147
                                                        $objDrawing->setRotation(Drawing::angleToDegrees(/** @scrutinizer ignore-type */ self::getArrayItem($xfrm->attributes(), 'rot')));
Loading history...
1148
                                                    }
1149 4
                                                    if ($outerShdw) {
1150 2
                                                        $shadow = $objDrawing->getShadow();
1151 2
                                                        $shadow->setVisible(true);
1152 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

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

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

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

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

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