Completed
Push — master ( a6c56d...e2f87e )
by Adrien
23:29 queued 16:09
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 76
    public function __construct()
61
    {
62 76
        parent::__construct();
63 76
        $this->referenceHelper = ReferenceHelper::getInstance();
64 76
        $this->securityScanner = XmlScanner::getInstance($this);
65 76
    }
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 45
    private static function castToString($c)
261
    {
262 45
        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 72
    private function getFromZipArchive(ZipArchive $archive, $fileName = '')
297
    {
298
        // Root-relative paths
299 72
        if (strpos($fileName, '//') !== false) {
300
            $fileName = substr($fileName, strpos($fileName, '//') + 1);
301
        }
302 72
        $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 72
        $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
309 72
        if ($contents === false) {
310 2
            $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
311
        }
312
313 72
        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 69
    public function load($pFilename)
326
    {
327 69
        File::assertFile($pFilename);
328
329
        // Initialisations
330 69
        $excel = new Spreadsheet();
331 69
        $excel->removeSheetByIndex(0);
332 69
        if (!$this->readDataOnly) {
333 69
            $excel->removeCellStyleXfByIndex(0); // remove the default style
334 69
            $excel->removeCellXfByIndex(0); // remove the default style
335
        }
336 69
        $unparsedLoadedData = [];
337
338 69
        $zip = new ZipArchive();
339 69
        $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 69
        $workbookBasename = $this->getWorkbookBaseName($zip);
344 69
        $wbRels = simplexml_load_string(
345 69
            $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")),
346 69
            'SimpleXMLElement',
347 69
            Settings::getLibXmlLoaderOptions()
348
        );
349 69
        foreach ($wbRels->Relationship as $rel) {
350 69
            switch ($rel['Type']) {
351 69
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
352 69
                    $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
353 69
                    $themeOrderAdditional = count($themeOrderArray);
354
355 69
                    $xmlTheme = simplexml_load_string(
356 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")),
357 69
                        'SimpleXMLElement',
358 69
                        Settings::getLibXmlLoaderOptions()
359
                    );
360 69
                    if (is_object($xmlTheme)) {
361 69
                        $xmlThemeName = $xmlTheme->attributes();
362 69
                        $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
363 69
                        $themeName = (string) $xmlThemeName['name'];
364
365 69
                        $colourScheme = $xmlTheme->themeElements->clrScheme->attributes();
366 69
                        $colourSchemeName = (string) $colourScheme['name'];
367 69
                        $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
368
369 69
                        $themeColours = [];
370 69
                        foreach ($colourScheme as $k => $xmlColour) {
371 69
                            $themePos = array_search($k, $themeOrderArray);
372 69
                            if ($themePos === false) {
373 69
                                $themePos = $themeOrderAdditional++;
374
                            }
375 69
                            if (isset($xmlColour->sysClr)) {
376 69
                                $xmlColourData = $xmlColour->sysClr->attributes();
377 69
                                $themeColours[$themePos] = $xmlColourData['lastClr'];
378 69
                            } elseif (isset($xmlColour->srgbClr)) {
379 69
                                $xmlColourData = $xmlColour->srgbClr->attributes();
380 69
                                $themeColours[$themePos] = $xmlColourData['val'];
381
                            }
382
                        }
383 69
                        self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours);
384
                    }
385
386 69
                    break;
387
            }
388
        }
389
390
        //~ http://schemas.openxmlformats.org/package/2006/relationships"
391 69
        $rels = simplexml_load_string(
392 69
            $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
393 69
            'SimpleXMLElement',
394 69
            Settings::getLibXmlLoaderOptions()
395
        );
396
397 69
        $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties());
398 69
        foreach ($rels->Relationship as $rel) {
399 69
            switch ($rel['Type']) {
400 69
                case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties':
401 68
                    $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
402
403 68
                    break;
404 69
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties':
405 68
                    $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
406
407 68
                    break;
408 69
                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 69
                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 69
                case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
421 69
                    $dir = dirname($rel['Target']);
422
                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
423 69
                    $relsWorkbook = simplexml_load_string(
424 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')),
425 69
                        'SimpleXMLElement',
426 69
                        Settings::getLibXmlLoaderOptions()
427
                    );
428 69
                    $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
429
430 69
                    $sharedStrings = [];
431 69
                    $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));
432 69
                    if ($xpath) {
433
                        //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
434 66
                        $xmlStrings = simplexml_load_string(
435 66
                            $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
436 66
                            'SimpleXMLElement',
437 66
                            Settings::getLibXmlLoaderOptions()
438
                        );
439 66
                        if (isset($xmlStrings, $xmlStrings->si)) {
440 32
                            foreach ($xmlStrings->si as $val) {
441 32
                                if (isset($val->t)) {
442 32
                                    $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
443 4
                                } elseif (isset($val->r)) {
444 4
                                    $sharedStrings[] = $this->parseRichText($val);
445
                                }
446
                            }
447
                        }
448
                    }
449
450 69
                    $worksheets = [];
451 69
                    $macros = $customUI = null;
452 69
                    foreach ($relsWorkbook->Relationship as $ele) {
453 69
                        switch ($ele['Type']) {
454 69
                            case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':
455 69
                                $worksheets[(string) $ele['Id']] = $ele['Target'];
456
457 69
                                break;
458
                            // a vbaProject ? (: some macros)
459 69
                            case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject':
460 1
                                $macros = $ele['Target'];
461
462 1
                                break;
463
                        }
464
                    }
465
466 69
                    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 69
                    $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 69
                    $xmlStyles = simplexml_load_string(
482 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
483 69
                        'SimpleXMLElement',
484 69
                        Settings::getLibXmlLoaderOptions()
485
                    );
486
487 69
                    $styles = [];
488 69
                    $cellStyles = [];
489 69
                    $numFmts = null;
490 69
                    if ($xmlStyles && $xmlStyles->numFmts[0]) {
491 55
                        $numFmts = $xmlStyles->numFmts[0];
492
                    }
493 69
                    if (isset($numFmts) && ($numFmts !== null)) {
494 55
                        $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
495
                    }
496 69
                    if (!$this->readDataOnly && $xmlStyles) {
497 69
                        foreach ($xmlStyles->cellXfs->xf as $xf) {
498 69
                            $numFmt = NumberFormat::FORMAT_GENERAL;
499
500 69
                            if ($xf['numFmtId']) {
501 69
                                if (isset($numFmts)) {
502 55
                                    $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
503
504 55
                                    if (isset($tmpNumFmt['formatCode'])) {
505 5
                                        $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 69
                                if ((int) $xf['numFmtId'] < 164 &&
513 69
                                    NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '') {
514 69
                                    $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
515
                                }
516
                            }
517 69
                            $quotePrefix = false;
518 69
                            if (isset($xf['quotePrefix'])) {
519
                                $quotePrefix = (bool) $xf['quotePrefix'];
520
                            }
521
522
                            $style = (object) [
523 69
                                'numFmt' => $numFmt,
524 69
                                'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
525 69
                                'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
526 69
                                'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
527 69
                                'alignment' => $xf->alignment,
528 69
                                'protection' => $xf->protection,
529 69
                                'quotePrefix' => $quotePrefix,
530
                            ];
531 69
                            $styles[] = $style;
532
533
                            // add style to cellXf collection
534 69
                            $objStyle = new Style();
535 69
                            self::readStyle($objStyle, $style);
536 69
                            $excel->addCellXf($objStyle);
537
                        }
538
539 69
                        foreach (isset($xmlStyles->cellStyleXfs->xf) ? $xmlStyles->cellStyleXfs->xf : [] as $xf) {
540 69
                            $numFmt = NumberFormat::FORMAT_GENERAL;
541 69
                            if ($numFmts && $xf['numFmtId']) {
542 55
                                $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
543 55
                                if (isset($tmpNumFmt['formatCode'])) {
544 1
                                    $numFmt = (string) $tmpNumFmt['formatCode'];
545 55
                                } elseif ((int) $xf['numFmtId'] < 165) {
546 55
                                    $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
547
                                }
548
                            }
549
550
                            $cellStyle = (object) [
551 69
                                'numFmt' => $numFmt,
552 69
                                'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
553 69
                                'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
554 69
                                'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
555 69
                                'alignment' => $xf->alignment,
556 69
                                'protection' => $xf->protection,
557 69
                                '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 69
                            $cellStyles[] = $cellStyle;
560
561
                            // add style to cellStyleXf collection
562 69
                            $objStyle = new Style();
563 69
                            self::readStyle($objStyle, $cellStyle);
564 69
                            $excel->addCellStyleXf($objStyle);
565
                        }
566
                    }
567
568 69
                    $styleReader = new Styles($xmlStyles);
1 ignored issue
show
Bug introduced by
It seems like $xmlStyles can also be of type false; however, parameter $styleXml of PhpOffice\PhpSpreadsheet...x\Styles::__construct() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

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

568
                    $styleReader = new Styles(/** @scrutinizer ignore-type */ $xmlStyles);
Loading history...
569 69
                    $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
570 69
                    $dxfs = $styleReader->dxfs($this->readDataOnly);
571 69
                    $styles = $styleReader->styles();
572
573
                    //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
574 69
                    $xmlWorkbook = simplexml_load_string(
575 69
                        $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
576 69
                        'SimpleXMLElement',
577 69
                        Settings::getLibXmlLoaderOptions()
578
                    );
579
580
                    // Set base date
581 69
                    if ($xmlWorkbook->workbookPr) {
582 68
                        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
583 68
                        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 69
                    $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 69
                    $sheetId = 0; // keep track of new sheet id in final workbook
594 69
                    $oldSheetId = -1; // keep track of old sheet id in final workbook
595 69
                    $countSkippedSheets = 0; // keep track of number of skipped sheets
596 69
                    $mapSheetId = []; // mapping of sheet ids from old to new
597
598 69
                    $charts = $chartDetails = [];
599
600 69
                    if ($xmlWorkbook->sheets) {
601
                        /** @var SimpleXMLElement $eleSheet */
602 69
                        foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
603 69
                            ++$oldSheetId;
604
605
                            // Check if sheet should be skipped
606 69
                            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 69
                            $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
616
617
                            // Load sheet
618 69
                            $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 69
                            $docSheet->setTitle((string) $eleSheet['name'], false, false);
624 69
                            $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
625
                            //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
626 69
                            $xmlSheet = simplexml_load_string(
627 69
                                $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")),
628 69
                                'SimpleXMLElement',
629 69
                                Settings::getLibXmlLoaderOptions()
630
                            );
631
632 69
                            $sharedFormulas = [];
633
634 69
                            if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') {
635 2
                                $docSheet->setSheetState((string) $eleSheet['state']);
636
                            }
637
638 69
                            if ($xmlSheet) {
639 69
                                if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) {
640 69
                                    $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);
641 69
                                    $sheetViews->load();
642
                                }
643
644 69
                                $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);
1 ignored issue
show
Bug introduced by
It seems like $xmlSheet can also be of type false; however, parameter $worksheetXml of PhpOffice\PhpSpreadsheet...wOptions::__construct() does only seem to accept SimpleXMLElement|null, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

647
                                (new ColumnAndRowAttributes($docSheet, /** @scrutinizer ignore-type */ $xmlSheet))
Loading history...
648 69
                                    ->load($this->getReadFilter(), $this->getReadDataOnly());
649
                            }
650
651 69
                            if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
652 62
                                $cIndex = 1; // Cell Start from 1
653 62
                                foreach ($xmlSheet->sheetData->row as $row) {
654 62
                                    $rowIndex = 1;
655 62
                                    foreach ($row->c as $c) {
656 62
                                        $r = (string) $c['r'];
657 62
                                        if ($r == '') {
658 1
                                            $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
659
                                        }
660 62
                                        $cellDataType = (string) $c['t'];
661 62
                                        $value = null;
662 62
                                        $calculatedValue = null;
663
664
                                        // Read cell?
665 62
                                        if ($this->getReadFilter() !== null) {
666 62
                                            $coordinates = Coordinate::coordinateFromString($r);
667
668 62
                                            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
                                        switch ($cellDataType) {
677 62
                                            case 's':
678 32
                                                if ((string) $c->v != '') {
679 32
                                                    $value = $sharedStrings[(int) ($c->v)];
680
681 32
                                                    if ($value instanceof RichText) {
682 32
                                                        $value = clone $value;
683
                                                    }
684
                                                } else {
685
                                                    $value = '';
686
                                                }
687
688 32
                                                break;
689 49
                                            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 45
                                            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 45
                                            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 45
                                                if (!isset($c->f)) {
721 43
                                                    $value = self::castToString($c);
722
                                                } else {
723
                                                    // Formula
724 7
                                                    $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
725
                                                }
726
727 45
                                                break;
728
                                        }
729
730
                                        // read empty cells or the cells are not empty
731 62
                                        if ($this->readEmptyCells || ($value !== null && $value !== '')) {
732
                                            // Rich text?
733 62
                                            if ($value instanceof RichText && $this->readDataOnly) {
734
                                                $value = $value->getPlainText();
735
                                            }
736
737 62
                                            $cell = $docSheet->getCell($r);
738
                                            // Assign value
739 62
                                            if ($cellDataType != '') {
740 36
                                                $cell->setValueExplicit($value, $cellDataType);
741
                                            } else {
742 43
                                                $cell->setValue($value);
743
                                            }
744 62
                                            if ($calculatedValue !== null) {
745 8
                                                $cell->setCalculatedValue($calculatedValue);
746
                                            }
747
748
                                            // Style information?
749 62
                                            if ($c['s'] && !$this->readDataOnly) {
750
                                                // no style index means 0, it seems
751 14
                                                $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ?
752 14
                                                    (int) ($c['s']) : 0);
753
                                            }
754
                                        }
755 62
                                        $rowIndex += 1;
756
                                    }
757 62
                                    $cIndex += 1;
758
                                }
759
                            }
760
761 69
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) {
762 6
                                (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
763
                            }
764
765 69
                            $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells'];
766 69
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
767 58
                                foreach ($aKeys as $key) {
768 58
                                    $method = 'set' . ucfirst($key);
769 58
                                    $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key]));
770
                                }
771
                            }
772
773 69
                            if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
774 58
                                $docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true);
775 58
                                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 69
                            if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
783 1
                                (new AutoFilter($docSheet, $xmlSheet))->load();
784
                            }
785
786 69
                            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 69
                            if ($xmlSheet && !$this->readDataOnly) {
796 69
                                $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
797
                            }
798
799 69
                            if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
800 1
                                (new DataValidations($docSheet, $xmlSheet))->load();
801
                            }
802
803
                            // unparsed sheet AlternateContent
804 69
                            if ($xmlSheet && !$this->readDataOnly) {
805 69
                                $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
806 69
                                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 69
                            if (!$this->readDataOnly) {
815 69
                                $hyperlinkReader = new Hyperlinks($docSheet);
816
                                // Locate hyperlink relations
817 69
                                $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
818 69
                                if ($zip->locateName($relationsFileName)) {
819
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
820 63
                                    $relsWorksheet = simplexml_load_string(
821 63
                                        $this->securityScanner->scan(
822 63
                                            $this->getFromZipArchive($zip, $relationsFileName)
823
                                        ),
824 63
                                        'SimpleXMLElement',
825 63
                                        Settings::getLibXmlLoaderOptions()
826
                                    );
827 63
                                    $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 69
                                if ($xmlSheet && $xmlSheet->hyperlinks) {
832 2
                                    $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);
833
                                }
834
                            }
835
836
                            // Add comments
837 69
                            $comments = [];
838 69
                            $vmlComments = [];
839 69
                            if (!$this->readDataOnly) {
840
                                // Locate comment relations
841 69
                                if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
842
                                    //~ http://schemas.openxmlformats.org/package/2006/relationships"
843 63
                                    $relsWorksheet = simplexml_load_string(
844 63
                                        $this->securityScanner->scan(
845 63
                                            $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
846
                                        ),
847 63
                                        'SimpleXMLElement',
848 63
                                        Settings::getLibXmlLoaderOptions()
849
                                    );
850 63
                                    foreach ($relsWorksheet->Relationship as $ele) {
851 17
                                        if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') {
852 3
                                            $comments[(string) $ele['Id']] = (string) $ele['Target'];
853
                                        }
854 17
                                        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 69
                                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 69
                                $unparsedVmlDrawings = $vmlComments;
889
890
                                // Loop through VML comments
891 69
                                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 69
                                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 69
                                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 69
                            if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
1064
                                //~ http://schemas.openxmlformats.org/package/2006/relationships"
1065 63
                                $relsWorksheet = simplexml_load_string(
1066 63
                                    $this->securityScanner->scan(
1067 63
                                        $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
1068
                                    ),
1069 63
                                    'SimpleXMLElement',
1070 63
                                    Settings::getLibXmlLoaderOptions()
1071
                                );
1072 63
                                $drawings = [];
1073 63
                                foreach ($relsWorksheet->Relationship as $ele) {
1074 17
                                    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 63
                                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 69
                            $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
1275 69
                            $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
1276
1277
                            // Loop through definedNames
1278 69
                            if ($xmlWorkbook->definedNames) {
1279 53
                                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 69
                            ++$sheetId;
1350
                        }
1351
1352
                        // Loop through definedNames
1353 69
                        if ($xmlWorkbook->definedNames) {
1354 53
                            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 69
                    if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) {
1418 69
                        $workbookView = $xmlWorkbook->bookViews->workbookView;
1419
1420
                        // active sheet index
1421 69
                        $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 69
                        if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
1425 69
                            $excel->setActiveSheetIndex($mapSheetId[$activeTab]);
1426
                        } else {
1427
                            if ($excel->getSheetCount() == 0) {
1428
                                $excel->createSheet();
1429
                            }
1430
                            $excel->setActiveSheetIndex(0);
1431
                        }
1432
1433 69
                        if (isset($workbookView['showHorizontalScroll'])) {
1434 52
                            $showHorizontalScroll = (string) $workbookView['showHorizontalScroll'];
1435 52
                            $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
1436
                        }
1437
1438 69
                        if (isset($workbookView['showVerticalScroll'])) {
1439 52
                            $showVerticalScroll = (string) $workbookView['showVerticalScroll'];
1440 52
                            $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
1441
                        }
1442
1443 69
                        if (isset($workbookView['showSheetTabs'])) {
1444 52
                            $showSheetTabs = (string) $workbookView['showSheetTabs'];
1445 52
                            $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
1446
                        }
1447
1448 69
                        if (isset($workbookView['minimized'])) {
1449 52
                            $minimized = (string) $workbookView['minimized'];
1450 52
                            $excel->setMinimized($this->castXsdBooleanToBool($minimized));
1451
                        }
1452
1453 69
                        if (isset($workbookView['autoFilterDateGrouping'])) {
1454 52
                            $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping'];
1455 52
                            $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
1456
                        }
1457
1458 69
                        if (isset($workbookView['firstSheet'])) {
1459 52
                            $firstSheet = (string) $workbookView['firstSheet'];
1460 52
                            $excel->setFirstSheetIndex((int) $firstSheet);
1461
                        }
1462
1463 69
                        if (isset($workbookView['visibility'])) {
1464 52
                            $visibility = (string) $workbookView['visibility'];
1465 52
                            $excel->setVisibility($visibility);
1466
                        }
1467
1468 69
                        if (isset($workbookView['tabRatio'])) {
1469 52
                            $tabRatio = (string) $workbookView['tabRatio'];
1470 52
                            $excel->setTabRatio((int) $tabRatio);
1471
                        }
1472
                    }
1473
1474 69
                    break;
1475
            }
1476
        }
1477
1478 69
        if (!$this->readDataOnly) {
1479 69
            $contentTypes = simplexml_load_string(
1480 69
                $this->securityScanner->scan(
1481 69
                    $this->getFromZipArchive($zip, '[Content_Types].xml')
1482
                ),
1483 69
                'SimpleXMLElement',
1484 69
                Settings::getLibXmlLoaderOptions()
1485
            );
1486
1487
            // Default content types
1488 69
            foreach ($contentTypes->Default as $contentType) {
1489 69
                switch ($contentType['ContentType']) {
1490 69
                    case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
1491 12
                        $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
1492
1493 12
                        break;
1494
                }
1495
            }
1496
1497
            // Override content types
1498 69
            foreach ($contentTypes->Override as $contentType) {
1499 69
                switch ($contentType['ContentType']) {
1500 69
                    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 69
                    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 69
        $excel->setUnparsedLoadedData($unparsedLoadedData);
1535
1536 69
        $zip->close();
1537
1538 69
        return $excel;
1539
    }
1540
1541 69
    private static function readColor($color, $background = false)
1542
    {
1543 69
        if (isset($color['rgb'])) {
1544 60
            return (string) $color['rgb'];
1545 17
        } elseif (isset($color['indexed'])) {
1546 9
            return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
1547 14
        } elseif (isset($color['theme'])) {
1548 13
            if (self::$theme !== null) {
1549 13
                $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
1550 13
                if (isset($color['tint'])) {
1551 1
                    $tintAdjust = (float) $color['tint'];
1552 1
                    $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
1553
                }
1554
1555 13
                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 69
    private static function readStyle(Style $docStyle, $style)
1571
    {
1572 69
        $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
1573
1574
        // font
1575 69
        if (isset($style->font)) {
1576 69
            $docStyle->getFont()->setName((string) $style->font->name['val']);
1577 69
            $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 69
            if (isset($style->font->b)) {
1579 60
                $docStyle->getFont()->setBold(!isset($style->font->b['val']) || self::boolean((string) $style->font->b['val']));
1580
            }
1581 69
            if (isset($style->font->i)) {
1582 54
                $docStyle->getFont()->setItalic(!isset($style->font->i['val']) || self::boolean((string) $style->font->i['val']));
1583
            }
1584 69
            if (isset($style->font->strike)) {
1585 53
                $docStyle->getFont()->setStrikethrough(!isset($style->font->strike['val']) || self::boolean((string) $style->font->strike['val']));
1586
            }
1587 69
            $docStyle->getFont()->getColor()->setARGB(self::readColor($style->font->color));
1588
1589 69
            if (isset($style->font->u) && !isset($style->font->u['val'])) {
1590 1
                $docStyle->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
1591 69
            } elseif (isset($style->font->u, $style->font->u['val'])) {
1592 52
                $docStyle->getFont()->setUnderline((string) $style->font->u['val']);
1593
            }
1594
1595 69
            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 69
        if (isset($style->fill)) {
1608 69
            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 69
            } elseif ($style->fill->patternFill) {
1619 69
                $patternType = (string) $style->fill->patternFill['patternType'] != '' ? (string) $style->fill->patternFill['patternType'] : 'solid';
1620 69
                $docStyle->getFill()->setFillType($patternType);
1621 69
                if ($style->fill->patternFill->fgColor) {
1622 6
                    $docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true));
1623
                }
1624 69
                if ($style->fill->patternFill->bgColor) {
1625 6
                    $docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true));
1626
                }
1627
            }
1628
        }
1629
1630
        // border
1631 69
        if (isset($style->border)) {
1632 69
            $diagonalUp = self::boolean((string) $style->border['diagonalUp']);
1633 69
            $diagonalDown = self::boolean((string) $style->border['diagonalDown']);
1634 69
            if (!$diagonalUp && !$diagonalDown) {
1635 69
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
1636 1
            } elseif ($diagonalUp && !$diagonalDown) {
1637 1
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
1638 1
            } elseif (!$diagonalUp && $diagonalDown) {
1639 1
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
1640
            } else {
1641
                $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
1642
            }
1643 69
            self::readBorder($docStyle->getBorders()->getLeft(), $style->border->left);
1644 69
            self::readBorder($docStyle->getBorders()->getRight(), $style->border->right);
1645 69
            self::readBorder($docStyle->getBorders()->getTop(), $style->border->top);
1646 69
            self::readBorder($docStyle->getBorders()->getBottom(), $style->border->bottom);
1647 69
            self::readBorder($docStyle->getBorders()->getDiagonal(), $style->border->diagonal);
1648
        }
1649
1650
        // alignment
1651 69
        if (isset($style->alignment)) {
1652 69
            $docStyle->getAlignment()->setHorizontal((string) $style->alignment['horizontal']);
1653 69
            $docStyle->getAlignment()->setVertical((string) $style->alignment['vertical']);
1654
1655 69
            $textRotation = 0;
1656 69
            if ((int) $style->alignment['textRotation'] <= 90) {
1657 69
                $textRotation = (int) $style->alignment['textRotation'];
1658
            } elseif ((int) $style->alignment['textRotation'] > 90) {
1659
                $textRotation = 90 - (int) $style->alignment['textRotation'];
1660
            }
1661
1662 69
            $docStyle->getAlignment()->setTextRotation((int) $textRotation);
1663 69
            $docStyle->getAlignment()->setWrapText(self::boolean((string) $style->alignment['wrapText']));
1664 69
            $docStyle->getAlignment()->setShrinkToFit(self::boolean((string) $style->alignment['shrinkToFit']));
1665 69
            $docStyle->getAlignment()->setIndent((int) ((string) $style->alignment['indent']) > 0 ? (int) ((string) $style->alignment['indent']) : 0);
1666 69
            $docStyle->getAlignment()->setReadOrder((int) ((string) $style->alignment['readingOrder']) > 0 ? (int) ((string) $style->alignment['readingOrder']) : 0);
1667
        }
1668
1669
        // protection
1670 69
        if (isset($style->protection)) {
1671 69
            if (isset($style->protection['locked'])) {
1672 2
                if (self::boolean((string) $style->protection['locked'])) {
1673
                    $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
1674
                } else {
1675 2
                    $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
1676
                }
1677
            }
1678
1679 69
            if (isset($style->protection['hidden'])) {
1680
                if (self::boolean((string) $style->protection['hidden'])) {
1681
                    $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
1682
                } else {
1683
                    $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
1684
                }
1685
            }
1686
        }
1687
1688
        // top-level style settings
1689 69
        if (isset($style->quotePrefix)) {
1690 69
            $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

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