Completed
Push — develop ( c1f5bd...b96478 )
by Adrien
23:38 queued 12:24
created

Xls::getInt4d()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 15
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
/**
6
 * Copyright (c) 2006 - 2016 PhpSpreadsheet
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 *
22
 * @category   PhpSpreadsheet
23
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
24
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
25
 */
26
27
// Original file header of ParseXL (used as the base for this class):
28
// --------------------------------------------------------------------------------
29
// Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
30
// trex005, and mmp11 (SourceForge.net)
31
// http://sourceforge.net/projects/phpexcelreader/
32
// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
33
//     Modelled moreso after Perl Excel Parse/Write modules
34
//     Added Parse_Excel_Spreadsheet object
35
//         Reads a whole worksheet or tab as row,column array or as
36
//         associated hash of indexed rows and named column fields
37
//     Added variables for worksheet (tab) indexes and names
38
//     Added an object call for loading individual woorksheets
39
//     Changed default indexing defaults to 0 based arrays
40
//     Fixed date/time and percent formats
41
//     Includes patches found at SourceForge...
42
//         unicode patch by nobody
43
//         unpack("d") machine depedency patch by matchy
44
//         boundsheet utf16 patch by bjaenichen
45
//     Renamed functions for shorter names
46
//     General code cleanup and rigor, including <80 column width
47
//     Included a testcase Excel file and PHP example calls
48
//     Code works for PHP 5.x
49
50
// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
51
// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
52
//     Decoding of formula conditions, results, and tokens.
53
//     Support for user-defined named cells added as an array "namedcells"
54
//         Patch code for user-defined named cells supports single cells only.
55
//         NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
56
//         external sheet reference structure
57
class Xls extends BaseReader implements IReader
58
{
59
    // ParseXL definitions
60
    const XLS_BIFF8 = 0x0600;
61
    const XLS_BIFF7 = 0x0500;
62
    const XLS_WORKBOOKGLOBALS = 0x0005;
63
    const XLS_WORKSHEET = 0x0010;
64
65
    // record identifiers
66
    const XLS_TYPE_FORMULA = 0x0006;
67
    const XLS_TYPE_EOF = 0x000a;
68
    const XLS_TYPE_PROTECT = 0x0012;
69
    const XLS_TYPE_OBJECTPROTECT = 0x0063;
70
    const XLS_TYPE_SCENPROTECT = 0x00dd;
71
    const XLS_TYPE_PASSWORD = 0x0013;
72
    const XLS_TYPE_HEADER = 0x0014;
73
    const XLS_TYPE_FOOTER = 0x0015;
74
    const XLS_TYPE_EXTERNSHEET = 0x0017;
75
    const XLS_TYPE_DEFINEDNAME = 0x0018;
76
    const XLS_TYPE_VERTICALPAGEBREAKS = 0x001a;
77
    const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
78
    const XLS_TYPE_NOTE = 0x001c;
79
    const XLS_TYPE_SELECTION = 0x001d;
80
    const XLS_TYPE_DATEMODE = 0x0022;
81
    const XLS_TYPE_EXTERNNAME = 0x0023;
82
    const XLS_TYPE_LEFTMARGIN = 0x0026;
83
    const XLS_TYPE_RIGHTMARGIN = 0x0027;
84
    const XLS_TYPE_TOPMARGIN = 0x0028;
85
    const XLS_TYPE_BOTTOMMARGIN = 0x0029;
86
    const XLS_TYPE_PRINTGRIDLINES = 0x002b;
87
    const XLS_TYPE_FILEPASS = 0x002f;
88
    const XLS_TYPE_FONT = 0x0031;
89
    const XLS_TYPE_CONTINUE = 0x003c;
90
    const XLS_TYPE_PANE = 0x0041;
91
    const XLS_TYPE_CODEPAGE = 0x0042;
92
    const XLS_TYPE_DEFCOLWIDTH = 0x0055;
93
    const XLS_TYPE_OBJ = 0x005d;
94
    const XLS_TYPE_COLINFO = 0x007d;
95
    const XLS_TYPE_IMDATA = 0x007f;
96
    const XLS_TYPE_SHEETPR = 0x0081;
97
    const XLS_TYPE_HCENTER = 0x0083;
98
    const XLS_TYPE_VCENTER = 0x0084;
99
    const XLS_TYPE_SHEET = 0x0085;
100
    const XLS_TYPE_PALETTE = 0x0092;
101
    const XLS_TYPE_SCL = 0x00a0;
102
    const XLS_TYPE_PAGESETUP = 0x00a1;
103
    const XLS_TYPE_MULRK = 0x00bd;
104
    const XLS_TYPE_MULBLANK = 0x00be;
105
    const XLS_TYPE_DBCELL = 0x00d7;
106
    const XLS_TYPE_XF = 0x00e0;
107
    const XLS_TYPE_MERGEDCELLS = 0x00e5;
108
    const XLS_TYPE_MSODRAWINGGROUP = 0x00eb;
109
    const XLS_TYPE_MSODRAWING = 0x00ec;
110
    const XLS_TYPE_SST = 0x00fc;
111
    const XLS_TYPE_LABELSST = 0x00fd;
112
    const XLS_TYPE_EXTSST = 0x00ff;
113
    const XLS_TYPE_EXTERNALBOOK = 0x01ae;
114
    const XLS_TYPE_DATAVALIDATIONS = 0x01b2;
115
    const XLS_TYPE_TXO = 0x01b6;
116
    const XLS_TYPE_HYPERLINK = 0x01b8;
117
    const XLS_TYPE_DATAVALIDATION = 0x01be;
118
    const XLS_TYPE_DIMENSION = 0x0200;
119
    const XLS_TYPE_BLANK = 0x0201;
120
    const XLS_TYPE_NUMBER = 0x0203;
121
    const XLS_TYPE_LABEL = 0x0204;
122
    const XLS_TYPE_BOOLERR = 0x0205;
123
    const XLS_TYPE_STRING = 0x0207;
124
    const XLS_TYPE_ROW = 0x0208;
125
    const XLS_TYPE_INDEX = 0x020b;
126
    const XLS_TYPE_ARRAY = 0x0221;
127
    const XLS_TYPE_DEFAULTROWHEIGHT = 0x0225;
128
    const XLS_TYPE_WINDOW2 = 0x023e;
129
    const XLS_TYPE_RK = 0x027e;
130
    const XLS_TYPE_STYLE = 0x0293;
131
    const XLS_TYPE_FORMAT = 0x041e;
132
    const XLS_TYPE_SHAREDFMLA = 0x04bc;
133
    const XLS_TYPE_BOF = 0x0809;
134
    const XLS_TYPE_SHEETPROTECTION = 0x0867;
135
    const XLS_TYPE_RANGEPROTECTION = 0x0868;
136
    const XLS_TYPE_SHEETLAYOUT = 0x0862;
137
    const XLS_TYPE_XFEXT = 0x087d;
138
    const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
139
    const XLS_TYPE_UNKNOWN = 0xffff;
140
141
    // Encryption type
142
    const MS_BIFF_CRYPTO_NONE = 0;
143
    const MS_BIFF_CRYPTO_XOR = 1;
144
    const MS_BIFF_CRYPTO_RC4 = 2;
145
146
    // Size of stream blocks when using RC4 encryption
147
    const REKEY_BLOCK = 0x400;
148
149
    /**
150
     * Summary Information stream data.
151
     *
152
     * @var string
153
     */
154
    private $summaryInformation;
155
156
    /**
157
     * Extended Summary Information stream data.
158
     *
159
     * @var string
160
     */
161
    private $documentSummaryInformation;
162
163
    /**
164
     * User-Defined Properties stream data.
165
     *
166
     * @var string
167
     */
168
    private $userDefinedProperties;
0 ignored issues
show
Unused Code introduced by
The property $userDefinedProperties is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
169
170
    /**
171
     * Workbook stream data. (Includes workbook globals substream as well as sheet substreams)
172
     *
173
     * @var string
174
     */
175
    private $data;
176
177
    /**
178
     * Size in bytes of $this->data
179
     *
180
     * @var int
181
     */
182
    private $dataSize;
183
184
    /**
185
     * Current position in stream
186
     *
187
     * @var int
188
     */
189
    private $pos;
190
191
    /**
192
     * Workbook to be returned by the reader.
193
     *
194
     * @var \PhpOffice\PhpSpreadsheet\Spreadsheet
195
     */
196
    private $spreadsheet;
197
198
    /**
199
     * Worksheet that is currently being built by the reader.
200
     *
201
     * @var Worksheet
202
     */
203
    private $phpSheet;
204
205
    /**
206
     * BIFF version
207
     *
208
     * @var int
209
     */
210
    private $version;
211
212
    /**
213
     * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
214
     * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'
215
     *
216
     * @var string
217
     */
218
    private $codepage;
219
220
    /**
221
     * Shared formats
222
     *
223
     * @var array
224
     */
225
    private $formats;
226
227
    /**
228
     * Shared fonts
229
     *
230
     * @var array
231
     */
232
    private $objFonts;
233
234
    /**
235
     * Color palette
236
     *
237
     * @var array
238
     */
239
    private $palette;
240
241
    /**
242
     * Worksheets
243
     *
244
     * @var array
245
     */
246
    private $sheets;
247
248
    /**
249
     * External books
250
     *
251
     * @var array
252
     */
253
    private $externalBooks;
254
255
    /**
256
     * REF structures. Only applies to BIFF8.
257
     *
258
     * @var array
259
     */
260
    private $ref;
261
262
    /**
263
     * External names
264
     *
265
     * @var array
266
     */
267
    private $externalNames;
268
269
    /**
270
     * Defined names
271
     *
272
     * @var array
273
     */
274
    private $definedname;
275
276
    /**
277
     * Shared strings. Only applies to BIFF8.
278
     *
279
     * @var array
280
     */
281
    private $sst;
282
283
    /**
284
     * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
285
     *
286
     * @var bool
287
     */
288
    private $frozen;
289
290
    /**
291
     * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
292
     *
293
     * @var bool
294
     */
295
    private $isFitToPages;
296
297
    /**
298
     * Objects. One OBJ record contributes with one entry.
299
     *
300
     * @var array
301
     */
302
    private $objs;
303
304
    /**
305
     * Text Objects. One TXO record corresponds with one entry.
306
     *
307
     * @var array
308
     */
309
    private $textObjects;
310
311
    /**
312
     * Cell Annotations (BIFF8)
313
     *
314
     * @var array
315
     */
316
    private $cellNotes;
317
318
    /**
319
     * The combined MSODRAWINGGROUP data
320
     *
321
     * @var string
322
     */
323
    private $drawingGroupData;
324
325
    /**
326
     * The combined MSODRAWING data (per sheet)
327
     *
328
     * @var string
329
     */
330
    private $drawingData;
331
332
    /**
333
     * Keep track of XF index
334
     *
335
     * @var int
336
     */
337
    private $xfIndex;
338
339
    /**
340
     * Mapping of XF index (that is a cell XF) to final index in cellXf collection
341
     *
342
     * @var array
343
     */
344
    private $mapCellXfIndex;
345
346
    /**
347
     * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection
348
     *
349
     * @var array
350
     */
351
    private $mapCellStyleXfIndex;
352
353
    /**
354
     * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
355
     *
356
     * @var array
357
     */
358
    private $sharedFormulas;
359
360
    /**
361
     * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
362
     * refers to a shared formula.
363
     *
364
     * @var array
365
     */
366
    private $sharedFormulaParts;
367
368
    /**
369
     * The type of encryption in use
370
     *
371
     * @var int
372
     */
373
    private $encryption = 0;
374
375
    /**
376
     * The position in the stream after which contents are encrypted
377
     *
378
     * @var int
379
     */
380
    private $encryptionStartPos = false;
381
382
    /**
383
     * The current RC4 decryption object
384
     *
385
     * @var Xls\RC4
386
     */
387
    private $rc4Key = null;
388
389
    /**
390
     * The position in the stream that the RC4 decryption object was left at
391
     *
392
     * @var int
393
     */
394
    private $rc4Pos = 0;
395
396
    /**
397
     * The current MD5 context state
398
     *
399
     * @var string
400
     */
401
    private $md5Ctxt = null;
402
403
    /**
404
     * Create a new Xls Reader instance
405
     */
406 4
    public function __construct()
407
    {
408 4
        $this->readFilter = new DefaultReadFilter();
409 4
    }
410
411
    /**
412
     * Can the current IReader read the file?
413
     *
414
     * @param     string         $pFilename
415
     * @throws Exception
416
     * @return     bool
417
     */
418 2
    public function canRead($pFilename)
419
    {
420
        // Check if file exists
421 2
        if (!file_exists($pFilename)) {
422
            throw new Exception('Could not open ' . $pFilename . ' for reading! File does not exist.');
423
        }
424
425
        try {
426
            // Use ParseXL for the hard work.
427 2
            $ole = new \PhpOffice\PhpSpreadsheet\Shared\OLERead();
428
429
            // get excel data
430 2
            $ole->read($pFilename);
431
432 2
            return true;
433
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
434
            return false;
435
        }
436
    }
437
438
    /**
439
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object
440
     *
441
     * @param     string         $pFilename
442
     * @throws     Exception
443
     */
444
    public function listWorksheetNames($pFilename)
445
    {
446
        // Check if file exists
447
        if (!file_exists($pFilename)) {
448
            throw new Exception('Could not open ' . $pFilename . ' for reading! File does not exist.');
449
        }
450
451
        $worksheetNames = [];
452
453
        // Read the OLE file
454
        $this->loadOLE($pFilename);
455
456
        // total byte size of Excel data (workbook global substream + sheet substreams)
457
        $this->dataSize = strlen($this->data);
458
459
        $this->pos = 0;
460
        $this->sheets = [];
461
462
        // Parse Workbook Global Substream
463 View Code Duplication
        while ($this->pos < $this->dataSize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
464
            $code = self::getInt2d($this->data, $this->pos);
465
466
            switch ($code) {
467
                case self::XLS_TYPE_BOF:
468
                    $this->readBof();
469
                    break;
470
                case self::XLS_TYPE_SHEET:
471
                    $this->readSheet();
472
                    break;
473
                case self::XLS_TYPE_EOF:
474
                    $this->readDefault();
475
                    break 2;
476
                default:
477
                    $this->readDefault();
478
                    break;
479
            }
480
        }
481
482
        foreach ($this->sheets as $sheet) {
483
            if ($sheet['sheetType'] != 0x00) {
484
                // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
485
                continue;
486
            }
487
488
            $worksheetNames[] = $sheet['name'];
489
        }
490
491
        return $worksheetNames;
492
    }
493
494
    /**
495
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
496
     *
497
     * @param   string     $pFilename
498
     * @throws   Exception
499
     */
500
    public function listWorksheetInfo($pFilename)
501
    {
502
        // Check if file exists
503
        if (!file_exists($pFilename)) {
504
            throw new Exception('Could not open ' . $pFilename . ' for reading! File does not exist.');
505
        }
506
507
        $worksheetInfo = [];
508
509
        // Read the OLE file
510
        $this->loadOLE($pFilename);
511
512
        // total byte size of Excel data (workbook global substream + sheet substreams)
513
        $this->dataSize = strlen($this->data);
514
515
        // initialize
516
        $this->pos = 0;
517
        $this->sheets = [];
518
519
        // Parse Workbook Global Substream
520 View Code Duplication
        while ($this->pos < $this->dataSize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
521
            $code = self::getInt2d($this->data, $this->pos);
522
523
            switch ($code) {
524
                case self::XLS_TYPE_BOF:
525
                    $this->readBof();
526
                    break;
527
                case self::XLS_TYPE_SHEET:
528
                    $this->readSheet();
529
                    break;
530
                case self::XLS_TYPE_EOF:
531
                    $this->readDefault();
532
                    break 2;
533
                default:
534
                    $this->readDefault();
535
                    break;
536
            }
537
        }
538
539
        // Parse the individual sheets
540
        foreach ($this->sheets as $sheet) {
541
            if ($sheet['sheetType'] != 0x00) {
542
                // 0x00: Worksheet
543
                // 0x02: Chart
544
                // 0x06: Visual Basic module
545
                continue;
546
            }
547
548
            $tmpInfo = [];
549
            $tmpInfo['worksheetName'] = $sheet['name'];
550
            $tmpInfo['lastColumnLetter'] = 'A';
551
            $tmpInfo['lastColumnIndex'] = 0;
552
            $tmpInfo['totalRows'] = 0;
553
            $tmpInfo['totalColumns'] = 0;
554
555
            $this->pos = $sheet['offset'];
556
557
            while ($this->pos <= $this->dataSize - 4) {
558
                $code = self::getInt2d($this->data, $this->pos);
559
560
                switch ($code) {
561
                    case self::XLS_TYPE_RK:
562
                    case self::XLS_TYPE_LABELSST:
563
                    case self::XLS_TYPE_NUMBER:
564
                    case self::XLS_TYPE_FORMULA:
565
                    case self::XLS_TYPE_BOOLERR:
566
                    case self::XLS_TYPE_LABEL:
567
                        $length = self::getInt2d($this->data, $this->pos + 2);
568
                        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
569
570
                        // move stream pointer to next record
571
                        $this->pos += 4 + $length;
572
573
                        $rowIndex = self::getInt2d($recordData, 0) + 1;
574
                        $columnIndex = self::getInt2d($recordData, 2);
575
576
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
577
                        $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
578
                        break;
579
                    case self::XLS_TYPE_BOF:
580
                        $this->readBof();
581
                        break;
582
                    case self::XLS_TYPE_EOF:
583
                        $this->readDefault();
584
                        break 2;
585
                    default:
586
                        $this->readDefault();
587
                        break;
588
                }
589
            }
590
591
            $tmpInfo['lastColumnLetter'] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
592
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
593
594
            $worksheetInfo[] = $tmpInfo;
595
        }
596
597
        return $worksheetInfo;
598
    }
599
600
    /**
601
     * Loads PhpSpreadsheet from file
602
     *
603
     * @param     string         $pFilename
604
     * @throws    Exception
605
     * @return    \PhpOffice\PhpSpreadsheet\Spreadsheet
606
     */
607 4
    public function load($pFilename)
608
    {
609
        // Read the OLE file
610 4
        $this->loadOLE($pFilename);
611
612
        // Initialisations
613 4
        $this->spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
614 4
        $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
615 4
        if (!$this->readDataOnly) {
616 4
            $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
617 4
            $this->spreadsheet->removeCellXfByIndex(0); // remove the default style
618
        }
619
620
        // Read the summary information stream (containing meta data)
621 4
        $this->readSummaryInformation();
622
623
        // Read the Additional document summary information stream (containing application-specific meta data)
624 4
        $this->readDocumentSummaryInformation();
625
626
        // total byte size of Excel data (workbook global substream + sheet substreams)
627 4
        $this->dataSize = strlen($this->data);
628
629
        // initialize
630 4
        $this->pos = 0;
631 4
        $this->codepage = 'CP1252';
632 4
        $this->formats = [];
633 4
        $this->objFonts = [];
634 4
        $this->palette = [];
635 4
        $this->sheets = [];
636 4
        $this->externalBooks = [];
637 4
        $this->ref = [];
638 4
        $this->definedname = [];
639 4
        $this->sst = [];
640 4
        $this->drawingGroupData = '';
641 4
        $this->xfIndex = '';
0 ignored issues
show
Documentation Bug introduced by
The property $xfIndex was declared of type integer, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
642 4
        $this->mapCellXfIndex = [];
643 4
        $this->mapCellStyleXfIndex = [];
644
645
        // Parse Workbook Global Substream
646 4
        while ($this->pos < $this->dataSize) {
647 4
            $code = self::getInt2d($this->data, $this->pos);
648
649
            switch ($code) {
650 4
                case self::XLS_TYPE_BOF:
651 4
                    $this->readBof();
652 4
                    break;
653 4
                case self::XLS_TYPE_FILEPASS:
654
                    $this->readFilepass();
655
                    break;
656 4
                case self::XLS_TYPE_CODEPAGE:
657 4
                    $this->readCodepage();
658 4
                    break;
659 4
                case self::XLS_TYPE_DATEMODE:
660 4
                    $this->readDateMode();
661 4
                    break;
662 4
                case self::XLS_TYPE_FONT:
663 4
                    $this->readFont();
664 4
                    break;
665 4
                case self::XLS_TYPE_FORMAT:
666 4
                    $this->readFormat();
667 4
                    break;
668 4
                case self::XLS_TYPE_XF:
669 4
                    $this->readXf();
670 4
                    break;
671 4
                case self::XLS_TYPE_XFEXT:
672 3
                    $this->readXfExt();
673 3
                    break;
674 4
                case self::XLS_TYPE_STYLE:
675 4
                    $this->readStyle();
676 4
                    break;
677 4
                case self::XLS_TYPE_PALETTE:
678 2
                    $this->readPalette();
679 2
                    break;
680 4
                case self::XLS_TYPE_SHEET:
681 4
                    $this->readSheet();
682 4
                    break;
683 4
                case self::XLS_TYPE_EXTERNALBOOK:
684 3
                    $this->readExternalBook();
685 3
                    break;
686 4
                case self::XLS_TYPE_EXTERNNAME:
687
                    $this->readExternName();
688
                    break;
689 4
                case self::XLS_TYPE_EXTERNSHEET:
690 3
                    $this->readExternSheet();
691 3
                    break;
692 4
                case self::XLS_TYPE_DEFINEDNAME:
693 1
                    $this->readDefinedName();
694 1
                    break;
695 4
                case self::XLS_TYPE_MSODRAWINGGROUP:
696 3
                    $this->readMsoDrawingGroup();
697 3
                    break;
698 4
                case self::XLS_TYPE_SST:
699 4
                    $this->readSst();
700 4
                    break;
701 4
                case self::XLS_TYPE_EOF:
702 4
                    $this->readDefault();
703 4
                    break 2;
704
                default:
705 4
                    $this->readDefault();
706 4
                    break;
707
            }
708
        }
709
710
        // Resolve indexed colors for font, fill, and border colors
711
        // Cannot be resolved already in XF record, because PALETTE record comes afterwards
712 4
        if (!$this->readDataOnly) {
713 4
            foreach ($this->objFonts as $objFont) {
714 4 View Code Duplication
                if (isset($objFont->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
715 4
                    $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
716 4
                    $objFont->getColor()->setRGB($color['rgb']);
717
                }
718
            }
719
720 4
            foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
721
                // fill start and end color
722 4
                $fill = $objStyle->getFill();
723
724 4
                if (isset($fill->startcolorIndex)) {
725 4
                    $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version);
0 ignored issues
show
Bug introduced by
The property startcolorIndex does not seem to exist. Did you mean startColor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
726 4
                    $fill->getStartColor()->setRGB($startColor['rgb']);
727
                }
728 4
                if (isset($fill->endcolorIndex)) {
729 4
                    $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version);
0 ignored issues
show
Bug introduced by
The property endcolorIndex does not seem to exist. Did you mean endColor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
730 4
                    $fill->getEndColor()->setRGB($endColor['rgb']);
731
                }
732
733
                // border colors
734 4
                $top = $objStyle->getBorders()->getTop();
735 4
                $right = $objStyle->getBorders()->getRight();
736 4
                $bottom = $objStyle->getBorders()->getBottom();
737 4
                $left = $objStyle->getBorders()->getLeft();
738 4
                $diagonal = $objStyle->getBorders()->getDiagonal();
739
740 4 View Code Duplication
                if (isset($top->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
741 4
                    $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version);
0 ignored issues
show
Bug introduced by
The property colorIndex does not seem to exist in PhpOffice\PhpSpreadsheet\Style\Border.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
742 4
                    $top->getColor()->setRGB($borderTopColor['rgb']);
743
                }
744 4 View Code Duplication
                if (isset($right->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
745 4
                    $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
746 4
                    $right->getColor()->setRGB($borderRightColor['rgb']);
747
                }
748 4 View Code Duplication
                if (isset($bottom->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
749 4
                    $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
750 4
                    $bottom->getColor()->setRGB($borderBottomColor['rgb']);
751
                }
752 4 View Code Duplication
                if (isset($left->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
753 4
                    $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
754 4
                    $left->getColor()->setRGB($borderLeftColor['rgb']);
755
                }
756 4 View Code Duplication
                if (isset($diagonal->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
757 4
                    $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
758 4
                    $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
759
                }
760
            }
761
        }
762
763
        // treat MSODRAWINGGROUP records, workbook-level Escher
764 4
        if (!$this->readDataOnly && $this->drawingGroupData) {
765 2
            $escherWorkbook = new \PhpOffice\PhpSpreadsheet\Shared\Escher();
766 2
            $reader = new Xls\Escher($escherWorkbook);
767 2
            $escherWorkbook = $reader->load($this->drawingGroupData);
768
        }
769
770
        // Parse the individual sheets
771 4
        foreach ($this->sheets as $sheet) {
772 4
            if ($sheet['sheetType'] != 0x00) {
773
                // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
774
                continue;
775
            }
776
777
            // check if sheet should be skipped
778 4
            if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
779
                continue;
780
            }
781
782
            // add sheet to PhpSpreadsheet object
783 4
            $this->phpSheet = $this->spreadsheet->createSheet();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->spreadsheet->createSheet() of type object<PhpOffice\PhpSpreadsheet\Worksheet> is incompatible with the declared type object<PhpOffice\PhpSpreadsheet\Reader\Worksheet> of property $phpSheet.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
784
            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
785
            //        cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
786
            //        name in line with the formula, not the reverse
787 4
            $this->phpSheet->setTitle($sheet['name'], false);
788 4
            $this->phpSheet->setSheetState($sheet['sheetState']);
789
790 4
            $this->pos = $sheet['offset'];
791
792
            // Initialize isFitToPages. May change after reading SHEETPR record.
793 4
            $this->isFitToPages = false;
794
795
            // Initialize drawingData
796 4
            $this->drawingData = '';
797
798
            // Initialize objs
799 4
            $this->objs = [];
800
801
            // Initialize shared formula parts
802 4
            $this->sharedFormulaParts = [];
803
804
            // Initialize shared formulas
805 4
            $this->sharedFormulas = [];
806
807
            // Initialize text objs
808 4
            $this->textObjects = [];
809
810
            // Initialize cell annotations
811 4
            $this->cellNotes = [];
812 4
            $this->textObjRef = -1;
0 ignored issues
show
Bug introduced by
The property textObjRef does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
813
814 4
            while ($this->pos <= $this->dataSize - 4) {
815 4
                $code = self::getInt2d($this->data, $this->pos);
816
817
                switch ($code) {
818 4
                    case self::XLS_TYPE_BOF:
819 4
                        $this->readBof();
820 4
                        break;
821 4
                    case self::XLS_TYPE_PRINTGRIDLINES:
822 4
                        $this->readPrintGridlines();
823 4
                        break;
824 4
                    case self::XLS_TYPE_DEFAULTROWHEIGHT:
825 3
                        $this->readDefaultRowHeight();
826 3
                        break;
827 4
                    case self::XLS_TYPE_SHEETPR:
828 4
                        $this->readSheetPr();
829 4
                        break;
830 4
                    case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
831
                        $this->readHorizontalPageBreaks();
832
                        break;
833 4
                    case self::XLS_TYPE_VERTICALPAGEBREAKS:
834
                        $this->readVerticalPageBreaks();
835
                        break;
836 4
                    case self::XLS_TYPE_HEADER:
837 4
                        $this->readHeader();
838 4
                        break;
839 4
                    case self::XLS_TYPE_FOOTER:
840 4
                        $this->readFooter();
841 4
                        break;
842 4
                    case self::XLS_TYPE_HCENTER:
843 4
                        $this->readHcenter();
844 4
                        break;
845 4
                    case self::XLS_TYPE_VCENTER:
846 4
                        $this->readVcenter();
847 4
                        break;
848 4
                    case self::XLS_TYPE_LEFTMARGIN:
849 4
                        $this->readLeftMargin();
850 4
                        break;
851 4
                    case self::XLS_TYPE_RIGHTMARGIN:
852 4
                        $this->readRightMargin();
853 4
                        break;
854 4
                    case self::XLS_TYPE_TOPMARGIN:
855 4
                        $this->readTopMargin();
856 4
                        break;
857 4
                    case self::XLS_TYPE_BOTTOMMARGIN:
858 4
                        $this->readBottomMargin();
859 4
                        break;
860 4
                    case self::XLS_TYPE_PAGESETUP:
861 4
                        $this->readPageSetup();
862 4
                        break;
863 4
                    case self::XLS_TYPE_PROTECT:
864 1
                        $this->readProtect();
865 1
                        break;
866 4
                    case self::XLS_TYPE_SCENPROTECT:
867
                        $this->readScenProtect();
868
                        break;
869 4
                    case self::XLS_TYPE_OBJECTPROTECT:
870
                        $this->readObjectProtect();
871
                        break;
872 4
                    case self::XLS_TYPE_PASSWORD:
873
                        $this->readPassword();
874
                        break;
875 4
                    case self::XLS_TYPE_DEFCOLWIDTH:
876 4
                        $this->readDefColWidth();
877 4
                        break;
878 4
                    case self::XLS_TYPE_COLINFO:
879 3
                        $this->readColInfo();
880 3
                        break;
881 4
                    case self::XLS_TYPE_DIMENSION:
882 4
                        $this->readDefault();
883 4
                        break;
884 4
                    case self::XLS_TYPE_ROW:
885 3
                        $this->readRow();
886 3
                        break;
887 4
                    case self::XLS_TYPE_DBCELL:
888 3
                        $this->readDefault();
889 3
                        break;
890 4
                    case self::XLS_TYPE_RK:
891 1
                        $this->readRk();
892 1
                        break;
893 4
                    case self::XLS_TYPE_LABELSST:
894 4
                        $this->readLabelSst();
895 4
                        break;
896 4
                    case self::XLS_TYPE_MULRK:
897
                        $this->readMulRk();
898
                        break;
899 4
                    case self::XLS_TYPE_NUMBER:
900 1
                        $this->readNumber();
901 1
                        break;
902 4
                    case self::XLS_TYPE_FORMULA:
903 2
                        $this->readFormula();
904 2
                        break;
905 4
                    case self::XLS_TYPE_SHAREDFMLA:
906
                        $this->readSharedFmla();
907
                        break;
908 4
                    case self::XLS_TYPE_BOOLERR:
909
                        $this->readBoolErr();
910
                        break;
911 4
                    case self::XLS_TYPE_MULBLANK:
912 1
                        $this->readMulBlank();
913 1
                        break;
914 4
                    case self::XLS_TYPE_LABEL:
915
                        $this->readLabel();
916
                        break;
917 4
                    case self::XLS_TYPE_BLANK:
918 2
                        $this->readBlank();
919 2
                        break;
920 4
                    case self::XLS_TYPE_MSODRAWING:
921 2
                        $this->readMsoDrawing();
922 2
                        break;
923 4
                    case self::XLS_TYPE_OBJ:
924 2
                        $this->readObj();
925 2
                        break;
926 4
                    case self::XLS_TYPE_WINDOW2:
927 4
                        $this->readWindow2();
928 4
                        break;
929 4
                    case self::XLS_TYPE_PAGELAYOUTVIEW:
930 4
                        $this->readPageLayoutView();
931 4
                        break;
932 4
                    case self::XLS_TYPE_SCL:
933
                        $this->readScl();
934
                        break;
935 4
                    case self::XLS_TYPE_PANE:
936
                        $this->readPane();
937
                        break;
938 4
                    case self::XLS_TYPE_SELECTION:
939 4
                        $this->readSelection();
940 4
                        break;
941 4
                    case self::XLS_TYPE_MERGEDCELLS:
942 2
                        $this->readMergedCells();
943 2
                        break;
944 4
                    case self::XLS_TYPE_HYPERLINK:
945 2
                        $this->readHyperLink();
946 2
                        break;
947 4
                    case self::XLS_TYPE_DATAVALIDATIONS:
948
                        $this->readDataValidations();
949
                        break;
950 4
                    case self::XLS_TYPE_DATAVALIDATION:
951
                        $this->readDataValidation();
952
                        break;
953 4
                    case self::XLS_TYPE_SHEETLAYOUT:
954 2
                        $this->readSheetLayout();
955 2
                        break;
956 4
                    case self::XLS_TYPE_SHEETPROTECTION:
957 4
                        $this->readSheetProtection();
958 4
                        break;
959 4
                    case self::XLS_TYPE_RANGEPROTECTION:
960 1
                        $this->readRangeProtection();
961 1
                        break;
962 4
                    case self::XLS_TYPE_NOTE:
963 1
                        $this->readNote();
964 1
                        break;
965
                    //case self::XLS_TYPE_IMDATA:                $this->readImData();                    break;
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
966 4
                    case self::XLS_TYPE_TXO:
967 1
                        $this->readTextObject();
968 1
                        break;
969 4
                    case self::XLS_TYPE_CONTINUE:
970
                        $this->readContinue();
971
                        break;
972 4
                    case self::XLS_TYPE_EOF:
973 4
                        $this->readDefault();
974 4
                        break 2;
975
                    default:
976 4
                        $this->readDefault();
977 4
                        break;
978
                }
979
            }
980
981
            // treat MSODRAWING records, sheet-level Escher
982 4
            if (!$this->readDataOnly && $this->drawingData) {
983 2
                $escherWorksheet = new \PhpOffice\PhpSpreadsheet\Shared\Escher();
984 2
                $reader = new Xls\Escher($escherWorksheet);
985 2
                $escherWorksheet = $reader->load($this->drawingData);
986
987
                // get all spContainers in one long array, so they can be mapped to OBJ records
988 2
                $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
989
            }
990
991
            // treat OBJ records
992 4
            foreach ($this->objs as $n => $obj) {
993
                // the first shape container never has a corresponding OBJ record, hence $n + 1
994 2
                if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
995 2
                    $spContainer = $allSpContainers[$n + 1];
996
997
                    // we skip all spContainers that are a part of a group shape since we cannot yet handle those
998 2
                    if ($spContainer->getNestingLevel() > 1) {
999
                        continue;
1000
                    }
1001
1002
                    // calculate the width and height of the shape
1003 2
                    list($startColumn, $startRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($spContainer->getStartCoordinates());
1004 2
                    list($endColumn, $endRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($spContainer->getEndCoordinates());
1005
1006 2
                    $startOffsetX = $spContainer->getStartOffsetX();
1007 2
                    $startOffsetY = $spContainer->getStartOffsetY();
1008 2
                    $endOffsetX = $spContainer->getEndOffsetX();
1009 2
                    $endOffsetY = $spContainer->getEndOffsetY();
1010
1011 2
                    $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
1012 2
                    $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
1013
1014
                    // calculate offsetX and offsetY of the shape
1015 2
                    $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024;
1016 2
                    $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256;
1017
1018 2
                    switch ($obj['otObjType']) {
1019 2
                        case 0x19:
1020
                            // Note
1021 1
                            if (isset($this->cellNotes[$obj['idObjID']])) {
1022 1
                                $cellNote = $this->cellNotes[$obj['idObjID']];
0 ignored issues
show
Unused Code introduced by
$cellNote is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1023
1024 1 View Code Duplication
                                if (isset($this->textObjects[$obj['idObjID']])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1025 1
                                    $textObject = $this->textObjects[$obj['idObjID']];
1026 1
                                    $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
1027
                                }
1028
                            }
1029 1
                            break;
1030 2
                        case 0x08:
1031
                            // picture
1032
                            // get index to BSE entry (1-based)
1033 2
                            $BSEindex = $spContainer->getOPT(0x0104);
1034
1035
                            // If there is no BSE Index, we will fail here and other fields are not read.
1036
                            // Fix by checking here.
1037
                            // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field?
1038
                            // More likely : a uncompatible picture
1039 2
                            if (!$BSEindex) {
1040
                                continue;
1041
                            }
1042
1043 2
                            $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
0 ignored issues
show
Bug introduced by
The variable $escherWorkbook does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1044 2
                            $BSE = $BSECollection[$BSEindex - 1];
1045 2
                            $blipType = $BSE->getBlipType();
1046
1047
                            // need check because some blip types are not supported by Escher reader such as EMF
1048 2
                            if ($blip = $BSE->getBlip()) {
1049 2
                                $ih = imagecreatefromstring($blip->getData());
1050 2
                                $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing();
1051 2
                                $drawing->setImageResource($ih);
1052
1053
                                // width, height, offsetX, offsetY
1054 2
                                $drawing->setResizeProportional(false);
1055 2
                                $drawing->setWidth($width);
1056 2
                                $drawing->setHeight($height);
1057 2
                                $drawing->setOffsetX($offsetX);
1058 2
                                $drawing->setOffsetY($offsetY);
1059
1060
                                switch ($blipType) {
1061 2
                                    case \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE::BLIPTYPE_JPEG:
1062 2
                                        $drawing->setRenderingFunction(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_JPEG);
1063 2
                                        $drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_JPEG);
1064 2
                                        break;
1065 2
                                    case \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE::BLIPTYPE_PNG:
1066 2
                                        $drawing->setRenderingFunction(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_PNG);
1067 2
                                        $drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_PNG);
1068 2
                                        break;
1069
                                }
1070
1071 2
                                $drawing->setWorksheet($this->phpSheet);
1072 2
                                $drawing->setCoordinates($spContainer->getStartCoordinates());
1073
                            }
1074 2
                            break;
1075
                        default:
1076
                            // other object type
1077 2
                            break;
1078
                    }
1079
                }
1080
            }
1081
1082
            // treat SHAREDFMLA records
1083 4
            if ($this->version == self::XLS_BIFF8) {
1084 4
                foreach ($this->sharedFormulaParts as $cell => $baseCell) {
1085
                    list($column, $row) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($cell);
1086
                    if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
1087
                        $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
1088
                        $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA);
1089
                    }
1090
                }
1091
            }
1092
1093 4
            if (!empty($this->cellNotes)) {
1094 1
                foreach ($this->cellNotes as $note => $noteDetails) {
1095 1
                    if (!isset($noteDetails['objTextData'])) {
1096 View Code Duplication
                        if (isset($this->textObjects[$note])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1097
                            $textObject = $this->textObjects[$note];
1098
                            $noteDetails['objTextData'] = $textObject;
1099
                        } else {
1100
                            $noteDetails['objTextData']['text'] = '';
1101
                        }
1102
                    }
1103 1
                    $cellAddress = str_replace('$', '', $noteDetails['cellRef']);
1104 4
                    $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
1105
                }
1106
            }
1107
        }
1108
1109
        // add the named ranges (defined names)
1110 4
        foreach ($this->definedname as $definedName) {
1111 1
            if ($definedName['isBuiltInName']) {
1112
                switch ($definedName['name']) {
1113
                    case pack('C', 0x06):
1114
                        // print area
1115
                        //    in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1116
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1117
1118
                        $extractedRanges = [];
1119
                        foreach ($ranges as $range) {
1120
                            // $range should look like one of these
1121
                            //        Foo!$C$7:$J$66
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1122
                            //        Bar!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1123
                            $explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
1124
                            $sheetName = trim($explodes[0], "'");
1125
                            if (count($explodes) == 2) {
1126
                                if (strpos($explodes[1], ':') === false) {
1127
                                    $explodes[1] = $explodes[1] . ':' . $explodes[1];
1128
                                }
1129
                                $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
1130
                            }
1131
                        }
1132
                        if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) {
0 ignored issues
show
Bug introduced by
The variable $sheetName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1133
                            $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
1134
                        }
1135
                        break;
1136
                    case pack('C', 0x07):
1137
                        // print titles (repeating rows)
1138
                        // Assuming BIFF8, there are 3 cases
1139
                        // 1. repeating rows
1140
                        //        formula looks like this: Sheet!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1141
                        //        rows 1-2 repeat
1142
                        // 2. repeating columns
1143
                        //        formula looks like this: Sheet!$A$1:$B$65536
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1144
                        //        columns A-B repeat
1145
                        // 3. both repeating rows and repeating columns
1146
                        //        formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1147
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1148
                        foreach ($ranges as $range) {
1149
                            // $range should look like this one of these
1150
                            //        Sheet!$A$1:$B$65536
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1151
                            //        Sheet!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1152
                            $explodes = explode('!', $range);
1153
                            if (count($explodes) == 2) {
1154
                                if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
1155
                                    $extractedRange = $explodes[1];
1156
                                    $extractedRange = str_replace('$', '', $extractedRange);
1157
1158
                                    $coordinateStrings = explode(':', $extractedRange);
1159
                                    if (count($coordinateStrings) == 2) {
1160
                                        list($firstColumn, $firstRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($coordinateStrings[0]);
1161
                                        list($lastColumn, $lastRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($coordinateStrings[1]);
1162
1163
                                        if ($firstColumn == 'A' and $lastColumn == 'IV') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1164
                                            // then we have repeating rows
1165
                                            $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
1166
                                        } elseif ($firstRow == 1 and $lastRow == 65536) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1167
                                            // then we have repeating columns
1168
                                            $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]);
1169
                                        }
1170
                                    }
1171
                                }
1172
                            }
1173
                        }
1174
                        break;
1175
                }
1176
            } else {
1177
                // Extract range
1178 1
                $explodes = explode('!', $definedName['formula']);
1179
1180 1
                if (count($explodes) == 2) {
1181 1
                    if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
1182 1
                        ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $docSheet is correct as $this->spreadsheet->getS...im($explodes[0], '\'')) (which targets PhpOffice\PhpSpreadsheet...sheet::getSheetByName()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1183 1
                        $extractedRange = $explodes[1];
1184 1
                        $extractedRange = str_replace('$', '', $extractedRange);
1185
1186 1
                        $localOnly = ($definedName['scope'] == 0) ? false : true;
1187
1188 1
                        $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
1189
1190 1
                        $this->spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope));
0 ignored issues
show
Bug introduced by
It seems like $docSheet can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1191
                    }
1192 1
                } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
1193
                    //    Named Value
1194
                    //    TODO Provide support for named values
1195
                }
1196
            }
1197
        }
1198 4
        $this->data = null;
1199
1200 4
        return $this->spreadsheet;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->spreadsheet; (PhpOffice\PhpSpreadsheet\Spreadsheet) is incompatible with the return type declared by the interface PhpOffice\PhpSpreadsheet\Reader\IReader::load of type PhpOffice\PhpSpreadsheet\Reader\PhpSpreadsheet.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1201
    }
1202
1203
    /**
1204
     * Read record data from stream, decrypting as required
1205
     *
1206
     * @param string $data   Data stream to read from
1207
     * @param int    $pos    Position to start reading from
1208
     * @param int    $len    Record data length
1209
     *
1210
     * @return string Record data
1211
     */
1212 4
    private function readRecordData($data, $pos, $len)
1213
    {
1214 4
        $data = substr($data, $pos, $len);
1215
1216
        // File not encrypted, or record before encryption start point
1217 4
        if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
1218 4
            return $data;
1219
        }
1220
1221
        $recordData = '';
1222
        if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
1223
            $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
1224
            $block = floor($pos / self::REKEY_BLOCK);
1225
            $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
1226
1227
            // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
1228
            // at a point earlier in the current block, re-use it as we can save some time.
1229
            if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
1230
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1231
                $step = $pos % self::REKEY_BLOCK;
1232
            } else {
1233
                $step = $pos - $this->rc4Pos;
1234
            }
1235
            $this->rc4Key->RC4(str_repeat("\0", $step));
1236
1237
            // Decrypt record data (re-keying at the end of every block)
1238
            while ($block != $endBlock) {
1239
                $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
1240
                $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
1241
                $data = substr($data, $step);
1242
                $pos += $step;
1243
                $len -= $step;
1244
                ++$block;
1245
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1246
            }
1247
            $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
1248
1249
            // Keep track of the position of this decryptor.
1250
            // We'll try and re-use it later if we can to speed things up
1251
            $this->rc4Pos = $pos + $len;
1252
        } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
1253
            throw new Exception('XOr encryption not supported');
1254
        }
1255
1256
        return $recordData;
1257
    }
1258
1259
    /**
1260
     * Use OLE reader to extract the relevant data streams from the OLE file
1261
     *
1262
     * @param string $pFilename
1263
     */
1264 4
    private function loadOLE($pFilename)
1265
    {
1266
        // OLE reader
1267 4
        $ole = new \PhpOffice\PhpSpreadsheet\Shared\OLERead();
1268
        // get excel data,
1269 4
        $ole->read($pFilename);
1270
        // Get workbook data: workbook stream + sheet streams
1271 4
        $this->data = $ole->getStream($ole->wrkbook);
1272
        // Get summary information data
1273 4
        $this->summaryInformation = $ole->getStream($ole->summaryInformation);
1274
        // Get additional document summary information data
1275 4
        $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1276 4
    }
1277
1278
    /**
1279
     * Read summary information
1280
     */
1281 4
    private function readSummaryInformation()
1282
    {
1283 4
        if (!isset($this->summaryInformation)) {
1284
            return;
1285
        }
1286
1287
        // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1288
        // offset: 2; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1289
        // offset: 4; size: 2; OS version
1290
        // offset: 6; size: 2; OS indicator
1291
        // offset: 8; size: 16
1292
        // offset: 24; size: 4; section count
1293 4
        $secCount = self::getInt4d($this->summaryInformation, 24);
0 ignored issues
show
Unused Code introduced by
$secCount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1294
1295
        // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
1296
        // offset: 44; size: 4
1297 4
        $secOffset = self::getInt4d($this->summaryInformation, 44);
1298
1299
        // section header
1300
        // offset: $secOffset; size: 4; section length
1301 4
        $secLength = self::getInt4d($this->summaryInformation, $secOffset);
0 ignored issues
show
Unused Code introduced by
$secLength is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1302
1303
        // offset: $secOffset+4; size: 4; property count
1304 4
        $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
1305
1306
        // initialize code page (used to resolve string values)
1307 4
        $codePage = 'CP1252';
1308
1309
        // offset: ($secOffset+8); size: var
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1310
        // loop through property decarations and properties
1311 4
        for ($i = 0; $i < $countProperties; ++$i) {
1312
            // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1313 4
            $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i));
1314
1315
            // Use value of property id as appropriate
1316
            // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1317 4
            $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
1318
1319 4
            $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
1320
1321
            // initialize property value
1322 4
            $value = null;
1323
1324
            // extract property value based on property type
1325
            switch ($type) {
1326 4
                case 0x02: // 2 byte signed integer
1327 4
                    $value = self::getInt2d($this->summaryInformation, $secOffset + 4 + $offset);
1328 4
                    break;
1329 4
                case 0x03: // 4 byte signed integer
1330 4
                    $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1331 4
                    break;
1332 4
                case 0x13: // 4 byte unsigned integer
1333
                    // not needed yet, fix later if necessary
1334
                    break;
1335 4 View Code Duplication
                case 0x1E: // null-terminated string prepended by dword string length
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1336 4
                    $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1337 4
                    $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
1338 4
                    $value = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1339 4
                    $value = rtrim($value);
1340 4
                    break;
1341 4 View Code Duplication
                case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1342
                    // PHP-time
1343 4
                    $value = \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
1344 4
                    break;
1345
                case 0x47: // Clipboard format
1346
                    // not needed yet, fix later if necessary
1347
                    break;
1348
            }
1349
1350
            switch ($id) {
1351 4
                case 0x01:    //    Code Page
1352 4
                    $codePage = \PhpOffice\PhpSpreadsheet\Shared\CodePage::numberToName($value);
1353 4
                    break;
1354 4
                case 0x02:    //    Title
1355 3
                    $this->spreadsheet->getProperties()->setTitle($value);
1356 3
                    break;
1357 4
                case 0x03:    //    Subject
1358 3
                    $this->spreadsheet->getProperties()->setSubject($value);
1359 3
                    break;
1360 4
                case 0x04:    //    Author (Creator)
1361 4
                    $this->spreadsheet->getProperties()->setCreator($value);
1362 4
                    break;
1363 4
                case 0x05:    //    Keywords
1364 3
                    $this->spreadsheet->getProperties()->setKeywords($value);
1365 3
                    break;
1366 4
                case 0x06:    //    Comments (Description)
1367 3
                    $this->spreadsheet->getProperties()->setDescription($value);
1368 3
                    break;
1369 4
                case 0x07:    //    Template
1370
                    //    Not supported by PhpSpreadsheet
1371
                    break;
1372 4
                case 0x08:    //    Last Saved By (LastModifiedBy)
1373 4
                    $this->spreadsheet->getProperties()->setLastModifiedBy($value);
1374 4
                    break;
1375 4
                case 0x09:    //    Revision
1376
                    //    Not supported by PhpSpreadsheet
1377
                    break;
1378 4
                case 0x0A:    //    Total Editing Time
1379
                    //    Not supported by PhpSpreadsheet
1380
                    break;
1381 4
                case 0x0B:    //    Last Printed
1382
                    //    Not supported by PhpSpreadsheet
1383
                    break;
1384 4
                case 0x0C:    //    Created Date/Time
1385 4
                    $this->spreadsheet->getProperties()->setCreated($value);
1386 4
                    break;
1387 4
                case 0x0D:    //    Modified Date/Time
1388 4
                    $this->spreadsheet->getProperties()->setModified($value);
1389 4
                    break;
1390 4
                case 0x0E:    //    Number of Pages
1391
                    //    Not supported by PhpSpreadsheet
1392
                    break;
1393 4
                case 0x0F:    //    Number of Words
1394
                    //    Not supported by PhpSpreadsheet
1395
                    break;
1396 4
                case 0x10:    //    Number of Characters
1397
                    //    Not supported by PhpSpreadsheet
1398
                    break;
1399 4
                case 0x11:    //    Thumbnail
1400
                    //    Not supported by PhpSpreadsheet
1401
                    break;
1402 4
                case 0x12:    //    Name of creating application
1403
                    //    Not supported by PhpSpreadsheet
1404 2
                    break;
1405 4
                case 0x13:    //    Security
1406
                    //    Not supported by PhpSpreadsheet
1407 4
                    break;
1408
            }
1409
        }
1410 4
    }
1411
1412
    /**
1413
     * Read additional document summary information
1414
     */
1415 4
    private function readDocumentSummaryInformation()
1416
    {
1417 4
        if (!isset($this->documentSummaryInformation)) {
1418
            return;
1419
        }
1420
1421
        //    offset: 0;    size: 2;    must be 0xFE 0xFF (UTF-16 LE byte order mark)
1422
        //    offset: 2;    size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1423
        //    offset: 4;    size: 2;    OS version
1424
        //    offset: 6;    size: 2;    OS indicator
1425
        //    offset: 8;    size: 16
1426
        //    offset: 24;    size: 4;    section count
1427 4
        $secCount = self::getInt4d($this->documentSummaryInformation, 24);
0 ignored issues
show
Unused Code introduced by
$secCount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1428
1429
        // offset: 28;    size: 16;    first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
1430
        // offset: 44;    size: 4;    first section offset
1431 4
        $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
1432
1433
        //    section header
1434
        //    offset: $secOffset;    size: 4;    section length
1435 4
        $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
0 ignored issues
show
Unused Code introduced by
$secLength is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1436
1437
        //    offset: $secOffset+4;    size: 4;    property count
1438 4
        $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
1439
1440
        // initialize code page (used to resolve string values)
1441 4
        $codePage = 'CP1252';
1442
1443
        //    offset: ($secOffset+8);    size: var
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1444
        //    loop through property decarations and properties
1445 4
        for ($i = 0; $i < $countProperties; ++$i) {
1446
            //    offset: ($secOffset+8) + (8 * $i);    size: 4;    property ID
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1447 4
            $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i));
1448
1449
            // Use value of property id as appropriate
1450
            // offset: 60 + 8 * $i;    size: 4;    offset from beginning of section (48)
1451 4
            $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
1452
1453 4
            $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
1454
1455
            // initialize property value
1456 4
            $value = null;
1457
1458
            // extract property value based on property type
1459
            switch ($type) {
1460 4
                case 0x02:    //    2 byte signed integer
1461 4
                    $value = self::getInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1462 4
                    break;
1463 4
                case 0x03:    //    4 byte signed integer
1464 4
                    $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1465 4
                    break;
1466 4
                case 0x0B:  // Boolean
1467 4
                    $value = self::getInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1468 4
                    $value = ($value == 0 ? false : true);
1469 4
                    break;
1470 4
                case 0x13:    //    4 byte unsigned integer
1471
                    // not needed yet, fix later if necessary
1472
                    break;
1473 4 View Code Duplication
                case 0x1E:    //    null-terminated string prepended by dword string length
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1474 4
                    $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1475 4
                    $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1476 4
                    $value = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1477 4
                    $value = rtrim($value);
1478 4
                    break;
1479 4 View Code Duplication
                case 0x40:    //    Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1480
                    // PHP-Time
1481
                    $value = \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
1482
                    break;
1483 4
                case 0x47:    //    Clipboard format
1484
                    // not needed yet, fix later if necessary
1485
                    break;
1486
            }
1487
1488
            switch ($id) {
1489 4
                case 0x01:    //    Code Page
1490 4
                    $codePage = \PhpOffice\PhpSpreadsheet\Shared\CodePage::numberToName($value);
1491 4
                    break;
1492 4
                case 0x02:    //    Category
1493 3
                    $this->spreadsheet->getProperties()->setCategory($value);
1494 3
                    break;
1495 4
                case 0x03:    //    Presentation Target
1496
                    //    Not supported by PhpSpreadsheet
1497
                    break;
1498 4
                case 0x04:    //    Bytes
1499
                    //    Not supported by PhpSpreadsheet
1500
                    break;
1501 4
                case 0x05:    //    Lines
1502
                    //    Not supported by PhpSpreadsheet
1503
                    break;
1504 4
                case 0x06:    //    Paragraphs
1505
                    //    Not supported by PhpSpreadsheet
1506
                    break;
1507 4
                case 0x07:    //    Slides
1508
                    //    Not supported by PhpSpreadsheet
1509
                    break;
1510 4
                case 0x08:    //    Notes
1511
                    //    Not supported by PhpSpreadsheet
1512
                    break;
1513 4
                case 0x09:    //    Hidden Slides
1514
                    //    Not supported by PhpSpreadsheet
1515
                    break;
1516 4
                case 0x0A:    //    MM Clips
1517
                    //    Not supported by PhpSpreadsheet
1518
                    break;
1519 4
                case 0x0B:    //    Scale Crop
1520
                    //    Not supported by PhpSpreadsheet
1521 4
                    break;
1522 4
                case 0x0C:    //    Heading Pairs
1523
                    //    Not supported by PhpSpreadsheet
1524 4
                    break;
1525 4
                case 0x0D:    //    Titles of Parts
1526
                    //    Not supported by PhpSpreadsheet
1527 4
                    break;
1528 4
                case 0x0E:    //    Manager
1529 1
                    $this->spreadsheet->getProperties()->setManager($value);
1530 1
                    break;
1531 4
                case 0x0F:    //    Company
1532 3
                    $this->spreadsheet->getProperties()->setCompany($value);
1533 3
                    break;
1534 4
                case 0x10:    //    Links up-to-date
1535
                    //    Not supported by PhpSpreadsheet
1536 4
                    break;
1537
            }
1538
        }
1539 4
    }
1540
1541
    /**
1542
     * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
1543
     */
1544 4
    private function readDefault()
1545
    {
1546 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1547
1548
        // move stream pointer to next record
1549 4
        $this->pos += 4 + $length;
1550 4
    }
1551
1552
    /**
1553
     *    The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
1554
     *        this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
1555
     */
1556 1
    private function readNote()
1557
    {
1558 1
        $length = self::getInt2d($this->data, $this->pos + 2);
1559 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1560
1561
        // move stream pointer to next record
1562 1
        $this->pos += 4 + $length;
1563
1564 1
        if ($this->readDataOnly) {
1565
            return;
1566
        }
1567
1568 1
        $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
1569 1
        if ($this->version == self::XLS_BIFF8) {
1570 1
            $noteObjID = self::getInt2d($recordData, 6);
1571 1
            $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
1572 1
            $noteAuthor = $noteAuthor['value'];
1573 1
            $this->cellNotes[$noteObjID] = [
1574 1
                'cellRef' => $cellAddress,
1575 1
                'objectID' => $noteObjID,
1576 1
                'author' => $noteAuthor,
1577
            ];
1578
        } else {
1579
            $extension = false;
1580
            if ($cellAddress == '$B$65536') {
1581
                //    If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
1582
                //        note from the previous cell annotation. We're not yet handling this, so annotations longer than the
1583
                //        max 2048 bytes will probably throw a wobbly.
1584
                $row = self::getInt2d($recordData, 0);
0 ignored issues
show
Unused Code introduced by
$row is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1585
                $extension = true;
1586
                $cellAddress = array_pop(array_keys($this->phpSheet->getComments()));
0 ignored issues
show
Bug introduced by
array_keys($this->phpSheet->getComments()) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
1587
            }
1588
1589
            $cellAddress = str_replace('$', '', $cellAddress);
1590
            $noteLength = self::getInt2d($recordData, 4);
0 ignored issues
show
Unused Code introduced by
$noteLength is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1591
            $noteText = trim(substr($recordData, 6));
1592
1593
            if ($extension) {
1594
                //    Concatenate this extension with the currently set comment for the cell
1595
                $comment = $this->phpSheet->getComment($cellAddress);
1596
                $commentText = $comment->getText()->getPlainText();
1597
                $comment->setText($this->parseRichText($commentText . $noteText));
1598
            } else {
1599
                //    Set comment for the cell
1600
                $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
1601
//                                                    ->setAuthor($author)
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1602
            }
1603
        }
1604 1
    }
1605
1606
    /**
1607
     *    The TEXT Object record contains the text associated with a cell annotation.
1608
     */
1609 1
    private function readTextObject()
1610
    {
1611 1
        $length = self::getInt2d($this->data, $this->pos + 2);
1612 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1613
1614
        // move stream pointer to next record
1615 1
        $this->pos += 4 + $length;
1616
1617 1
        if ($this->readDataOnly) {
1618
            return;
1619
        }
1620
1621
        // recordData consists of an array of subrecords looking like this:
1622
        //    grbit: 2 bytes; Option Flags
1623
        //    rot: 2 bytes; rotation
1624
        //    cchText: 2 bytes; length of the text (in the first continue record)
1625
        //    cbRuns: 2 bytes; length of the formatting (in the second continue record)
1626
        // followed by the continuation records containing the actual text and formatting
1627 1
        $grbitOpts = self::getInt2d($recordData, 0);
1628 1
        $rot = self::getInt2d($recordData, 2);
1629 1
        $cchText = self::getInt2d($recordData, 10);
1630 1
        $cbRuns = self::getInt2d($recordData, 12);
1631 1
        $text = $this->getSplicedRecordData();
1632
1633 1
        $this->textObjects[$this->textObjRef] = [
1634 1
            'text' => substr($text['recordData'], $text['spliceOffsets'][0] + 1, $cchText),
1635 1
            'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns),
1636 1
            'alignment' => $grbitOpts,
1637 1
            'rotation' => $rot,
1638
        ];
1639 1
    }
1640
1641
    /**
1642
     * Read BOF
1643
     */
1644 4
    private function readBof()
1645
    {
1646 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1647 4
        $recordData = substr($this->data, $this->pos + 4, $length);
1648
1649
        // move stream pointer to next record
1650 4
        $this->pos += 4 + $length;
1651
1652
        // offset: 2; size: 2; type of the following data
1653 4
        $substreamType = self::getInt2d($recordData, 2);
1654
1655
        switch ($substreamType) {
1656 4
            case self::XLS_WORKBOOKGLOBALS:
1657 4
                $version = self::getInt2d($recordData, 0);
1658 4
                if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1659
                    throw new Exception('Cannot read this Excel file. Version is too old.');
1660
                }
1661 4
                $this->version = $version;
1662 4
                break;
1663 4
            case self::XLS_WORKSHEET:
1664
                // do not use this version information for anything
1665
                // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
1666 4
                break;
1667
            default:
1668
                // substream, e.g. chart
1669
                // just skip the entire substream
1670
                do {
1671
                    $code = self::getInt2d($this->data, $this->pos);
1672
                    $this->readDefault();
1673
                } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
1674
                break;
1675
        }
1676 4
    }
1677
1678
    /**
1679
     * FILEPASS
1680
     *
1681
     * This record is part of the File Protection Block. It
1682
     * contains information about the read/write password of the
1683
     * file. All record contents following this record will be
1684
     * encrypted.
1685
     *
1686
     * --    "OpenOffice.org's Documentation of the Microsoft
1687
     *         Excel File Format"
1688
     *
1689
     * The decryption functions and objects used from here on in
1690
     * are based on the source of Spreadsheet-ParseExcel:
1691
     * http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/
1692
     */
1693
    private function readFilepass()
1694
    {
1695
        $length = self::getInt2d($this->data, $this->pos + 2);
1696
1697
        if ($length != 54) {
1698
            throw new Exception('Unexpected file pass record length');
1699
        }
1700
1701
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1702
1703
        // move stream pointer to next record
1704
        $this->pos += 4 + $length;
1705
1706
        if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
1707
            throw new Exception('Decryption password incorrect');
1708
        }
1709
1710
        $this->encryption = self::MS_BIFF_CRYPTO_RC4;
1711
1712
        // Decryption required from the record after next onwards
1713
        $this->encryptionStartPos = $this->pos + self::getInt2d($this->data, $this->pos + 2);
1714
    }
1715
1716
    /**
1717
     * Make an RC4 decryptor for the given block
1718
     *
1719
     * @param int         Block for which to create decrypto
1720
     * @param string $valContext MD5 context state
1721
     *
1722
     * @return Xls\RC4
1723
     */
1724
    private function makeKey($block, $valContext)
1725
    {
1726
        $pwarray = str_repeat("\0", 64);
1727
1728
        for ($i = 0; $i < 5; ++$i) {
1729
            $pwarray[$i] = $valContext[$i];
1730
        }
1731
1732
        $pwarray[5] = chr($block & 0xff);
1733
        $pwarray[6] = chr(($block >> 8) & 0xff);
1734
        $pwarray[7] = chr(($block >> 16) & 0xff);
1735
        $pwarray[8] = chr(($block >> 24) & 0xff);
1736
1737
        $pwarray[9] = "\x80";
1738
        $pwarray[56] = "\x48";
1739
1740
        $md5 = new Xls\MD5();
1741
        $md5->add($pwarray);
1742
1743
        $s = $md5->getContext();
1744
1745
        return new Xls\RC4($s);
1746
    }
1747
1748
    /**
1749
     * Verify RC4 file password
1750
     *
1751
     * @param string $password        Password to check
1752
     * @param string $docid           Document id
1753
     * @param string $salt_data       Salt data
1754
     * @param string $hashedsalt_data Hashed salt data
1755
     * @param string $valContext     Set to the MD5 context of the value
1756
     *
1757
     * @return bool Success
1758
     */
1759
    private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
1760
    {
1761
        $pwarray = str_repeat("\0", 64);
1762
1763
        for ($i = 0; $i < strlen($password); ++$i) {
1764
            $o = ord(substr($password, $i, 1));
1765
            $pwarray[2 * $i] = chr($o & 0xff);
1766
            $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1767
        }
1768
        $pwarray[2 * $i] = chr(0x80);
1769
        $pwarray[56] = chr(($i << 4) & 0xff);
1770
1771
        $md5 = new Xls\MD5();
1772
        $md5->add($pwarray);
1773
1774
        $mdContext1 = $md5->getContext();
1775
1776
        $offset = 0;
1777
        $keyoffset = 0;
1778
        $tocopy = 5;
1779
1780
        $md5->reset();
1781
1782
        while ($offset != 16) {
1783
            if ((64 - $offset) < 5) {
1784
                $tocopy = 64 - $offset;
1785
            }
1786
            for ($i = 0; $i <= $tocopy; ++$i) {
1787
                $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1788
            }
1789
            $offset += $tocopy;
1790
1791
            if ($offset == 64) {
1792
                $md5->add($pwarray);
1793
                $keyoffset = $tocopy;
1794
                $tocopy = 5 - $tocopy;
1795
                $offset = 0;
1796
                continue;
1797
            }
1798
1799
            $keyoffset = 0;
1800
            $tocopy = 5;
1801
            for ($i = 0; $i < 16; ++$i) {
1802
                $pwarray[$offset + $i] = $docid[$i];
1803
            }
1804
            $offset += 16;
1805
        }
1806
1807
        $pwarray[16] = "\x80";
1808
        for ($i = 0; $i < 47; ++$i) {
1809
            $pwarray[17 + $i] = "\0";
1810
        }
1811
        $pwarray[56] = "\x80";
1812
        $pwarray[57] = "\x0a";
1813
1814
        $md5->add($pwarray);
1815
        $valContext = $md5->getContext();
1816
1817
        $key = $this->makeKey(0, $valContext);
1818
1819
        $salt = $key->RC4($salt_data);
1820
        $hashedsalt = $key->RC4($hashedsalt_data);
1821
1822
        $salt .= "\x80" . str_repeat("\0", 47);
1823
        $salt[56] = "\x80";
1824
1825
        $md5->reset();
1826
        $md5->add($salt);
1827
        $mdContext2 = $md5->getContext();
1828
1829
        return $mdContext2 == $hashedsalt;
1830
    }
1831
1832
    /**
1833
     * CODEPAGE
1834
     *
1835
     * This record stores the text encoding used to write byte
1836
     * strings, stored as MS Windows code page identifier.
1837
     *
1838
     * --    "OpenOffice.org's Documentation of the Microsoft
1839
     *         Excel File Format"
1840
     */
1841 4 View Code Duplication
    private function readCodepage()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1842
    {
1843 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1844 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1845
1846
        // move stream pointer to next record
1847 4
        $this->pos += 4 + $length;
1848
1849
        // offset: 0; size: 2; code page identifier
1850 4
        $codepage = self::getInt2d($recordData, 0);
1851
1852 4
        $this->codepage = \PhpOffice\PhpSpreadsheet\Shared\CodePage::numberToName($codepage);
1853 4
    }
1854
1855
    /**
1856
     * DATEMODE
1857
     *
1858
     * This record specifies the base date for displaying date
1859
     * values. All dates are stored as count of days past this
1860
     * base date. In BIFF2-BIFF4 this record is part of the
1861
     * Calculation Settings Block. In BIFF5-BIFF8 it is
1862
     * stored in the Workbook Globals Substream.
1863
     *
1864
     * --    "OpenOffice.org's Documentation of the Microsoft
1865
     *         Excel File Format"
1866
     */
1867 4
    private function readDateMode()
1868
    {
1869 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1870 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1871
1872
        // move stream pointer to next record
1873 4
        $this->pos += 4 + $length;
1874
1875
        // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
1876 4
        \PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar(\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_WINDOWS_1900);
1877 4
        if (ord($recordData{0}) == 1) {
1878
            \PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar(\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904);
1879
        }
1880 4
    }
1881
1882
    /**
1883
     * Read a FONT record
1884
     */
1885 4
    private function readFont()
1886
    {
1887 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1888 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1889
1890
        // move stream pointer to next record
1891 4
        $this->pos += 4 + $length;
1892
1893 4
        if (!$this->readDataOnly) {
1894 4
            $objFont = new \PhpOffice\PhpSpreadsheet\Style\Font();
1895
1896
            // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
1897 4
            $size = self::getInt2d($recordData, 0);
1898 4
            $objFont->setSize($size / 20);
1899
1900
            // offset: 2; size: 2; option flags
1901
            // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
1902
            // bit: 1; mask 0x0002; italic
1903 4
            $isItalic = (0x0002 & self::getInt2d($recordData, 2)) >> 1;
1904 4
            if ($isItalic) {
1905 4
                $objFont->setItalic(true);
1906
            }
1907
1908
            // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
1909
            // bit: 3; mask 0x0008; strike
1910 4
            $isStrike = (0x0008 & self::getInt2d($recordData, 2)) >> 3;
1911 4
            if ($isStrike) {
1912
                $objFont->setStrikethrough(true);
1913
            }
1914
1915
            // offset: 4; size: 2; colour index
1916 4
            $colorIndex = self::getInt2d($recordData, 4);
1917 4
            $objFont->colorIndex = $colorIndex;
0 ignored issues
show
Bug introduced by
The property colorIndex does not seem to exist in PhpOffice\PhpSpreadsheet\Style\Font.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1918
1919
            // offset: 6; size: 2; font weight
1920 4
            $weight = self::getInt2d($recordData, 6);
1921
            switch ($weight) {
1922 4
                case 0x02BC:
1923 4
                    $objFont->setBold(true);
1924 4
                    break;
1925
            }
1926
1927
            // offset: 8; size: 2; escapement type
1928 4
            $escapement = self::getInt2d($recordData, 8);
1929
            switch ($escapement) {
1930 4
                case 0x0001:
1931
                    $objFont->setSuperScript(true);
1932
                    break;
1933 4
                case 0x0002:
1934
                    $objFont->setSubScript(true);
1935
                    break;
1936
            }
1937
1938
            // offset: 10; size: 1; underline type
1939 4
            $underlineType = ord($recordData{10});
1940
            switch ($underlineType) {
1941 4
                case 0x00:
1942 4
                    break; // no underline
1943 2
                case 0x01:
1944 2
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
1945 2
                    break;
1946
                case 0x02:
1947
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE);
1948
                    break;
1949
                case 0x21:
1950
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING);
1951
                    break;
1952
                case 0x22:
1953
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING);
1954
                    break;
1955
            }
1956
1957
            // offset: 11; size: 1; font family
1958
            // offset: 12; size: 1; character set
1959
            // offset: 13; size: 1; not used
1960
            // offset: 14; size: var; font name
1961 4 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1962 4
                $string = self::readUnicodeStringShort(substr($recordData, 14));
1963
            } else {
1964
                $string = $this->readByteStringShort(substr($recordData, 14));
1965
            }
1966 4
            $objFont->setName($string['value']);
1967
1968 4
            $this->objFonts[] = $objFont;
1969
        }
1970 4
    }
1971
1972
    /**
1973
     * FORMAT
1974
     *
1975
     * This record contains information about a number format.
1976
     * All FORMAT records occur together in a sequential list.
1977
     *
1978
     * In BIFF2-BIFF4 other records referencing a FORMAT record
1979
     * contain a zero-based index into this list. From BIFF5 on
1980
     * the FORMAT record contains the index itself that will be
1981
     * used by other records.
1982
     *
1983
     * --    "OpenOffice.org's Documentation of the Microsoft
1984
     *         Excel File Format"
1985
     */
1986 4
    private function readFormat()
1987
    {
1988 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1989 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1990
1991
        // move stream pointer to next record
1992 4
        $this->pos += 4 + $length;
1993
1994 4
        if (!$this->readDataOnly) {
1995 4
            $indexCode = self::getInt2d($recordData, 0);
1996
1997 4 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1998 4
                $string = self::readUnicodeStringLong(substr($recordData, 2));
1999
            } else {
2000
                // BIFF7
2001
                $string = $this->readByteStringShort(substr($recordData, 2));
2002
            }
2003
2004 4
            $formatString = $string['value'];
2005 4
            $this->formats[$indexCode] = $formatString;
2006
        }
2007 4
    }
2008
2009
    /**
2010
     * XF - Extended Format
2011
     *
2012
     * This record contains formatting information for cells, rows, columns or styles.
2013
     * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2014
     * and 1 cell XF.
2015
     * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2016
     * and XF record 15 is a cell XF
2017
     * We only read the first cell style XF and skip the remaining cell style XF records
2018
     * We read all cell XF records.
2019
     *
2020
     * --    "OpenOffice.org's Documentation of the Microsoft
2021
     *         Excel File Format"
2022
     */
2023 4
    private function readXf()
2024
    {
2025 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2026 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2027
2028
        // move stream pointer to next record
2029 4
        $this->pos += 4 + $length;
2030
2031 4
        $objStyle = new \PhpOffice\PhpSpreadsheet\Style();
2032
2033 4
        if (!$this->readDataOnly) {
2034
            // offset:  0; size: 2; Index to FONT record
2035 4
            if (self::getInt2d($recordData, 0) < 4) {
2036 4
                $fontIndex = self::getInt2d($recordData, 0);
2037
            } else {
2038
                // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2039
                // check the OpenOffice documentation of the FONT record
2040 4
                $fontIndex = self::getInt2d($recordData, 0) - 1;
2041
            }
2042 4
            $objStyle->setFont($this->objFonts[$fontIndex]);
2043
2044
            // offset:  2; size: 2; Index to FORMAT record
2045 4
            $numberFormatIndex = self::getInt2d($recordData, 2);
2046 4
            if (isset($this->formats[$numberFormatIndex])) {
2047
                // then we have user-defined format code
2048 3
                $numberformat = ['code' => $this->formats[$numberFormatIndex]];
2049 4
            } elseif (($code = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2050
                // then we have built-in format code
2051 4
                $numberformat = ['code' => $code];
2052
            } else {
2053
                // we set the general format code
2054
                $numberformat = ['code' => 'General'];
2055
            }
2056 4
            $objStyle->getNumberFormat()->setFormatCode($numberformat['code']);
2057
2058
            // offset:  4; size: 2; XF type, cell protection, and parent style XF
2059
            // bit 2-0; mask 0x0007; XF_TYPE_PROT
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2060 4
            $xfTypeProt = self::getInt2d($recordData, 4);
2061
            // bit 0; mask 0x01; 1 = cell is locked
2062 4
            $isLocked = (0x01 & $xfTypeProt) >> 0;
2063 4
            $objStyle->getProtection()->setLocked($isLocked ? \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_INHERIT : \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED);
2064
2065
            // bit 1; mask 0x02; 1 = Formula is hidden
2066 4
            $isHidden = (0x02 & $xfTypeProt) >> 1;
2067 4
            $objStyle->getProtection()->setHidden($isHidden ? \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_PROTECTED : \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED);
2068
2069
            // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2070 4
            $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2071
2072
            // offset:  6; size: 1; Alignment and text break
2073
            // bit 2-0, mask 0x07; horizontal alignment
2074 4
            $horAlign = (0x07 & ord($recordData{6})) >> 0;
2075
            switch ($horAlign) {
2076 4
                case 0:
2077 4
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_GENERAL);
2078 4
                    break;
2079 2
                case 1:
2080 2
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
2081 2
                    break;
2082 2
                case 2:
2083
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
2084
                    break;
2085 2
                case 3:
2086 2
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT);
2087 2
                    break;
2088 2
                case 4:
2089
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_FILL);
2090
                    break;
2091 2
                case 5:
2092 2
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_JUSTIFY);
2093 2
                    break;
2094
                case 6:
2095
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2096
                    break;
2097
            }
2098
            // bit 3, mask 0x08; wrap text
2099 4
            $wrapText = (0x08 & ord($recordData{6})) >> 3;
2100
            switch ($wrapText) {
2101 4
                case 0:
2102 4
                    $objStyle->getAlignment()->setWrapText(false);
2103 4
                    break;
2104 2
                case 1:
2105 2
                    $objStyle->getAlignment()->setWrapText(true);
2106 2
                    break;
2107
            }
2108
            // bit 6-4, mask 0x70; vertical alignment
2109 4
            $vertAlign = (0x70 & ord($recordData{6})) >> 4;
2110
            switch ($vertAlign) {
2111 4
                case 0:
2112
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP);
2113
                    break;
2114 4
                case 1:
2115 2
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
2116 2
                    break;
2117 4
                case 2:
2118 4
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_BOTTOM);
2119 4
                    break;
2120
                case 3:
2121
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_JUSTIFY);
2122
                    break;
2123
            }
2124
2125 4
            if ($this->version == self::XLS_BIFF8) {
2126
                // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2127 4
                $angle = ord($recordData{7});
2128 4
                $rotation = 0;
2129 4
                if ($angle <= 90) {
2130 4
                    $rotation = $angle;
2131
                } elseif ($angle <= 180) {
2132
                    $rotation = 90 - $angle;
2133
                } elseif ($angle == 255) {
2134
                    $rotation = -165;
2135
                }
2136 4
                $objStyle->getAlignment()->setTextRotation($rotation);
2137
2138
                // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2139
                // bit: 3-0; mask: 0x0F; indent level
2140 4
                $indent = (0x0F & ord($recordData{8})) >> 0;
2141 4
                $objStyle->getAlignment()->setIndent($indent);
2142
2143
                // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2144 4
                $shrinkToFit = (0x10 & ord($recordData{8})) >> 4;
2145
                switch ($shrinkToFit) {
2146 4
                    case 0:
2147 4
                        $objStyle->getAlignment()->setShrinkToFit(false);
2148 4
                        break;
2149 1
                    case 1:
2150 1
                        $objStyle->getAlignment()->setShrinkToFit(true);
2151 1
                        break;
2152
                }
2153
2154
                // offset:  9; size: 1; Flags used for attribute groups
2155
2156
                // offset: 10; size: 4; Cell border lines and background area
2157
                // bit: 3-0; mask: 0x0000000F; left style
2158 4 View Code Duplication
                if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2159 4
                    $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2160
                }
2161
                // bit: 7-4; mask: 0x000000F0; right style
2162 4 View Code Duplication
                if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2163 4
                    $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2164
                }
2165
                // bit: 11-8; mask: 0x00000F00; top style
2166 4 View Code Duplication
                if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2167 4
                    $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2168
                }
2169
                // bit: 15-12; mask: 0x0000F000; bottom style
2170 4 View Code Duplication
                if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2171 4
                    $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2172
                }
2173
                // bit: 22-16; mask: 0x007F0000; left color
2174 4
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
0 ignored issues
show
Bug introduced by
The property colorIndex does not seem to exist in PhpOffice\PhpSpreadsheet\Style\Border.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2175
2176
                // bit: 29-23; mask: 0x3F800000; right color
2177 4
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2178
2179
                // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2180 4
                $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2181
2182
                // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2183 4
                $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2184
2185 4
                if ($diagonalUp == false && $diagonalDown == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2186 4
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_NONE);
2187
                } elseif ($diagonalUp == true && $diagonalDown == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2188
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_UP);
2189
                } elseif ($diagonalUp == false && $diagonalDown == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2190
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_DOWN);
2191
                } elseif ($diagonalUp == true && $diagonalDown == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2192
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_BOTH);
2193
                }
2194
2195
                // offset: 14; size: 4;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2196
                // bit: 6-0; mask: 0x0000007F; top color
2197 4
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2198
2199
                // bit: 13-7; mask: 0x00003F80; bottom color
2200 4
                $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2201
2202
                // bit: 20-14; mask: 0x001FC000; diagonal color
2203 4
                $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2204
2205
                // bit: 24-21; mask: 0x01E00000; diagonal style
2206 4 View Code Duplication
                if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2207 4
                    $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2208
                }
2209
2210
                // bit: 31-26; mask: 0xFC000000 fill pattern
2211 4
                if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2212 4
                    $objStyle->getFill()->setFillType($fillType);
2213
                }
2214
                // offset: 18; size: 2; pattern and background colour
2215
                // bit: 6-0; mask: 0x007F; color index for pattern color
2216 4
                $objStyle->getFill()->startcolorIndex = (0x007F & self::getInt2d($recordData, 18)) >> 0;
0 ignored issues
show
Bug introduced by
The property startcolorIndex does not seem to exist. Did you mean startColor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2217
2218
                // bit: 13-7; mask: 0x3F80; color index for pattern background
2219 4
                $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getInt2d($recordData, 18)) >> 7;
0 ignored issues
show
Bug introduced by
The property endcolorIndex does not seem to exist. Did you mean endColor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2220
            } else {
2221
                // BIFF5
2222
2223
                // offset: 7; size: 1; Text orientation and flags
2224
                $orientationAndFlags = ord($recordData{7});
2225
2226
                // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2227
                $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2228
                switch ($xfOrientation) {
2229
                    case 0:
2230
                        $objStyle->getAlignment()->setTextRotation(0);
2231
                        break;
2232
                    case 1:
2233
                        $objStyle->getAlignment()->setTextRotation(-165);
2234
                        break;
2235
                    case 2:
2236
                        $objStyle->getAlignment()->setTextRotation(90);
2237
                        break;
2238
                    case 3:
2239
                        $objStyle->getAlignment()->setTextRotation(-90);
2240
                        break;
2241
                }
2242
2243
                // offset: 8; size: 4; cell border lines and background area
2244
                $borderAndBackground = self::getInt4d($recordData, 8);
2245
2246
                // bit: 6-0; mask: 0x0000007F; color index for pattern color
2247
                $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
0 ignored issues
show
Bug introduced by
The property startcolorIndex does not seem to exist. Did you mean startColor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2248
2249
                // bit: 13-7; mask: 0x00003F80; color index for pattern background
2250
                $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
0 ignored issues
show
Bug introduced by
The property endcolorIndex does not seem to exist. Did you mean endColor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2251
2252
                // bit: 21-16; mask: 0x003F0000; fill pattern
2253
                $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
2254
2255
                // bit: 24-22; mask: 0x01C00000; bottom line style
2256
                $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
2257
2258
                // bit: 31-25; mask: 0xFE000000; bottom line color
2259
                $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2260
2261
                // offset: 12; size: 4; cell border lines
2262
                $borderLines = self::getInt4d($recordData, 12);
2263
2264
                // bit: 2-0; mask: 0x00000007; top line style
2265
                $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
2266
2267
                // bit: 5-3; mask: 0x00000038; left line style
2268
                $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
2269
2270
                // bit: 8-6; mask: 0x000001C0; right line style
2271
                $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
2272
2273
                // bit: 15-9; mask: 0x0000FE00; top line color index
2274
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2275
2276
                // bit: 22-16; mask: 0x007F0000; left line color index
2277
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2278
2279
                // bit: 29-23; mask: 0x3F800000; right line color index
2280
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2281
            }
2282
2283
            // add cellStyleXf or cellXf and update mapping
2284 4
            if ($isCellStyleXf) {
2285
                // we only read one style XF record which is always the first
2286 4
                if ($this->xfIndex == 0) {
2287 4
                    $this->spreadsheet->addCellStyleXf($objStyle);
2288 4
                    $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2289
                }
2290
            } else {
2291
                // we read all cell XF records
2292 4
                $this->spreadsheet->addCellXf($objStyle);
2293 4
                $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
2294
            }
2295
2296
            // update XF index for when we read next record
2297 4
            ++$this->xfIndex;
2298
        }
2299 4
    }
2300
2301
    /**
2302
     *
2303
     */
2304 3
    private function readXfExt()
2305
    {
2306 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2307 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2308
2309
        // move stream pointer to next record
2310 3
        $this->pos += 4 + $length;
2311
2312 3
        if (!$this->readDataOnly) {
2313
            // offset: 0; size: 2; 0x087D = repeated header
2314
2315
            // offset: 2; size: 2
2316
2317
            // offset: 4; size: 8; not used
2318
2319
            // offset: 12; size: 2; record version
2320
2321
            // offset: 14; size: 2; index to XF record which this record modifies
2322 3
            $ixfe = self::getInt2d($recordData, 14);
2323
2324
            // offset: 16; size: 2; not used
2325
2326
            // offset: 18; size: 2; number of extension properties that follow
2327 3
            $cexts = self::getInt2d($recordData, 18);
0 ignored issues
show
Unused Code introduced by
$cexts is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2328
2329
            // start reading the actual extension data
2330 3
            $offset = 20;
2331 3
            while ($offset < $length) {
2332
                // extension type
2333 3
                $extType = self::getInt2d($recordData, $offset);
2334
2335
                // extension length
2336 3
                $cb = self::getInt2d($recordData, $offset + 2);
2337
2338
                // extension data
2339 3
                $extData = substr($recordData, $offset + 4, $cb);
2340
2341
                switch ($extType) {
2342 3 View Code Duplication
                    case 4:        // fill start color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2343 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2344 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2345
2346 3
                        if ($xclfType == 2) {
2347 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2348
2349
                            // modify the relevant style property
2350 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2351 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2352 1
                                $fill->getStartColor()->setRGB($rgb);
2353 1
                                unset($fill->startcolorIndex); // normal color index does not apply, discard
2354
                            }
2355
                        }
2356 3
                        break;
2357 3 View Code Duplication
                    case 5:        // fill end color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2358 1
                        $xclfType = self::getInt2d($extData, 0); // color type
2359 1
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2360
2361 1
                        if ($xclfType == 2) {
2362 1
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2363
2364
                            // modify the relevant style property
2365 1
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2366 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2367 1
                                $fill->getEndColor()->setRGB($rgb);
2368 1
                                unset($fill->endcolorIndex); // normal color index does not apply, discard
2369
                            }
2370
                        }
2371 1
                        break;
2372 3 View Code Duplication
                    case 7:        // border color top
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2373 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2374 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2375
2376 3
                        if ($xclfType == 2) {
2377 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2378
2379
                            // modify the relevant style property
2380 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2381 1
                                $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
2382 1
                                $top->getColor()->setRGB($rgb);
2383 1
                                unset($top->colorIndex); // normal color index does not apply, discard
2384
                            }
2385
                        }
2386 3
                        break;
2387 3 View Code Duplication
                    case 8:        // border color bottom
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2388 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2389 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2390
2391 3
                        if ($xclfType == 2) {
2392 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2393
2394
                            // modify the relevant style property
2395 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2396 1
                                $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2397 1
                                $bottom->getColor()->setRGB($rgb);
2398 1
                                unset($bottom->colorIndex); // normal color index does not apply, discard
2399
                            }
2400
                        }
2401 3
                        break;
2402 3 View Code Duplication
                    case 9:        // border color left
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2403 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2404 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2405
2406 3
                        if ($xclfType == 2) {
2407 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2408
2409
                            // modify the relevant style property
2410 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2411 1
                                $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2412 1
                                $left->getColor()->setRGB($rgb);
2413 1
                                unset($left->colorIndex); // normal color index does not apply, discard
2414
                            }
2415
                        }
2416 3
                        break;
2417 3 View Code Duplication
                    case 10:        // border color right
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2418 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2419 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2420
2421 3
                        if ($xclfType == 2) {
2422 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2423
2424
                            // modify the relevant style property
2425 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2426 1
                                $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
2427 1
                                $right->getColor()->setRGB($rgb);
2428 1
                                unset($right->colorIndex); // normal color index does not apply, discard
2429
                            }
2430
                        }
2431 3
                        break;
2432 3 View Code Duplication
                    case 11:        // border color diagonal
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2433
                        $xclfType = self::getInt2d($extData, 0); // color type
2434
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2435
2436
                        if ($xclfType == 2) {
2437
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2438
2439
                            // modify the relevant style property
2440
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2441
                                $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2442
                                $diagonal->getColor()->setRGB($rgb);
2443
                                unset($diagonal->colorIndex); // normal color index does not apply, discard
2444
                            }
2445
                        }
2446
                        break;
2447 3 View Code Duplication
                    case 13:    // font color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2448 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2449 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2450
2451 3
                        if ($xclfType == 2) {
2452 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2453
2454
                            // modify the relevant style property
2455 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2456 1
                                $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2457 1
                                $font->getColor()->setRGB($rgb);
2458 1
                                unset($font->colorIndex); // normal color index does not apply, discard
2459
                            }
2460
                        }
2461 3
                        break;
2462
                }
2463
2464 3
                $offset += $cb;
2465
            }
2466
        }
2467 3
    }
2468
2469
    /**
2470
     * Read STYLE record
2471
     */
2472 4
    private function readStyle()
2473
    {
2474 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2475 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2476
2477
        // move stream pointer to next record
2478 4
        $this->pos += 4 + $length;
2479
2480 4
        if (!$this->readDataOnly) {
2481
            // offset: 0; size: 2; index to XF record and flag for built-in style
2482 4
            $ixfe = self::getInt2d($recordData, 0);
2483
2484
            // bit: 11-0; mask 0x0FFF; index to XF record
2485 4
            $xfIndex = (0x0FFF & $ixfe) >> 0;
0 ignored issues
show
Unused Code introduced by
$xfIndex is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2486
2487
            // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2488 4
            $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2489
2490 4
            if ($isBuiltIn) {
2491
                // offset: 2; size: 1; identifier for built-in style
2492 4
                $builtInId = ord($recordData{2});
2493
2494
                switch ($builtInId) {
2495 4
                    case 0x00:
2496
                        // currently, we are not using this for anything
2497 4
                        break;
2498
                    default:
2499 4
                        break;
2500
                }
2501
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
2502
                // user-defined; not supported by PhpSpreadsheet
2503
            }
2504
        }
2505 4
    }
2506
2507
    /**
2508
     * Read PALETTE record
2509
     */
2510 2
    private function readPalette()
2511
    {
2512 2
        $length = self::getInt2d($this->data, $this->pos + 2);
2513 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2514
2515
        // move stream pointer to next record
2516 2
        $this->pos += 4 + $length;
2517
2518 2
        if (!$this->readDataOnly) {
2519
            // offset: 0; size: 2; number of following colors
2520 2
            $nm = self::getInt2d($recordData, 0);
2521
2522
            // list of RGB colors
2523 2
            for ($i = 0; $i < $nm; ++$i) {
2524 2
                $rgb = substr($recordData, 2 + 4 * $i, 4);
2525 2
                $this->palette[] = self::readRGB($rgb);
2526
            }
2527
        }
2528 2
    }
2529
2530
    /**
2531
     * SHEET
2532
     *
2533
     * This record is  located in the  Workbook Globals
2534
     * Substream  and represents a sheet inside the workbook.
2535
     * One SHEET record is written for each sheet. It stores the
2536
     * sheet name and a stream offset to the BOF record of the
2537
     * respective Sheet Substream within the Workbook Stream.
2538
     *
2539
     * --    "OpenOffice.org's Documentation of the Microsoft
2540
     *         Excel File Format"
2541
     */
2542 4
    private function readSheet()
2543
    {
2544 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2545 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2546
2547
        // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2548
        // NOTE: not encrypted
2549 4
        $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2550
2551
        // move stream pointer to next record
2552 4
        $this->pos += 4 + $length;
2553
2554
        // offset: 4; size: 1; sheet state
2555 4
        switch (ord($recordData{4})) {
2556 4
            case 0x00:
2557 4
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE;
2558 4
                break;
2559
            case 0x01:
2560
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_HIDDEN;
2561
                break;
2562
            case 0x02:
2563
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VERYHIDDEN;
2564
                break;
2565
            default:
2566
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE;
2567
                break;
2568
        }
2569
2570
        // offset: 5; size: 1; sheet type
2571 4
        $sheetType = ord($recordData{5});
2572
2573
        // offset: 6; size: var; sheet name
2574 4 View Code Duplication
        if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2575 4
            $string = self::readUnicodeStringShort(substr($recordData, 6));
2576 4
            $rec_name = $string['value'];
2577
        } elseif ($this->version == self::XLS_BIFF7) {
2578
            $string = $this->readByteStringShort(substr($recordData, 6));
2579
            $rec_name = $string['value'];
2580
        }
2581
2582 4
        $this->sheets[] = [
2583 4
            'name' => $rec_name,
0 ignored issues
show
Bug introduced by
The variable $rec_name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2584 4
            'offset' => $rec_offset,
2585 4
            'sheetState' => $sheetState,
2586 4
            'sheetType' => $sheetType,
2587
        ];
2588 4
    }
2589
2590
    /**
2591
     * Read EXTERNALBOOK record
2592
     */
2593 3
    private function readExternalBook()
2594
    {
2595 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2596 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2597
2598
        // move stream pointer to next record
2599 3
        $this->pos += 4 + $length;
2600
2601
        // offset within record data
2602 3
        $offset = 0;
2603
2604
        // there are 4 types of records
2605 3
        if (strlen($recordData) > 4) {
2606
            // external reference
2607
            // offset: 0; size: 2; number of sheet names ($nm)
2608
            $nm = self::getInt2d($recordData, 0);
2609
            $offset += 2;
2610
2611
            // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2612
            $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2613
            $offset += $encodedUrlString['size'];
2614
2615
            // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2616
            $externalSheetNames = [];
2617
            for ($i = 0; $i < $nm; ++$i) {
2618
                $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2619
                $externalSheetNames[] = $externalSheetNameString['value'];
2620
                $offset += $externalSheetNameString['size'];
2621
            }
2622
2623
            // store the record data
2624
            $this->externalBooks[] = [
2625
                'type' => 'external',
2626
                'encodedUrl' => $encodedUrlString['value'],
2627
                'externalSheetNames' => $externalSheetNames,
2628
            ];
2629 3
        } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2630
            // internal reference
2631
            // offset: 0; size: 2; number of sheet in this document
2632
            // offset: 2; size: 2; 0x01 0x04
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2633 3
            $this->externalBooks[] = [
2634 3
                'type' => 'internal',
2635
            ];
2636
        } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2637
            // add-in function
2638
            // offset: 0; size: 2; 0x0001
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2639
            $this->externalBooks[] = [
2640
                'type' => 'addInFunction',
2641
            ];
2642
        } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2643
            // DDE links, OLE links
2644
            // offset: 0; size: 2; 0x0000
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2645
            // offset: 2; size: var; encoded source document name
2646
            $this->externalBooks[] = [
2647
                'type' => 'DDEorOLE',
2648
            ];
2649
        }
2650 3
    }
2651
2652
    /**
2653
     * Read EXTERNNAME record.
2654
     */
2655
    private function readExternName()
2656
    {
2657
        $length = self::getInt2d($this->data, $this->pos + 2);
2658
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2659
2660
        // move stream pointer to next record
2661
        $this->pos += 4 + $length;
2662
2663
        // external sheet references provided for named cells
2664
        if ($this->version == self::XLS_BIFF8) {
2665
            // offset: 0; size: 2; options
2666
            $options = self::getInt2d($recordData, 0);
0 ignored issues
show
Unused Code introduced by
$options is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2667
2668
            // offset: 2; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2669
2670
            // offset: 4; size: 2; not used
2671
2672
            // offset: 6; size: var
2673
            $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2674
2675
            // offset: var; size: var; formula data
2676
            $offset = 6 + $nameString['size'];
2677
            $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2678
2679
            $this->externalNames[] = [
2680
                'name' => $nameString['value'],
2681
                'formula' => $formula,
2682
            ];
2683
        }
2684
    }
2685
2686
    /**
2687
     * Read EXTERNSHEET record
2688
     */
2689 3
    private function readExternSheet()
2690
    {
2691 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2692 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2693
2694
        // move stream pointer to next record
2695 3
        $this->pos += 4 + $length;
2696
2697
        // external sheet references provided for named cells
2698 3
        if ($this->version == self::XLS_BIFF8) {
2699
            // offset: 0; size: 2; number of following ref structures
2700 3
            $nm = self::getInt2d($recordData, 0);
2701 3
            for ($i = 0; $i < $nm; ++$i) {
2702 3
                $this->ref[] = [
2703
                    // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2704 3
                    'externalBookIndex' => self::getInt2d($recordData, 2 + 6 * $i),
2705
                    // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2706 3
                    'firstSheetIndex' => self::getInt2d($recordData, 4 + 6 * $i),
2707
                    // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2708 3
                    'lastSheetIndex' => self::getInt2d($recordData, 6 + 6 * $i),
2709
                ];
2710
            }
2711
        }
2712 3
    }
2713
2714
    /**
2715
     * DEFINEDNAME
2716
     *
2717
     * This record is part of a Link Table. It contains the name
2718
     * and the token array of an internal defined name. Token
2719
     * arrays of defined names contain tokens with aberrant
2720
     * token classes.
2721
     *
2722
     * --    "OpenOffice.org's Documentation of the Microsoft
2723
     *         Excel File Format"
2724
     */
2725 1
    private function readDefinedName()
2726
    {
2727 1
        $length = self::getInt2d($this->data, $this->pos + 2);
2728 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2729
2730
        // move stream pointer to next record
2731 1
        $this->pos += 4 + $length;
2732
2733 1
        if ($this->version == self::XLS_BIFF8) {
2734
            // retrieves named cells
2735
2736
            // offset: 0; size: 2; option flags
2737 1
            $opts = self::getInt2d($recordData, 0);
2738
2739
            // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2740 1
            $isBuiltInName = (0x0020 & $opts) >> 5;
2741
2742
            // offset: 2; size: 1; keyboard shortcut
2743
2744
            // offset: 3; size: 1; length of the name (character count)
2745 1
            $nlen = ord($recordData{3});
2746
2747
            // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2748
            // note: there can also be additional data, this is not included in $flen
2749 1
            $flen = self::getInt2d($recordData, 4);
2750
2751
            // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2752 1
            $scope = self::getInt2d($recordData, 8);
2753
2754
            // offset: 14; size: var; Name (Unicode string without length field)
2755 1
            $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2756
2757
            // offset: var; size: $flen; formula data
2758 1
            $offset = 14 + $string['size'];
2759 1
            $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2760
2761
            try {
2762 1
                $formula = $this->getFormulaFromStructure($formulaStructure);
2763
            } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
2764
                $formula = '';
2765
            }
2766
2767 1
            $this->definedname[] = [
2768 1
                'isBuiltInName' => $isBuiltInName,
2769 1
                'name' => $string['value'],
2770 1
                'formula' => $formula,
2771 1
                'scope' => $scope,
2772
            ];
2773
        }
2774 1
    }
2775
2776
    /**
2777
     * Read MSODRAWINGGROUP record
2778
     */
2779 3 View Code Duplication
    private function readMsoDrawingGroup()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2780
    {
2781 3
        $length = self::getInt2d($this->data, $this->pos + 2);
0 ignored issues
show
Unused Code introduced by
$length is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2782
2783
        // get spliced record data
2784 3
        $splicedRecordData = $this->getSplicedRecordData();
2785 3
        $recordData = $splicedRecordData['recordData'];
2786
2787 3
        $this->drawingGroupData .= $recordData;
2788 3
    }
2789
2790
    /**
2791
     * SST - Shared String Table
2792
     *
2793
     * This record contains a list of all strings used anywhere
2794
     * in the workbook. Each string occurs only once. The
2795
     * workbook uses indexes into the list to reference the
2796
     * strings.
2797
     *
2798
     * --    "OpenOffice.org's Documentation of the Microsoft
2799
     *         Excel File Format"
2800
     **/
2801 4
    private function readSst()
2802
    {
2803
        // offset within (spliced) record data
2804 4
        $pos = 0;
2805
2806
        // get spliced record data
2807 4
        $splicedRecordData = $this->getSplicedRecordData();
2808
2809 4
        $recordData = $splicedRecordData['recordData'];
2810 4
        $spliceOffsets = $splicedRecordData['spliceOffsets'];
2811
2812
        // offset: 0; size: 4; total number of strings in the workbook
2813 4
        $pos += 4;
2814
2815
        // offset: 4; size: 4; number of following strings ($nm)
2816 4
        $nm = self::getInt4d($recordData, 4);
2817 4
        $pos += 4;
2818
2819
        // loop through the Unicode strings (16-bit length)
2820 4
        for ($i = 0; $i < $nm; ++$i) {
2821
            // number of characters in the Unicode string
2822 4
            $numChars = self::getInt2d($recordData, $pos);
2823 4
            $pos += 2;
2824
2825
            // option flags
2826 4
            $optionFlags = ord($recordData{$pos});
2827 4
            ++$pos;
2828
2829
            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2830 4
            $isCompressed = (($optionFlags & 0x01) == 0);
2831
2832
            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2833 4
            $hasAsian = (($optionFlags & 0x04) != 0);
2834
2835
            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
2836 4
            $hasRichText = (($optionFlags & 0x08) != 0);
2837
2838 4
            if ($hasRichText) {
2839
                // number of Rich-Text formatting runs
2840 2
                $formattingRuns = self::getInt2d($recordData, $pos);
2841 2
                $pos += 2;
2842
            }
2843
2844 4
            if ($hasAsian) {
2845
                // size of Asian phonetic setting
2846
                $extendedRunLength = self::getInt4d($recordData, $pos);
2847
                $pos += 4;
2848
            }
2849
2850
            // expected byte length of character array if not split
2851 4
            $len = ($isCompressed) ? $numChars : $numChars * 2;
2852
2853
            // look up limit position
2854 4
            foreach ($spliceOffsets as $spliceOffset) {
2855
                // it can happen that the string is empty, therefore we need
2856
                // <= and not just <
2857 4
                if ($pos <= $spliceOffset) {
2858 4
                    $limitpos = $spliceOffset;
2859 4
                    break;
2860
                }
2861
            }
2862
2863 4
            if ($pos + $len <= $limitpos) {
2864
                // character array is not split between records
2865
2866 4
                $retstr = substr($recordData, $pos, $len);
2867 4
                $pos += $len;
2868
            } else {
2869
                // character array is split between records
2870
2871
                // first part of character array
2872
                $retstr = substr($recordData, $pos, $limitpos - $pos);
0 ignored issues
show
Bug introduced by
The variable $limitpos does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2873
2874
                $bytesRead = $limitpos - $pos;
2875
2876
                // remaining characters in Unicode string
2877
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
2878
2879
                $pos = $limitpos;
2880
2881
                // keep reading the characters
2882
                while ($charsLeft > 0) {
2883
                    // look up next limit position, in case the string span more than one continue record
2884
                    foreach ($spliceOffsets as $spliceOffset) {
2885
                        if ($pos < $spliceOffset) {
2886
                            $limitpos = $spliceOffset;
2887
                            break;
2888
                        }
2889
                    }
2890
2891
                    // repeated option flags
2892
                    // OpenOffice.org documentation 5.21
2893
                    $option = ord($recordData{$pos});
2894
                    ++$pos;
2895
2896
                    if ($isCompressed && ($option == 0)) {
2897
                        // 1st fragment compressed
2898
                        // this fragment compressed
2899
                        $len = min($charsLeft, $limitpos - $pos);
2900
                        $retstr .= substr($recordData, $pos, $len);
2901
                        $charsLeft -= $len;
2902
                        $isCompressed = true;
2903
                    } elseif (!$isCompressed && ($option != 0)) {
2904
                        // 1st fragment uncompressed
2905
                        // this fragment uncompressed
2906
                        $len = min($charsLeft * 2, $limitpos - $pos);
2907
                        $retstr .= substr($recordData, $pos, $len);
2908
                        $charsLeft -= $len / 2;
2909
                        $isCompressed = false;
2910
                    } elseif (!$isCompressed && ($option == 0)) {
2911
                        // 1st fragment uncompressed
2912
                        // this fragment compressed
2913
                        $len = min($charsLeft, $limitpos - $pos);
2914
                        for ($j = 0; $j < $len; ++$j) {
2915
                            $retstr .= $recordData{$pos + $j}
2916
                            . chr(0);
2917
                        }
2918
                        $charsLeft -= $len;
2919
                        $isCompressed = false;
2920
                    } else {
2921
                        // 1st fragment compressed
2922
                        // this fragment uncompressed
2923
                        $newstr = '';
2924
                        for ($j = 0; $j < strlen($retstr); ++$j) {
2925
                            $newstr .= $retstr[$j] . chr(0);
2926
                        }
2927
                        $retstr = $newstr;
2928
                        $len = min($charsLeft * 2, $limitpos - $pos);
2929
                        $retstr .= substr($recordData, $pos, $len);
2930
                        $charsLeft -= $len / 2;
2931
                        $isCompressed = false;
2932
                    }
2933
2934
                    $pos += $len;
2935
                }
2936
            }
2937
2938
            // convert to UTF-8
2939 4
            $retstr = self::encodeUTF16($retstr, $isCompressed);
2940
2941
            // read additional Rich-Text information, if any
2942 4
            $fmtRuns = [];
2943 4
            if ($hasRichText) {
2944
                // list of formatting runs
2945 2
                for ($j = 0; $j < $formattingRuns; ++$j) {
0 ignored issues
show
Bug introduced by
The variable $formattingRuns does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2946
                    // first formatted character; zero-based
2947 2
                    $charPos = self::getInt2d($recordData, $pos + $j * 4);
2948
2949
                    // index to font record
2950 2
                    $fontIndex = self::getInt2d($recordData, $pos + 2 + $j * 4);
2951
2952 2
                    $fmtRuns[] = [
2953 2
                        'charPos' => $charPos,
2954 2
                        'fontIndex' => $fontIndex,
2955
                    ];
2956
                }
2957 2
                $pos += 4 * $formattingRuns;
2958
            }
2959
2960
            // read additional Asian phonetics information, if any
2961 4
            if ($hasAsian) {
2962
                // For Asian phonetic settings, we skip the extended string data
2963
                $pos += $extendedRunLength;
0 ignored issues
show
Bug introduced by
The variable $extendedRunLength does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2964
            }
2965
2966
            // store the shared sting
2967 4
            $this->sst[] = [
2968 4
                'value' => $retstr,
2969 4
                'fmtRuns' => $fmtRuns,
2970
            ];
2971
        }
2972
2973
        // getSplicedRecordData() takes care of moving current position in data stream
2974 4
    }
2975
2976
    /**
2977
     * Read PRINTGRIDLINES record
2978
     */
2979 4
    private function readPrintGridlines()
2980
    {
2981 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2982 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2983
2984
        // move stream pointer to next record
2985 4
        $this->pos += 4 + $length;
2986
2987 4
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
2988
            // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
2989 4
            $printGridlines = (bool) self::getInt2d($recordData, 0);
2990 4
            $this->phpSheet->setPrintGridlines($printGridlines);
2991
        }
2992 4
    }
2993
2994
    /**
2995
     * Read DEFAULTROWHEIGHT record
2996
     */
2997 3 View Code Duplication
    private function readDefaultRowHeight()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2998
    {
2999 3
        $length = self::getInt2d($this->data, $this->pos + 2);
3000 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3001
3002
        // move stream pointer to next record
3003 3
        $this->pos += 4 + $length;
3004
3005
        // offset: 0; size: 2; option flags
3006
        // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3007 3
        $height = self::getInt2d($recordData, 2);
3008 3
        $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3009 3
    }
3010
3011
    /**
3012
     * Read SHEETPR record
3013
     */
3014 4
    private function readSheetPr()
3015
    {
3016 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3017 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3018
3019
        // move stream pointer to next record
3020 4
        $this->pos += 4 + $length;
3021
3022
        // offset: 0; size: 2
3023
3024
        // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
3025 4
        $isSummaryBelow = (0x0040 & self::getInt2d($recordData, 0)) >> 6;
3026 4
        $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
3027
3028
        // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
3029 4
        $isSummaryRight = (0x0080 & self::getInt2d($recordData, 0)) >> 7;
3030 4
        $this->phpSheet->setShowSummaryRight($isSummaryRight);
3031
3032
        // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
3033
        // this corresponds to radio button setting in page setup dialog in Excel
3034 4
        $this->isFitToPages = (bool) ((0x0100 & self::getInt2d($recordData, 0)) >> 8);
3035 4
    }
3036
3037
    /**
3038
     * Read HORIZONTALPAGEBREAKS record
3039
     */
3040 View Code Duplication
    private function readHorizontalPageBreaks()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3041
    {
3042
        $length = self::getInt2d($this->data, $this->pos + 2);
3043
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3044
3045
        // move stream pointer to next record
3046
        $this->pos += 4 + $length;
3047
3048
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3049
            // offset: 0; size: 2; number of the following row index structures
3050
            $nm = self::getInt2d($recordData, 0);
3051
3052
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3053
            for ($i = 0; $i < $nm; ++$i) {
3054
                $r = self::getInt2d($recordData, 2 + 6 * $i);
3055
                $cf = self::getInt2d($recordData, 2 + 6 * $i + 2);
3056
                $cl = self::getInt2d($recordData, 2 + 6 * $i + 4);
0 ignored issues
show
Unused Code introduced by
$cl is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3057
3058
                // not sure why two column indexes are necessary?
3059
                $this->phpSheet->setBreakByColumnAndRow($cf, $r, \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_ROW);
3060
            }
3061
        }
3062
    }
3063
3064
    /**
3065
     * Read VERTICALPAGEBREAKS record
3066
     */
3067 View Code Duplication
    private function readVerticalPageBreaks()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3068
    {
3069
        $length = self::getInt2d($this->data, $this->pos + 2);
3070
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3071
3072
        // move stream pointer to next record
3073
        $this->pos += 4 + $length;
3074
3075
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3076
            // offset: 0; size: 2; number of the following column index structures
3077
            $nm = self::getInt2d($recordData, 0);
3078
3079
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3080
            for ($i = 0; $i < $nm; ++$i) {
3081
                $c = self::getInt2d($recordData, 2 + 6 * $i);
3082
                $rf = self::getInt2d($recordData, 2 + 6 * $i + 2);
3083
                $rl = self::getInt2d($recordData, 2 + 6 * $i + 4);
0 ignored issues
show
Unused Code introduced by
$rl is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3084
3085
                // not sure why two row indexes are necessary?
3086
                $this->phpSheet->setBreakByColumnAndRow($c, $rf, \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_COLUMN);
3087
            }
3088
        }
3089
    }
3090
3091
    /**
3092
     * Read HEADER record
3093
     */
3094 4 View Code Duplication
    private function readHeader()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3095
    {
3096 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3097 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3098
3099
        // move stream pointer to next record
3100 4
        $this->pos += 4 + $length;
3101
3102 4
        if (!$this->readDataOnly) {
3103
            // offset: 0; size: var
3104
            // realized that $recordData can be empty even when record exists
3105 4
            if ($recordData) {
3106 3
                if ($this->version == self::XLS_BIFF8) {
3107 3
                    $string = self::readUnicodeStringLong($recordData);
3108
                } else {
3109
                    $string = $this->readByteStringShort($recordData);
3110
                }
3111
3112 3
                $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3113 3
                $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3114
            }
3115
        }
3116 4
    }
3117
3118
    /**
3119
     * Read FOOTER record
3120
     */
3121 4 View Code Duplication
    private function readFooter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3122
    {
3123 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3124 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3125
3126
        // move stream pointer to next record
3127 4
        $this->pos += 4 + $length;
3128
3129 4
        if (!$this->readDataOnly) {
3130
            // offset: 0; size: var
3131
            // realized that $recordData can be empty even when record exists
3132 4
            if ($recordData) {
3133 3
                if ($this->version == self::XLS_BIFF8) {
3134 3
                    $string = self::readUnicodeStringLong($recordData);
3135
                } else {
3136
                    $string = $this->readByteStringShort($recordData);
3137
                }
3138 3
                $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3139 3
                $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3140
            }
3141
        }
3142 4
    }
3143
3144
    /**
3145
     * Read HCENTER record
3146
     */
3147 4 View Code Duplication
    private function readHcenter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3148
    {
3149 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3150 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3151
3152
        // move stream pointer to next record
3153 4
        $this->pos += 4 + $length;
3154
3155 4
        if (!$this->readDataOnly) {
3156
            // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3157 4
            $isHorizontalCentered = (bool) self::getInt2d($recordData, 0);
3158
3159 4
            $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3160
        }
3161 4
    }
3162
3163
    /**
3164
     * Read VCENTER record
3165
     */
3166 4 View Code Duplication
    private function readVcenter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3167
    {
3168 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3169 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3170
3171
        // move stream pointer to next record
3172 4
        $this->pos += 4 + $length;
3173
3174 4
        if (!$this->readDataOnly) {
3175
            // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3176 4
            $isVerticalCentered = (bool) self::getInt2d($recordData, 0);
3177
3178 4
            $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3179
        }
3180 4
    }
3181
3182
    /**
3183
     * Read LEFTMARGIN record
3184
     */
3185 4
    private function readLeftMargin()
3186
    {
3187 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3188 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3189
3190
        // move stream pointer to next record
3191 4
        $this->pos += 4 + $length;
3192
3193 4
        if (!$this->readDataOnly) {
3194
            // offset: 0; size: 8
3195 4
            $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3196
        }
3197 4
    }
3198
3199
    /**
3200
     * Read RIGHTMARGIN record
3201
     */
3202 4
    private function readRightMargin()
3203
    {
3204 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3205 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3206
3207
        // move stream pointer to next record
3208 4
        $this->pos += 4 + $length;
3209
3210 4
        if (!$this->readDataOnly) {
3211
            // offset: 0; size: 8
3212 4
            $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3213
        }
3214 4
    }
3215
3216
    /**
3217
     * Read TOPMARGIN record
3218
     */
3219 4
    private function readTopMargin()
3220
    {
3221 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3222 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3223
3224
        // move stream pointer to next record
3225 4
        $this->pos += 4 + $length;
3226
3227 4
        if (!$this->readDataOnly) {
3228
            // offset: 0; size: 8
3229 4
            $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3230
        }
3231 4
    }
3232
3233
    /**
3234
     * Read BOTTOMMARGIN record
3235
     */
3236 4
    private function readBottomMargin()
3237
    {
3238 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3239 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3240
3241
        // move stream pointer to next record
3242 4
        $this->pos += 4 + $length;
3243
3244 4
        if (!$this->readDataOnly) {
3245
            // offset: 0; size: 8
3246 4
            $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3247
        }
3248 4
    }
3249
3250
    /**
3251
     * Read PAGESETUP record
3252
     */
3253 4
    private function readPageSetup()
3254
    {
3255 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3256 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3257
3258
        // move stream pointer to next record
3259 4
        $this->pos += 4 + $length;
3260
3261 4
        if (!$this->readDataOnly) {
3262
            // offset: 0; size: 2; paper size
3263 4
            $paperSize = self::getInt2d($recordData, 0);
3264
3265
            // offset: 2; size: 2; scaling factor
3266 4
            $scale = self::getInt2d($recordData, 2);
3267
3268
            // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3269 4
            $fitToWidth = self::getInt2d($recordData, 6);
3270
3271
            // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3272 4
            $fitToHeight = self::getInt2d($recordData, 8);
3273
3274
            // offset: 10; size: 2; option flags
3275
3276
            // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3277 4
            $isPortrait = (0x0002 & self::getInt2d($recordData, 10)) >> 1;
3278
3279
            // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3280
            // when this bit is set, do not use flags for those properties
3281 4
            $isNotInit = (0x0004 & self::getInt2d($recordData, 10)) >> 2;
3282
3283 4
            if (!$isNotInit) {
3284 4
                $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3285
                switch ($isPortrait) {
3286 4
                    case 0:
3287 2
                        $this->phpSheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE);
3288 2
                        break;
3289 4
                    case 1:
3290 4
                        $this->phpSheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT);
3291 4
                        break;
3292
                }
3293
3294 4
                $this->phpSheet->getPageSetup()->setScale($scale, false);
3295 4
                $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3296 4
                $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3297 4
                $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3298
            }
3299
3300
            // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3301 4
            $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3302 4
            $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3303
3304
            // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3305 4
            $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3306 4
            $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3307
        }
3308 4
    }
3309
3310
    /**
3311
     * PROTECT - Sheet protection (BIFF2 through BIFF8)
3312
     *   if this record is omitted, then it also means no sheet protection
3313
     */
3314 1 View Code Duplication
    private function readProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3315
    {
3316 1
        $length = self::getInt2d($this->data, $this->pos + 2);
3317 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3318
3319
        // move stream pointer to next record
3320 1
        $this->pos += 4 + $length;
3321
3322 1
        if ($this->readDataOnly) {
3323
            return;
3324
        }
3325
3326
        // offset: 0; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3327
3328
        // bit 0, mask 0x01; 1 = sheet is protected
3329 1
        $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3330 1
        $this->phpSheet->getProtection()->setSheet((bool) $bool);
3331 1
    }
3332
3333
    /**
3334
     * SCENPROTECT
3335
     */
3336 View Code Duplication
    private function readScenProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3337
    {
3338
        $length = self::getInt2d($this->data, $this->pos + 2);
3339
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3340
3341
        // move stream pointer to next record
3342
        $this->pos += 4 + $length;
3343
3344
        if ($this->readDataOnly) {
3345
            return;
3346
        }
3347
3348
        // offset: 0; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3349
3350
        // bit: 0, mask 0x01; 1 = scenarios are protected
3351
        $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3352
3353
        $this->phpSheet->getProtection()->setScenarios((bool) $bool);
3354
    }
3355
3356
    /**
3357
     * OBJECTPROTECT
3358
     */
3359 View Code Duplication
    private function readObjectProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3360
    {
3361
        $length = self::getInt2d($this->data, $this->pos + 2);
3362
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3363
3364
        // move stream pointer to next record
3365
        $this->pos += 4 + $length;
3366
3367
        if ($this->readDataOnly) {
3368
            return;
3369
        }
3370
3371
        // offset: 0; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3372
3373
        // bit: 0, mask 0x01; 1 = objects are protected
3374
        $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3375
3376
        $this->phpSheet->getProtection()->setObjects((bool) $bool);
3377
    }
3378
3379
    /**
3380
     * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8)
3381
     */
3382
    private function readPassword()
3383
    {
3384
        $length = self::getInt2d($this->data, $this->pos + 2);
3385
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3386
3387
        // move stream pointer to next record
3388
        $this->pos += 4 + $length;
3389
3390
        if (!$this->readDataOnly) {
3391
            // offset: 0; size: 2; 16-bit hash value of password
3392
            $password = strtoupper(dechex(self::getInt2d($recordData, 0))); // the hashed password
3393
            $this->phpSheet->getProtection()->setPassword($password, true);
3394
        }
3395
    }
3396
3397
    /**
3398
     * Read DEFCOLWIDTH record
3399
     */
3400 4 View Code Duplication
    private function readDefColWidth()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3401
    {
3402 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3403 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3404
3405
        // move stream pointer to next record
3406 4
        $this->pos += 4 + $length;
3407
3408
        // offset: 0; size: 2; default column width
3409 4
        $width = self::getInt2d($recordData, 0);
3410 4
        if ($width != 8) {
3411
            $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
3412
        }
3413 4
    }
3414
3415
    /**
3416
     * Read COLINFO record
3417
     */
3418 3
    private function readColInfo()
3419
    {
3420 3
        $length = self::getInt2d($this->data, $this->pos + 2);
3421 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3422
3423
        // move stream pointer to next record
3424 3
        $this->pos += 4 + $length;
3425
3426 3
        if (!$this->readDataOnly) {
3427
            // offset: 0; size: 2; index to first column in range
3428 3
            $fc = self::getInt2d($recordData, 0); // first column index
3429
3430
            // offset: 2; size: 2; index to last column in range
3431 3
            $lc = self::getInt2d($recordData, 2); // first column index
3432
3433
            // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3434 3
            $width = self::getInt2d($recordData, 4);
3435
3436
            // offset: 6; size: 2; index to XF record for default column formatting
3437 3
            $xfIndex = self::getInt2d($recordData, 6);
3438
3439
            // offset: 8; size: 2; option flags
3440
            // bit: 0; mask: 0x0001; 1= columns are hidden
3441 3
            $isHidden = (0x0001 & self::getInt2d($recordData, 8)) >> 0;
3442
3443
            // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3444 3
            $level = (0x0700 & self::getInt2d($recordData, 8)) >> 8;
3445
3446
            // bit: 12; mask: 0x1000; 1 = collapsed
3447 3
            $isCollapsed = (0x1000 & self::getInt2d($recordData, 8)) >> 12;
3448
3449
            // offset: 10; size: 2; not used
3450
3451 3
            for ($i = $fc; $i <= $lc; ++$i) {
3452 3
                if ($lc == 255 || $lc == 256) {
3453
                    $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3454
                    break;
3455
                }
3456 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3457 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3458 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3459 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3460 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3461
            }
3462
        }
3463 3
    }
3464
3465
    /**
3466
     * ROW
3467
     *
3468
     * This record contains the properties of a single row in a
3469
     * sheet. Rows and cells in a sheet are divided into blocks
3470
     * of 32 rows.
3471
     *
3472
     * --    "OpenOffice.org's Documentation of the Microsoft
3473
     *         Excel File Format"
3474
     */
3475 3
    private function readRow()
3476
    {
3477 3
        $length = self::getInt2d($this->data, $this->pos + 2);
3478 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3479
3480
        // move stream pointer to next record
3481 3
        $this->pos += 4 + $length;
3482
3483 3
        if (!$this->readDataOnly) {
3484
            // offset: 0; size: 2; index of this row
3485 3
            $r = self::getInt2d($recordData, 0);
3486
3487
            // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3488
3489
            // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3490
3491
            // offset: 6; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3492
3493
            // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3494 3
            $height = (0x7FFF & self::getInt2d($recordData, 6)) >> 0;
3495
3496
            // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3497 3
            $useDefaultHeight = (0x8000 & self::getInt2d($recordData, 6)) >> 15;
3498
3499 3
            if (!$useDefaultHeight) {
3500 3
                $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3501
            }
3502
3503
            // offset: 8; size: 2; not used
3504
3505
            // offset: 10; size: 2; not used in BIFF5-BIFF8
3506
3507
            // offset: 12; size: 4; option flags and default row formatting
3508
3509
            // bit: 2-0: mask: 0x00000007; outline level of the row
3510 3
            $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
3511 3
            $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3512
3513
            // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3514 3
            $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
3515 3
            $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3516
3517
            // bit: 5; mask: 0x00000020; 1 = row is hidden
3518 3
            $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
3519 3
            $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3520
3521
            // bit: 7; mask: 0x00000080; 1 = row has explicit format
3522 3
            $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
3523
3524
            // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3525 3
            $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
3526
3527 3
            if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
3528
                $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3529
            }
3530
        }
3531 3
    }
3532
3533
    /**
3534
     * Read RK record
3535
     * This record represents a cell that contains an RK value
3536
     * (encoded integer or floating-point value). If a
3537
     * floating-point value cannot be encoded to an RK value,
3538
     * a NUMBER record will be written. This record replaces the
3539
     * record INTEGER written in BIFF2.
3540
     *
3541
     * --    "OpenOffice.org's Documentation of the Microsoft
3542
     *         Excel File Format"
3543
     */
3544 1 View Code Duplication
    private function readRk()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3545
    {
3546 1
        $length = self::getInt2d($this->data, $this->pos + 2);
3547 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3548
3549
        // move stream pointer to next record
3550 1
        $this->pos += 4 + $length;
3551
3552
        // offset: 0; size: 2; index to row
3553 1
        $row = self::getInt2d($recordData, 0);
3554
3555
        // offset: 2; size: 2; index to column
3556 1
        $column = self::getInt2d($recordData, 2);
3557 1
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3558
3559
        // Read cell?
3560 1
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3561
            // offset: 4; size: 2; index to XF record
3562 1
            $xfIndex = self::getInt2d($recordData, 4);
3563
3564
            // offset: 6; size: 4; RK value
3565 1
            $rknum = self::getInt4d($recordData, 6);
3566 1
            $numValue = self::getIEEE754($rknum);
3567
3568 1
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3569 1
            if (!$this->readDataOnly) {
3570
                // add style information
3571 1
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3572
            }
3573
3574
            // add cell
3575 1
            $cell->setValueExplicit($numValue, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
3576
        }
3577 1
    }
3578
3579
    /**
3580
     * Read LABELSST record
3581
     * This record represents a cell that contains a string. It
3582
     * replaces the LABEL record and RSTRING record used in
3583
     * BIFF2-BIFF5.
3584
     *
3585
     * --    "OpenOffice.org's Documentation of the Microsoft
3586
     *         Excel File Format"
3587
     */
3588 4
    private function readLabelSst()
3589
    {
3590 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3591 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3592
3593
        // move stream pointer to next record
3594 4
        $this->pos += 4 + $length;
3595
3596
        // offset: 0; size: 2; index to row
3597 4
        $row = self::getInt2d($recordData, 0);
3598
3599
        // offset: 2; size: 2; index to column
3600 4
        $column = self::getInt2d($recordData, 2);
3601 4
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3602
3603 4
        $emptyCell = true;
3604
        // Read cell?
3605 4
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3606
            // offset: 4; size: 2; index to XF record
3607 4
            $xfIndex = self::getInt2d($recordData, 4);
3608
3609
            // offset: 6; size: 4; index to SST record
3610 4
            $index = self::getInt4d($recordData, 6);
3611
3612
            // add cell
3613 4
            if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
3614
                // then we should treat as rich text
3615 2
                $richText = new \PhpOffice\PhpSpreadsheet\RichText();
3616 2
                $charPos = 0;
3617 2
                $sstCount = count($this->sst[$index]['fmtRuns']);
3618 2
                for ($i = 0; $i <= $sstCount; ++$i) {
3619 2
                    if (isset($fmtRuns[$i])) {
3620 2
                        $text = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3621 2
                        $charPos = $fmtRuns[$i]['charPos'];
3622
                    } else {
3623 2
                        $text = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::substring($this->sst[$index]['value'], $charPos, \PhpOffice\PhpSpreadsheet\Shared\StringHelper::countCharacters($this->sst[$index]['value']));
3624
                    }
3625
3626 2
                    if (\PhpOffice\PhpSpreadsheet\Shared\StringHelper::countCharacters($text) > 0) {
3627 2
                        if ($i == 0) { // first text run, no style
3628 1
                            $richText->createText($text);
3629
                        } else {
3630 2
                            $textRun = $richText->createTextRun($text);
3631 2
                            if (isset($fmtRuns[$i - 1])) {
3632 2
                                if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3633 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3634
                                } else {
3635
                                    // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3636
                                    // check the OpenOffice documentation of the FONT record
3637 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3638
                                }
3639 2
                                $textRun->setFont(clone $this->objFonts[$fontIndex]);
3640
                            }
3641
                        }
3642
                    }
3643
                }
3644 2
                if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
3645 2
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3646 2
                    $cell->setValueExplicit($richText, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
3647 2
                    $emptyCell = false;
3648
                }
3649
            } else {
3650 4
                if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
3651 4
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3652 4
                    $cell->setValueExplicit($this->sst[$index]['value'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
3653 4
                    $emptyCell = false;
3654
                }
3655
            }
3656
3657 4
            if (!$this->readDataOnly && !$emptyCell) {
3658
                // add style information
3659 4
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
0 ignored issues
show
Bug introduced by
The variable $cell does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3660
            }
3661
        }
3662 4
    }
3663
3664
    /**
3665
     * Read MULRK record
3666
     * This record represents a cell range containing RK value
3667
     * cells. All cells are located in the same row.
3668
     *
3669
     * --    "OpenOffice.org's Documentation of the Microsoft
3670
     *         Excel File Format"
3671
     */
3672
    private function readMulRk()
3673
    {
3674
        $length = self::getInt2d($this->data, $this->pos + 2);
3675
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3676
3677
        // move stream pointer to next record
3678
        $this->pos += 4 + $length;
3679
3680
        // offset: 0; size: 2; index to row
3681
        $row = self::getInt2d($recordData, 0);
3682
3683
        // offset: 2; size: 2; index to first column
3684
        $colFirst = self::getInt2d($recordData, 2);
3685
3686
        // offset: var; size: 2; index to last column
3687
        $colLast = self::getInt2d($recordData, $length - 2);
3688
        $columns = $colLast - $colFirst + 1;
3689
3690
        // offset within record data
3691
        $offset = 4;
3692
3693
        for ($i = 0; $i < $columns; ++$i) {
3694
            $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($colFirst + $i);
3695
3696
            // Read cell?
3697
            if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3698
                // offset: var; size: 2; index to XF record
3699
                $xfIndex = self::getInt2d($recordData, $offset);
3700
3701
                // offset: var; size: 4; RK value
3702
                $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
3703
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3704
                if (!$this->readDataOnly) {
3705
                    // add style
3706
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3707
                }
3708
3709
                // add cell value
3710
                $cell->setValueExplicit($numValue, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
3711
            }
3712
3713
            $offset += 6;
3714
        }
3715
    }
3716
3717
    /**
3718
     * Read NUMBER record
3719
     * This record represents a cell that contains a
3720
     * floating-point value.
3721
     *
3722
     * --    "OpenOffice.org's Documentation of the Microsoft
3723
     *         Excel File Format"
3724
     */
3725 1 View Code Duplication
    private function readNumber()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3726
    {
3727 1
        $length = self::getInt2d($this->data, $this->pos + 2);
3728 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3729
3730
        // move stream pointer to next record
3731 1
        $this->pos += 4 + $length;
3732
3733
        // offset: 0; size: 2; index to row
3734 1
        $row = self::getInt2d($recordData, 0);
3735
3736
        // offset: 2; size 2; index to column
3737 1
        $column = self::getInt2d($recordData, 2);
3738 1
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3739
3740
        // Read cell?
3741 1
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3742
            // offset 4; size: 2; index to XF record
3743 1
            $xfIndex = self::getInt2d($recordData, 4);
3744
3745 1
            $numValue = self::extractNumber(substr($recordData, 6, 8));
3746
3747 1
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3748 1
            if (!$this->readDataOnly) {
3749
                // add cell style
3750 1
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3751
            }
3752
3753
            // add cell value
3754 1
            $cell->setValueExplicit($numValue, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
3755
        }
3756 1
    }
3757
3758
    /**
3759
     * Read FORMULA record + perhaps a following STRING record if formula result is a string
3760
     * This record contains the token array and the result of a
3761
     * formula cell.
3762
     *
3763
     * --    "OpenOffice.org's Documentation of the Microsoft
3764
     *         Excel File Format"
3765
     */
3766 2
    private function readFormula()
3767
    {
3768 2
        $length = self::getInt2d($this->data, $this->pos + 2);
3769 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3770
3771
        // move stream pointer to next record
3772 2
        $this->pos += 4 + $length;
3773
3774
        // offset: 0; size: 2; row index
3775 2
        $row = self::getInt2d($recordData, 0);
3776
3777
        // offset: 2; size: 2; col index
3778 2
        $column = self::getInt2d($recordData, 2);
3779 2
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3780
3781
        // offset: 20: size: variable; formula structure
3782 2
        $formulaStructure = substr($recordData, 20);
3783
3784
        // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3785 2
        $options = self::getInt2d($recordData, 14);
3786
3787
        // bit: 0; mask: 0x0001; 1 = recalculate always
3788
        // bit: 1; mask: 0x0002; 1 = calculate on open
3789
        // bit: 2; mask: 0x0008; 1 = part of a shared formula
3790 2
        $isPartOfSharedFormula = (bool) (0x0008 & $options);
3791
3792
        // WARNING:
3793
        // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3794
        // the formula data may be ordinary formula data, therefore we need to check
3795
        // explicitly for the tExp token (0x01)
3796 2
        $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure{2}) == 0x01;
3797
3798 2
        if ($isPartOfSharedFormula) {
3799
            // part of shared formula which means there will be a formula with a tExp token and nothing else
3800
            // get the base cell, grab tExp token
3801
            $baseRow = self::getInt2d($formulaStructure, 3);
3802
            $baseCol = self::getInt2d($formulaStructure, 5);
3803
            $this->_baseCell = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol) . ($baseRow + 1);
0 ignored issues
show
Bug introduced by
The property _baseCell does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
3804
        }
3805
3806
        // Read cell?
3807 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3808 2
            if ($isPartOfSharedFormula) {
3809
                // formula is added to this cell after the sheet has been read
3810
                $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->_baseCell;
3811
            }
3812
3813
            // offset: 16: size: 4; not used
3814
3815
            // offset: 4; size: 2; XF index
3816 2
            $xfIndex = self::getInt2d($recordData, 4);
3817
3818
            // offset: 6; size: 8; result of the formula
3819 2
            if ((ord($recordData{6}) == 0) && (ord($recordData{12}) == 255) && (ord($recordData{13}) == 255)) {
3820
                // String formula. Result follows in appended STRING record
3821
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING;
3822
3823
                // read possible SHAREDFMLA record
3824
                $code = self::getInt2d($this->data, $this->pos);
3825
                if ($code == self::XLS_TYPE_SHAREDFMLA) {
3826
                    $this->readSharedFmla();
3827
                }
3828
3829
                // read STRING record
3830
                $value = $this->readString();
3831 2 View Code Duplication
            } elseif ((ord($recordData{6}) == 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3832 2
                && (ord($recordData{12}) == 255)
3833 2
                && (ord($recordData{13}) == 255)) {
3834
                // Boolean formula. Result is in +2; 0=false, 1=true
3835
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_BOOL;
3836
                $value = (bool) ord($recordData{8});
3837 2
            } elseif ((ord($recordData{6}) == 2)
3838 2
                && (ord($recordData{12}) == 255)
3839 2
                && (ord($recordData{13}) == 255)) {
3840
                // Error formula. Error code is in +2
3841
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_ERROR;
3842
                $value = Xls\ErrorCode::lookup(ord($recordData{8}));
3843 2 View Code Duplication
            } elseif ((ord($recordData{6}) == 3)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3844 2
                && (ord($recordData{12}) == 255)
3845 2
                && (ord($recordData{13}) == 255)) {
3846
                // Formula result is a null string
3847 1
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NULL;
3848 1
                $value = '';
3849
            } else {
3850
                // forumla result is a number, first 14 bytes like _NUMBER record
3851 2
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC;
3852 2
                $value = self::extractNumber(substr($recordData, 6, 8));
3853
            }
3854
3855 2
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3856 2
            if (!$this->readDataOnly) {
3857
                // add cell style
3858 2
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3859
            }
3860
3861
            // store the formula
3862 2
            if (!$isPartOfSharedFormula) {
3863
                // not part of shared formula
3864
                // add cell value. If we can read formula, populate with formula, otherwise just used cached value
3865
                try {
3866 2
                    if ($this->version != self::XLS_BIFF8) {
3867
                        throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
3868
                    }
3869 2
                    $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
3870 2
                    $cell->setValueExplicit('=' . $formula, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA);
3871
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
3872 2
                    $cell->setValueExplicit($value, $dataType);
3873
                }
3874
            } else {
3875
                if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
3876
                    // do nothing at this point, formula id added later in the code
3877
                } else {
3878
                    $cell->setValueExplicit($value, $dataType);
3879
                }
3880
            }
3881
3882
            // store the cached calculated value
3883 2
            $cell->setCalculatedValue($value);
3884
        }
3885 2
    }
3886
3887
    /**
3888
     * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
3889
     * which usually contains relative references.
3890
     * These will be used to construct the formula in each shared formula part after the sheet is read.
3891
     */
3892
    private function readSharedFmla()
3893
    {
3894
        $length = self::getInt2d($this->data, $this->pos + 2);
3895
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3896
3897
        // move stream pointer to next record
3898
        $this->pos += 4 + $length;
3899
3900
        // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
3901
        $cellRange = substr($recordData, 0, 6);
3902
        $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
0 ignored issues
show
Unused Code introduced by
$cellRange is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3903
3904
        // offset: 6, size: 1; not used
3905
3906
        // offset: 7, size: 1; number of existing FORMULA records for this shared formula
3907
        $no = ord($recordData{7});
0 ignored issues
show
Unused Code introduced by
$no is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3908
3909
        // offset: 8, size: var; Binary token array of the shared formula
3910
        $formula = substr($recordData, 8);
3911
3912
        // at this point we only store the shared formula for later use
3913
        $this->sharedFormulas[$this->_baseCell] = $formula;
3914
    }
3915
3916
    /**
3917
     * Read a STRING record from current stream position and advance the stream pointer to next record
3918
     * This record is used for storing result from FORMULA record when it is a string, and
3919
     * it occurs directly after the FORMULA record
3920
     *
3921
     * @return string The string contents as UTF-8
3922
     */
3923
    private function readString()
3924
    {
3925
        $length = self::getInt2d($this->data, $this->pos + 2);
3926
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3927
3928
        // move stream pointer to next record
3929
        $this->pos += 4 + $length;
3930
3931
        if ($this->version == self::XLS_BIFF8) {
3932
            $string = self::readUnicodeStringLong($recordData);
3933
            $value = $string['value'];
3934
        } else {
3935
            $string = $this->readByteStringLong($recordData);
3936
            $value = $string['value'];
3937
        }
3938
3939
        return $value;
3940
    }
3941
3942
    /**
3943
     * Read BOOLERR record
3944
     * This record represents a Boolean value or error value
3945
     * cell.
3946
     *
3947
     * --    "OpenOffice.org's Documentation of the Microsoft
3948
     *         Excel File Format"
3949
     */
3950
    private function readBoolErr()
3951
    {
3952
        $length = self::getInt2d($this->data, $this->pos + 2);
3953
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3954
3955
        // move stream pointer to next record
3956
        $this->pos += 4 + $length;
3957
3958
        // offset: 0; size: 2; row index
3959
        $row = self::getInt2d($recordData, 0);
3960
3961
        // offset: 2; size: 2; column index
3962
        $column = self::getInt2d($recordData, 2);
3963
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3964
3965
        // Read cell?
3966
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3967
            // offset: 4; size: 2; index to XF record
3968
            $xfIndex = self::getInt2d($recordData, 4);
3969
3970
            // offset: 6; size: 1; the boolean value or error value
3971
            $boolErr = ord($recordData{6});
3972
3973
            // offset: 7; size: 1; 0=boolean; 1=error
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3974
            $isError = ord($recordData{7});
3975
3976
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3977
            switch ($isError) {
3978
                case 0: // boolean
3979
                    $value = (bool) $boolErr;
3980
3981
                    // add cell value
3982
                    $cell->setValueExplicit($value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_BOOL);
3983
                    break;
3984
                case 1: // error type
3985
                    $value = Xls\ErrorCode::lookup($boolErr);
3986
3987
                    // add cell value
3988
                    $cell->setValueExplicit($value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_ERROR);
3989
                    break;
3990
            }
3991
3992
            if (!$this->readDataOnly) {
3993
                // add cell style
3994
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3995
            }
3996
        }
3997
    }
3998
3999
    /**
4000
     * Read MULBLANK record
4001
     * This record represents a cell range of empty cells. All
4002
     * cells are located in the same row
4003
     *
4004
     * --    "OpenOffice.org's Documentation of the Microsoft
4005
     *         Excel File Format"
4006
     */
4007 1
    private function readMulBlank()
4008
    {
4009 1
        $length = self::getInt2d($this->data, $this->pos + 2);
4010 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4011
4012
        // move stream pointer to next record
4013 1
        $this->pos += 4 + $length;
4014
4015
        // offset: 0; size: 2; index to row
4016 1
        $row = self::getInt2d($recordData, 0);
4017
4018
        // offset: 2; size: 2; index to first column
4019 1
        $fc = self::getInt2d($recordData, 2);
4020
4021
        // offset: 4; size: 2 x nc; list of indexes to XF records
4022
        // add style information
4023 1
        if (!$this->readDataOnly && $this->readEmptyCells) {
4024 1
            for ($i = 0; $i < $length / 2 - 3; ++$i) {
4025 1
                $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fc + $i);
4026
4027
                // Read cell?
4028 1
                if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4029 1
                    $xfIndex = self::getInt2d($recordData, 4 + 2 * $i);
4030 1
                    $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4031
                }
4032
            }
4033
        }
4034
4035
        // offset: 6; size 2; index to last column (not needed)
4036 1
    }
4037
4038
    /**
4039
     * Read LABEL record
4040
     * This record represents a cell that contains a string. In
4041
     * BIFF8 it is usually replaced by the LABELSST record.
4042
     * Excel still uses this record, if it copies unformatted
4043
     * text cells to the clipboard.
4044
     *
4045
     * --    "OpenOffice.org's Documentation of the Microsoft
4046
     *         Excel File Format"
4047
     */
4048
    private function readLabel()
4049
    {
4050
        $length = self::getInt2d($this->data, $this->pos + 2);
4051
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4052
4053
        // move stream pointer to next record
4054
        $this->pos += 4 + $length;
4055
4056
        // offset: 0; size: 2; index to row
4057
        $row = self::getInt2d($recordData, 0);
4058
4059
        // offset: 2; size: 2; index to column
4060
        $column = self::getInt2d($recordData, 2);
4061
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
4062
4063
        // Read cell?
4064
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4065
            // offset: 4; size: 2; XF index
4066
            $xfIndex = self::getInt2d($recordData, 4);
4067
4068
            // add cell value
4069
            // todo: what if string is very long? continue record
4070 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4071
                $string = self::readUnicodeStringLong(substr($recordData, 6));
4072
                $value = $string['value'];
4073
            } else {
4074
                $string = $this->readByteStringLong(substr($recordData, 6));
4075
                $value = $string['value'];
4076
            }
4077
            if ($this->readEmptyCells || trim($value) !== '') {
4078
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4079
                $cell->setValueExplicit($value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
4080
4081
                if (!$this->readDataOnly) {
4082
                    // add cell style
4083
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4084
                }
4085
            }
4086
        }
4087
    }
4088
4089
    /**
4090
     * Read BLANK record
4091
     */
4092 2
    private function readBlank()
4093
    {
4094 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4095 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4096
4097
        // move stream pointer to next record
4098 2
        $this->pos += 4 + $length;
4099
4100
        // offset: 0; size: 2; row index
4101 2
        $row = self::getInt2d($recordData, 0);
4102
4103
        // offset: 2; size: 2; col index
4104 2
        $col = self::getInt2d($recordData, 2);
4105 2
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($col);
4106
4107
        // Read cell?
4108 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4109
            // offset: 4; size: 2; XF index
4110 2
            $xfIndex = self::getInt2d($recordData, 4);
4111
4112
            // add style information
4113 2
            if (!$this->readDataOnly && $this->readEmptyCells) {
4114 2
                $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4115
            }
4116
        }
4117 2
    }
4118
4119
    /**
4120
     * Read MSODRAWING record
4121
     */
4122 2 View Code Duplication
    private function readMsoDrawing()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4123
    {
4124 2
        $length = self::getInt2d($this->data, $this->pos + 2);
0 ignored issues
show
Unused Code introduced by
$length is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4125
4126
        // get spliced record data
4127 2
        $splicedRecordData = $this->getSplicedRecordData();
4128 2
        $recordData = $splicedRecordData['recordData'];
4129
4130 2
        $this->drawingData .= $recordData;
4131 2
    }
4132
4133
    /**
4134
     * Read OBJ record
4135
     */
4136 2
    private function readObj()
4137
    {
4138 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4139 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4140
4141
        // move stream pointer to next record
4142 2
        $this->pos += 4 + $length;
4143
4144 2
        if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4145
            return;
4146
        }
4147
4148
        // recordData consists of an array of subrecords looking like this:
4149
        //    ft: 2 bytes; ftCmo type (0x15)
4150
        //    cb: 2 bytes; size in bytes of ftCmo data
4151
        //    ot: 2 bytes; Object Type
4152
        //    id: 2 bytes; Object id number
4153
        //    grbit: 2 bytes; Option Flags
4154
        //    data: var; subrecord data
4155
4156
        // for now, we are just interested in the second subrecord containing the object type
4157 2
        $ftCmoType = self::getInt2d($recordData, 0);
4158 2
        $cbCmoSize = self::getInt2d($recordData, 2);
4159 2
        $otObjType = self::getInt2d($recordData, 4);
4160 2
        $idObjID = self::getInt2d($recordData, 6);
4161 2
        $grbitOpts = self::getInt2d($recordData, 6);
4162
4163 2
        $this->objs[] = [
4164 2
            'ftCmoType' => $ftCmoType,
4165 2
            'cbCmoSize' => $cbCmoSize,
4166 2
            'otObjType' => $otObjType,
4167 2
            'idObjID' => $idObjID,
4168 2
            'grbitOpts' => $grbitOpts,
4169
        ];
4170 2
        $this->textObjRef = $idObjID;
4171 2
    }
4172
4173
    /**
4174
     * Read WINDOW2 record
4175
     */
4176 4
    private function readWindow2()
4177
    {
4178 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4179 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4180
4181
        // move stream pointer to next record
4182 4
        $this->pos += 4 + $length;
4183
4184
        // offset: 0; size: 2; option flags
4185 4
        $options = self::getInt2d($recordData, 0);
4186
4187
        // offset: 2; size: 2; index to first visible row
4188 4
        $firstVisibleRow = self::getInt2d($recordData, 2);
0 ignored issues
show
Unused Code introduced by
$firstVisibleRow is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4189
4190
        // offset: 4; size: 2; index to first visible colum
4191 4
        $firstVisibleColumn = self::getInt2d($recordData, 4);
0 ignored issues
show
Unused Code introduced by
$firstVisibleColumn is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4192 4
        if ($this->version === self::XLS_BIFF8) {
4193
            // offset:  8; size: 2; not used
4194
            // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4195
            // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4196
            // offset: 14; size: 4; not used
4197 4
            $zoomscaleInPageBreakPreview = self::getInt2d($recordData, 10);
4198 4
            if ($zoomscaleInPageBreakPreview === 0) {
4199 4
                $zoomscaleInPageBreakPreview = 60;
4200
            }
4201 4
            $zoomscaleInNormalView = self::getInt2d($recordData, 12);
4202 4
            if ($zoomscaleInNormalView === 0) {
4203 3
                $zoomscaleInNormalView = 100;
4204
            }
4205
        }
4206
4207
        // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4208 4
        $showGridlines = (bool) ((0x0002 & $options) >> 1);
4209 4
        $this->phpSheet->setShowGridlines($showGridlines);
4210
4211
        // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4212 4
        $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4213 4
        $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4214
4215
        // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4216 4
        $this->frozen = (bool) ((0x0008 & $options) >> 3);
4217
4218
        // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4219 4
        $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
4220
4221
        // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4222 4
        $isActive = (bool) ((0x0400 & $options) >> 10);
4223 4
        if ($isActive) {
4224 4
            $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
4225
        }
4226
4227
        // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4228 4
        $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4229
4230
        //FIXME: set $firstVisibleRow and $firstVisibleColumn
4231
4232 4
        if ($this->phpSheet->getSheetView()->getView() !== \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_PAGE_LAYOUT) {
4233
            //NOTE: this setting is inferior to page layout view(Excel2007-)
4234 4
            $view = $isPageBreakPreview ? \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_NORMAL;
4235 4
            $this->phpSheet->getSheetView()->setView($view);
4236 4
            if ($this->version === self::XLS_BIFF8) {
4237 4
                $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
0 ignored issues
show
Bug introduced by
The variable $zoomscaleInPageBreakPreview does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $zoomscaleInNormalView does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4238 4
                $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4239 4
                $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4240
            }
4241
        }
4242 4
    }
4243
4244
    /**
4245
     * Read PLV Record(Created by Excel2007 or upper)
4246
     */
4247 4
    private function readPageLayoutView()
4248
    {
4249 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4250 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4251
4252
        // move stream pointer to next record
4253 4
        $this->pos += 4 + $length;
4254
4255
        // offset: 0; size: 2; rt
4256
        //->ignore
4257 4
        $rt = self::getInt2d($recordData, 0);
0 ignored issues
show
Unused Code introduced by
$rt is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4258
        // offset: 2; size: 2; grbitfr
4259
        //->ignore
4260 4
        $grbitFrt = self::getInt2d($recordData, 2);
0 ignored issues
show
Unused Code introduced by
$grbitFrt is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4261
        // offset: 4; size: 8; reserved
4262
        //->ignore
4263
4264
        // offset: 12; size 2; zoom scale
4265 4
        $wScalePLV = self::getInt2d($recordData, 12);
4266
        // offset: 14; size 2; grbit
4267 4
        $grbit = self::getInt2d($recordData, 14);
4268
4269
        // decomprise grbit
4270 4
        $fPageLayoutView = $grbit & 0x01;
4271 4
        $fRulerVisible = ($grbit >> 1) & 0x01; //no support
0 ignored issues
show
Unused Code introduced by
$fRulerVisible is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4272 4
        $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
0 ignored issues
show
Unused Code introduced by
$fWhitespaceHidden is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4273
4274 4
        if ($fPageLayoutView === 1) {
4275
            $this->phpSheet->getSheetView()->setView(\PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_PAGE_LAYOUT);
4276
            $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4277
        }
4278
        //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4279 4
    }
4280
4281
    /**
4282
     * Read SCL record
4283
     */
4284
    private function readScl()
4285
    {
4286
        $length = self::getInt2d($this->data, $this->pos + 2);
4287
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4288
4289
        // move stream pointer to next record
4290
        $this->pos += 4 + $length;
4291
4292
        // offset: 0; size: 2; numerator of the view magnification
4293
        $numerator = self::getInt2d($recordData, 0);
4294
4295
        // offset: 2; size: 2; numerator of the view magnification
4296
        $denumerator = self::getInt2d($recordData, 2);
4297
4298
        // set the zoom scale (in percent)
4299
        $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4300
    }
4301
4302
    /**
4303
     * Read PANE record
4304
     */
4305
    private function readPane()
4306
    {
4307
        $length = self::getInt2d($this->data, $this->pos + 2);
4308
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4309
4310
        // move stream pointer to next record
4311
        $this->pos += 4 + $length;
4312
4313
        if (!$this->readDataOnly) {
4314
            // offset: 0; size: 2; position of vertical split
4315
            $px = self::getInt2d($recordData, 0);
4316
4317
            // offset: 2; size: 2; position of horizontal split
4318
            $py = self::getInt2d($recordData, 2);
4319
4320
            if ($this->frozen) {
4321
                // frozen panes
4322
                $this->phpSheet->freezePane(\PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($px) . ($py + 1));
4323
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
4324
                // unfrozen panes; split windows; not supported by PhpSpreadsheet core
4325
            }
4326
        }
4327
    }
4328
4329
    /**
4330
     * Read SELECTION record. There is one such record for each pane in the sheet.
4331
     */
4332 4
    private function readSelection()
4333
    {
4334 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4335 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4336
4337
        // move stream pointer to next record
4338 4
        $this->pos += 4 + $length;
4339
4340 4
        if (!$this->readDataOnly) {
4341
            // offset: 0; size: 1; pane identifier
4342 4
            $paneId = ord($recordData{0});
0 ignored issues
show
Unused Code introduced by
$paneId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4343
4344
            // offset: 1; size: 2; index to row of the active cell
4345 4
            $r = self::getInt2d($recordData, 1);
0 ignored issues
show
Unused Code introduced by
$r is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4346
4347
            // offset: 3; size: 2; index to column of the active cell
4348 4
            $c = self::getInt2d($recordData, 3);
0 ignored issues
show
Unused Code introduced by
$c is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4349
4350
            // offset: 5; size: 2; index into the following cell range list to the
4351
            //  entry that contains the active cell
4352 4
            $index = self::getInt2d($recordData, 5);
0 ignored issues
show
Unused Code introduced by
$index is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4353
4354
            // offset: 7; size: var; cell range address list containing all selected cell ranges
4355 4
            $data = substr($recordData, 7);
4356 4
            $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4357
4358 4
            $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4359
4360
            // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4361 4
            if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4362
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4363
            }
4364
4365
            // first row '1' + last row '65536' indicates that full column is selected
4366 4
            if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4367
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4368
            }
4369
4370
            // first column 'A' + last column 'IV' indicates that full row is selected
4371 4
            if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4372
                $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '${1}XFD${2}', $selectedCells);
4373
            }
4374
4375 4
            $this->phpSheet->setSelectedCells($selectedCells);
4376
        }
4377 4
    }
4378
4379 2
    private function includeCellRangeFiltered($cellRangeAddress)
4380
    {
4381 2
        $includeCellRange = true;
4382 2
        if ($this->getReadFilter() !== null) {
4383 2
            $includeCellRange = false;
4384 2
            $rangeBoundaries = \PhpOffice\PhpSpreadsheet\Cell::getRangeBoundaries($cellRangeAddress);
4385 2
            ++$rangeBoundaries[1][0];
4386 2
            for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
4387 2
                for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
4388 2
                    if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4389 2
                        $includeCellRange = true;
4390 2
                        break 2;
4391
                    }
4392
                }
4393
            }
4394
        }
4395
4396 2
        return $includeCellRange;
4397
    }
4398
4399
    /**
4400
     * MERGEDCELLS
4401
     *
4402
     * This record contains the addresses of merged cell ranges
4403
     * in the current sheet.
4404
     *
4405
     * --    "OpenOffice.org's Documentation of the Microsoft
4406
     *         Excel File Format"
4407
     */
4408 2
    private function readMergedCells()
4409
    {
4410 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4411 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4412
4413
        // move stream pointer to next record
4414 2
        $this->pos += 4 + $length;
4415
4416 2
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4417 2
            $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4418 2
            foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4419 2
                if ((strpos($cellRangeAddress, ':') !== false) &&
4420 2
                    ($this->includeCellRangeFiltered($cellRangeAddress))) {
4421 2
                    $this->phpSheet->mergeCells($cellRangeAddress);
4422
                }
4423
            }
4424
        }
4425 2
    }
4426
4427
    /**
4428
     * Read HYPERLINK record
4429
     */
4430 2
    private function readHyperLink()
4431
    {
4432 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4433 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4434
4435
        // move stream pointer forward to next record
4436 2
        $this->pos += 4 + $length;
4437
4438 2
        if (!$this->readDataOnly) {
4439
            // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4440
            try {
4441 2
                $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData, 0, 8);
0 ignored issues
show
Unused Code introduced by
The call to Xls::readBIFF8CellRangeAddressFixed() has too many arguments starting with 0.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
4442
            } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4443
                return;
4444
            }
4445
4446
            // offset: 8, size: 16; GUID of StdLink
4447
4448
            // offset: 24, size: 4; unknown value
4449
4450
            // offset: 28, size: 4; option flags
4451
            // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4452 2
            $isFileLinkOrUrl = (0x00000001 & self::getInt2d($recordData, 28)) >> 0;
4453
4454
            // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4455 2
            $isAbsPathOrUrl = (0x00000001 & self::getInt2d($recordData, 28)) >> 1;
0 ignored issues
show
Unused Code introduced by
$isAbsPathOrUrl is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4456
4457
            // bit: 2 (and 4); mask: 0x00000014; 0 = no description
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4458 2
            $hasDesc = (0x00000014 & self::getInt2d($recordData, 28)) >> 2;
4459
4460
            // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4461 2
            $hasText = (0x00000008 & self::getInt2d($recordData, 28)) >> 3;
4462
4463
            // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4464 2
            $hasFrame = (0x00000080 & self::getInt2d($recordData, 28)) >> 7;
4465
4466
            // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4467 2
            $isUNC = (0x00000100 & self::getInt2d($recordData, 28)) >> 8;
4468
4469
            // offset within record data
4470 2
            $offset = 32;
4471
4472 2
            if ($hasDesc) {
4473
                // offset: 32; size: var; character count of description text
4474 1
                $dl = self::getInt4d($recordData, 32);
4475
                // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4476 1
                $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
0 ignored issues
show
Unused Code introduced by
$desc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4477 1
                $offset += 4 + 2 * $dl;
4478
            }
4479 2
            if ($hasFrame) {
4480
                $fl = self::getInt4d($recordData, $offset);
4481
                $offset += 4 + 2 * $fl;
4482
            }
4483
4484
            // detect type of hyperlink (there are 4 types)
4485 2
            $hyperlinkType = null;
4486
4487 2
            if ($isUNC) {
4488
                $hyperlinkType = 'UNC';
4489 2
            } elseif (!$isFileLinkOrUrl) {
4490 2
                $hyperlinkType = 'workbook';
4491 2
            } elseif (ord($recordData{$offset}) == 0x03) {
4492
                $hyperlinkType = 'local';
4493 2
            } elseif (ord($recordData{$offset}) == 0xE0) {
4494 2
                $hyperlinkType = 'URL';
4495
            }
4496
4497
            switch ($hyperlinkType) {
4498 2
                case 'URL':
4499
                    // section 5.58.2: Hyperlink containing a URL
4500
                    // e.g. http://example.org/index.php
4501
4502
                    // offset: var; size: 16; GUID of URL Moniker
4503 2
                    $offset += 16;
4504
                    // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4505 2
                    $us = self::getInt4d($recordData, $offset);
4506 2
                    $offset += 4;
4507
                    // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4508 2
                    $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4509 2
                    $nullOffset = strpos($url, 0x00);
4510 2
                    if ($nullOffset) {
4511 1
                        $url = substr($url, 0, $nullOffset);
4512
                    }
4513 2
                    $url .= $hasText ? '#' : '';
4514 2
                    $offset += $us;
4515 2
                    break;
4516 2
                case 'local':
4517
                    // section 5.58.3: Hyperlink to local file
4518
                    // examples:
4519
                    //   mydoc.txt
4520
                    //   ../../somedoc.xls#Sheet!A1
4521
4522
                    // offset: var; size: 16; GUI of File Moniker
4523
                    $offset += 16;
4524
4525
                    // offset: var; size: 2; directory up-level count.
4526
                    $upLevelCount = self::getInt2d($recordData, $offset);
4527
                    $offset += 2;
4528
4529
                    // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4530
                    $sl = self::getInt4d($recordData, $offset);
4531
                    $offset += 4;
4532
4533
                    // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4534
                    $shortenedFilePath = substr($recordData, $offset, $sl);
4535
                    $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4536
                    $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4537
4538
                    $offset += $sl;
4539
4540
                    // offset: var; size: 24; unknown sequence
4541
                    $offset += 24;
4542
4543
                    // extended file path
4544
                    // offset: var; size: 4; size of the following file link field including string lenth mark
4545
                    $sz = self::getInt4d($recordData, $offset);
4546
                    $offset += 4;
4547
4548
                    // only present if $sz > 0
4549
                    if ($sz > 0) {
4550
                        // offset: var; size: 4; size of the character array of the extended file path and name
4551
                        $xl = self::getInt4d($recordData, $offset);
4552
                        $offset += 4;
4553
4554
                        // offset: var; size 2; unknown
4555
                        $offset += 2;
4556
4557
                        // offset: var; size $xl; character array of the extended file path and name.
4558
                        $extendedFilePath = substr($recordData, $offset, $xl);
4559
                        $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4560
                        $offset += $xl;
4561
                    }
4562
4563
                    // construct the path
4564
                    $url = str_repeat('..\\', $upLevelCount);
4565
                    $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
0 ignored issues
show
Bug introduced by
The variable $extendedFilePath does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4566
                    $url .= $hasText ? '#' : '';
4567
4568
                    break;
4569 2
                case 'UNC':
4570
                    // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4571
                    // todo: implement
4572
                    return;
4573 2
                case 'workbook':
4574
                    // section 5.58.5: Hyperlink to the Current Workbook
4575
                    // e.g. Sheet2!B1:C2, stored in text mark field
4576 2
                    $url = 'sheet://';
4577 2
                    break;
4578
                default:
4579
                    return;
4580
            }
4581
4582 2
            if ($hasText) {
4583
                // offset: var; size: 4; character count of text mark including trailing zero word
4584 2
                $tl = self::getInt4d($recordData, $offset);
4585 2
                $offset += 4;
4586
                // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4587 2
                $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4588 2
                $url .= $text;
4589
            }
4590
4591
            // apply the hyperlink to all the relevant cells
4592 2
            foreach (\PhpOffice\PhpSpreadsheet\Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4593 2
                $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4594
            }
4595
        }
4596 2
    }
4597
4598
    /**
4599
     * Read DATAVALIDATIONS record
4600
     */
4601
    private function readDataValidations()
4602
    {
4603
        $length = self::getInt2d($this->data, $this->pos + 2);
4604
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
0 ignored issues
show
Unused Code introduced by
$recordData is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4605
4606
        // move stream pointer forward to next record
4607
        $this->pos += 4 + $length;
4608
    }
4609
4610
    /**
4611
     * Read DATAVALIDATION record
4612
     */
4613
    private function readDataValidation()
4614
    {
4615
        $length = self::getInt2d($this->data, $this->pos + 2);
4616
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4617
4618
        // move stream pointer forward to next record
4619
        $this->pos += 4 + $length;
4620
4621
        if ($this->readDataOnly) {
4622
            return;
4623
        }
4624
4625
        // offset: 0; size: 4; Options
4626
        $options = self::getInt4d($recordData, 0);
4627
4628
        // bit: 0-3; mask: 0x0000000F; type
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4629
        $type = (0x0000000F & $options) >> 0;
4630 View Code Duplication
        switch ($type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4631
            case 0x00:
4632
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_NONE;
4633
                break;
4634
            case 0x01:
4635
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_WHOLE;
4636
                break;
4637
            case 0x02:
4638
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_DECIMAL;
4639
                break;
4640
            case 0x03:
4641
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST;
4642
                break;
4643
            case 0x04:
4644
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_DATE;
4645
                break;
4646
            case 0x05:
4647
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_TIME;
4648
                break;
4649
            case 0x06:
4650
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_TEXTLENGTH;
4651
                break;
4652
            case 0x07:
4653
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_CUSTOM;
4654
                break;
4655
        }
4656
4657
        // bit: 4-6; mask: 0x00000070; error type
4658
        $errorStyle = (0x00000070 & $options) >> 4;
4659 View Code Duplication
        switch ($errorStyle) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4660
            case 0x00:
4661
                $errorStyle = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_STOP;
4662
                break;
4663
            case 0x01:
4664
                $errorStyle = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_WARNING;
4665
                break;
4666
            case 0x02:
4667
                $errorStyle = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_INFORMATION;
4668
                break;
4669
        }
4670
4671
        // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4672
        // I have only seen cases where this is 1
4673
        $explicitFormula = (0x00000080 & $options) >> 7;
0 ignored issues
show
Unused Code introduced by
$explicitFormula is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4674
4675
        // bit: 8; mask: 0x00000100; 1= empty cells allowed
4676
        $allowBlank = (0x00000100 & $options) >> 8;
4677
4678
        // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4679
        $suppressDropDown = (0x00000200 & $options) >> 9;
4680
4681
        // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4682
        $showInputMessage = (0x00040000 & $options) >> 18;
4683
4684
        // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4685
        $showErrorMessage = (0x00080000 & $options) >> 19;
4686
4687
        // bit: 20-23; mask: 0x00F00000; condition operator
4688
        $operator = (0x00F00000 & $options) >> 20;
4689 View Code Duplication
        switch ($operator) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4690
            case 0x00:
4691
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_BETWEEN;
4692
                break;
4693
            case 0x01:
4694
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_NOTBETWEEN;
4695
                break;
4696
            case 0x02:
4697
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_EQUAL;
4698
                break;
4699
            case 0x03:
4700
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_NOTEQUAL;
4701
                break;
4702
            case 0x04:
4703
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_GREATERTHAN;
4704
                break;
4705
            case 0x05:
4706
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_LESSTHAN;
4707
                break;
4708
            case 0x06:
4709
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_GREATERTHANOREQUAL;
4710
                break;
4711
            case 0x07:
4712
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_LESSTHANOREQUAL;
4713
                break;
4714
        }
4715
4716
        // offset: 4; size: var; title of the prompt box
4717
        $offset = 4;
4718
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4719
        $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4720
        $offset += $string['size'];
4721
4722
        // offset: var; size: var; title of the error box
4723
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4724
        $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4725
        $offset += $string['size'];
4726
4727
        // offset: var; size: var; text of the prompt box
4728
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4729
        $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4730
        $offset += $string['size'];
4731
4732
        // offset: var; size: var; text of the error box
4733
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4734
        $error = $string['value'] !== chr(0) ? $string['value'] : '';
4735
        $offset += $string['size'];
4736
4737
        // offset: var; size: 2; size of the formula data for the first condition
4738
        $sz1 = self::getInt2d($recordData, $offset);
4739
        $offset += 2;
4740
4741
        // offset: var; size: 2; not used
4742
        $offset += 2;
4743
4744
        // offset: var; size: $sz1; formula data for first condition (without size field)
4745
        $formula1 = substr($recordData, $offset, $sz1);
4746
        $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4747
        try {
4748
            $formula1 = $this->getFormulaFromStructure($formula1);
4749
4750
            // in list type validity, null characters are used as item separators
4751
            if ($type == \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST) {
4752
                $formula1 = str_replace(chr(0), ',', $formula1);
4753
            }
4754
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4755
            return;
4756
        }
4757
        $offset += $sz1;
4758
4759
        // offset: var; size: 2; size of the formula data for the first condition
4760
        $sz2 = self::getInt2d($recordData, $offset);
4761
        $offset += 2;
4762
4763
        // offset: var; size: 2; not used
4764
        $offset += 2;
4765
4766
        // offset: var; size: $sz2; formula data for second condition (without size field)
4767
        $formula2 = substr($recordData, $offset, $sz2);
4768
        $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4769
        try {
4770
            $formula2 = $this->getFormulaFromStructure($formula2);
4771
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4772
            return;
4773
        }
4774
        $offset += $sz2;
4775
4776
        // offset: var; size: var; cell range address list with
4777
        $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4778
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4779
4780
        foreach ($cellRangeAddresses as $cellRange) {
4781
            $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4782
            foreach (\PhpOffice\PhpSpreadsheet\Cell::extractAllCellReferencesInRange($stRange) as $coordinate) {
4783
                $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4784
                $objValidation->setType($type);
4785
                $objValidation->setErrorStyle($errorStyle);
4786
                $objValidation->setAllowBlank((bool) $allowBlank);
4787
                $objValidation->setShowInputMessage((bool) $showInputMessage);
4788
                $objValidation->setShowErrorMessage((bool) $showErrorMessage);
4789
                $objValidation->setShowDropDown(!$suppressDropDown);
4790
                $objValidation->setOperator($operator);
4791
                $objValidation->setErrorTitle($errorTitle);
4792
                $objValidation->setError($error);
4793
                $objValidation->setPromptTitle($promptTitle);
4794
                $objValidation->setPrompt($prompt);
4795
                $objValidation->setFormula1($formula1);
4796
                $objValidation->setFormula2($formula2);
4797
            }
4798
        }
4799
    }
4800
4801
    /**
4802
     * Read SHEETLAYOUT record. Stores sheet tab color information.
4803
     */
4804 2
    private function readSheetLayout()
4805
    {
4806 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4807 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4808
4809
        // move stream pointer to next record
4810 2
        $this->pos += 4 + $length;
4811
4812
        // local pointer in record data
4813 2
        $offset = 0;
0 ignored issues
show
Unused Code introduced by
$offset is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4814
4815 2
        if (!$this->readDataOnly) {
4816
            // offset: 0; size: 2; repeated record identifier 0x0862
4817
4818
            // offset: 2; size: 10; not used
4819
4820
            // offset: 12; size: 4; size of record data
4821
            // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
4822 2
            $sz = self::getInt4d($recordData, 12);
4823
4824
            switch ($sz) {
4825 2
                case 0x14:
4826
                    // offset: 16; size: 2; color index for sheet tab
4827 1
                    $colorIndex = self::getInt2d($recordData, 16);
4828 1
                    $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
4829 1
                    $this->phpSheet->getTabColor()->setRGB($color['rgb']);
4830 1
                    break;
4831 1
                case 0x28:
4832
                    // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
4833 1
                    return;
4834
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
4835
            }
4836
        }
4837 1
    }
4838
4839
    /**
4840
     * Read SHEETPROTECTION record (FEATHEADR)
4841
     */
4842 4
    private function readSheetProtection()
4843
    {
4844 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4845 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4846
4847
        // move stream pointer to next record
4848 4
        $this->pos += 4 + $length;
4849
4850 4
        if ($this->readDataOnly) {
4851
            return;
4852
        }
4853
4854
        // offset: 0; size: 2; repeated record header
4855
4856
        // offset: 2; size: 2; FRT cell reference flag (=0 currently)
4857
4858
        // offset: 4; size: 8; Currently not used and set to 0
4859
4860
        // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
4861 4
        $isf = self::getInt2d($recordData, 12);
4862 4
        if ($isf != 2) {
4863
            return;
4864
        }
4865
4866
        // offset: 14; size: 1; =1 since this is a feat header
4867
4868
        // offset: 15; size: 4; size of rgbHdrSData
4869
4870
        // rgbHdrSData, assume "Enhanced Protection"
4871
        // offset: 19; size: 2; option flags
4872 4
        $options = self::getInt2d($recordData, 19);
4873
4874
        // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
4875 4
        $bool = (0x0001 & $options) >> 0;
4876 4
        $this->phpSheet->getProtection()->setObjects(!$bool);
4877
4878
        // bit: 1; mask 0x0002; edit scenarios
4879 4
        $bool = (0x0002 & $options) >> 1;
4880 4
        $this->phpSheet->getProtection()->setScenarios(!$bool);
4881
4882
        // bit: 2; mask 0x0004; format cells
4883 4
        $bool = (0x0004 & $options) >> 2;
4884 4
        $this->phpSheet->getProtection()->setFormatCells(!$bool);
4885
4886
        // bit: 3; mask 0x0008; format columns
4887 4
        $bool = (0x0008 & $options) >> 3;
4888 4
        $this->phpSheet->getProtection()->setFormatColumns(!$bool);
4889
4890
        // bit: 4; mask 0x0010; format rows
4891 4
        $bool = (0x0010 & $options) >> 4;
4892 4
        $this->phpSheet->getProtection()->setFormatRows(!$bool);
4893
4894
        // bit: 5; mask 0x0020; insert columns
4895 4
        $bool = (0x0020 & $options) >> 5;
4896 4
        $this->phpSheet->getProtection()->setInsertColumns(!$bool);
4897
4898
        // bit: 6; mask 0x0040; insert rows
4899 4
        $bool = (0x0040 & $options) >> 6;
4900 4
        $this->phpSheet->getProtection()->setInsertRows(!$bool);
4901
4902
        // bit: 7; mask 0x0080; insert hyperlinks
4903 4
        $bool = (0x0080 & $options) >> 7;
4904 4
        $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
4905
4906
        // bit: 8; mask 0x0100; delete columns
4907 4
        $bool = (0x0100 & $options) >> 8;
4908 4
        $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
4909
4910
        // bit: 9; mask 0x0200; delete rows
4911 4
        $bool = (0x0200 & $options) >> 9;
4912 4
        $this->phpSheet->getProtection()->setDeleteRows(!$bool);
4913
4914
        // bit: 10; mask 0x0400; select locked cells
4915 4
        $bool = (0x0400 & $options) >> 10;
4916 4
        $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
4917
4918
        // bit: 11; mask 0x0800; sort cell range
4919 4
        $bool = (0x0800 & $options) >> 11;
4920 4
        $this->phpSheet->getProtection()->setSort(!$bool);
4921
4922
        // bit: 12; mask 0x1000; auto filter
4923 4
        $bool = (0x1000 & $options) >> 12;
4924 4
        $this->phpSheet->getProtection()->setAutoFilter(!$bool);
4925
4926
        // bit: 13; mask 0x2000; pivot tables
4927 4
        $bool = (0x2000 & $options) >> 13;
4928 4
        $this->phpSheet->getProtection()->setPivotTables(!$bool);
4929
4930
        // bit: 14; mask 0x4000; select unlocked cells
4931 4
        $bool = (0x4000 & $options) >> 14;
4932 4
        $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
4933
4934
        // offset: 21; size: 2; not used
4935 4
    }
4936
4937
    /**
4938
     * Read RANGEPROTECTION record
4939
     * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
4940
     * where it is referred to as FEAT record
4941
     */
4942 1
    private function readRangeProtection()
4943
    {
4944 1
        $length = self::getInt2d($this->data, $this->pos + 2);
4945 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4946
4947
        // move stream pointer to next record
4948 1
        $this->pos += 4 + $length;
4949
4950
        // local pointer in record data
4951 1
        $offset = 0;
4952
4953 1
        if (!$this->readDataOnly) {
4954 1
            $offset += 12;
4955
4956
            // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
4957 1
            $isf = self::getInt2d($recordData, 12);
4958 1
            if ($isf != 2) {
4959
                // we only read FEAT records of type 2
4960
                return;
4961
            }
4962 1
            $offset += 2;
4963
4964 1
            $offset += 5;
4965
4966
            // offset: 19; size: 2; count of ref ranges this feature is on
4967 1
            $cref = self::getInt2d($recordData, 19);
4968 1
            $offset += 2;
4969
4970 1
            $offset += 6;
4971
4972
            // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
4973 1
            $cellRanges = [];
4974 1
            for ($i = 0; $i < $cref; ++$i) {
4975
                try {
4976 1
                    $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
4977
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4978
                    return;
4979
                }
4980 1
                $cellRanges[] = $cellRange;
4981 1
                $offset += 8;
4982
            }
4983
4984
            // offset: var; size: var; variable length of feature specific data
4985 1
            $rgbFeat = substr($recordData, $offset);
0 ignored issues
show
Unused Code introduced by
$rgbFeat is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4986 1
            $offset += 4;
4987
4988
            // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
4989 1
            $wPassword = self::getInt4d($recordData, $offset);
4990 1
            $offset += 4;
4991
4992
            // Apply range protection to sheet
4993 1
            if ($cellRanges) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cellRanges of type array 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...
4994 1
                $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
4995
            }
4996
        }
4997 1
    }
4998
4999
    /**
5000
     * Read IMDATA record
5001
     */
5002
    private function readImData()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
5003
    {
5004
        $length = self::getInt2d($this->data, $this->pos + 2);
0 ignored issues
show
Unused Code introduced by
$length is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5005
5006
        // get spliced record data
5007
        $splicedRecordData = $this->getSplicedRecordData();
5008
        $recordData = $splicedRecordData['recordData'];
5009
5010
        // UNDER CONSTRUCTION
5011
5012
        // offset: 0; size: 2; image format
5013
        $cf = self::getInt2d($recordData, 0);
5014
5015
        // offset: 2; size: 2; environment from which the file was written
5016
        $env = self::getInt2d($recordData, 2);
0 ignored issues
show
Unused Code introduced by
$env is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5017
5018
        // offset: 4; size: 4; length of the image data
5019
        $lcb = self::getInt4d($recordData, 4);
0 ignored issues
show
Unused Code introduced by
$lcb is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5020
5021
        // offset: 8; size: var; image data
5022
        $iData = substr($recordData, 8);
5023
5024
        switch ($cf) {
5025
            case 0x09: // Windows bitmap format
5026
                // BITMAPCOREINFO
5027
                // 1. BITMAPCOREHEADER
5028
                // offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure
5029
                $bcSize = self::getInt4d($iData, 0);
0 ignored issues
show
Unused Code introduced by
$bcSize is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5030
5031
                // offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels
5032
                $bcWidth = self::getInt2d($iData, 4);
5033
5034
                // offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels.
5035
                $bcHeight = self::getInt2d($iData, 6);
5036
                $ih = imagecreatetruecolor($bcWidth, $bcHeight);
5037
5038
                // offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1
5039
5040
                // offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24
5041
                $bcBitCount = self::getInt2d($iData, 10);
0 ignored issues
show
Unused Code introduced by
$bcBitCount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5042
5043
                $rgbString = substr($iData, 12);
5044
                $rgbTriples = [];
5045
                while (strlen($rgbString) > 0) {
5046
                    $rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString);
5047
                    $rgbString = substr($rgbString, 3);
5048
                }
5049
                $x = 0;
5050
                $y = 0;
5051
                foreach ($rgbTriples as $i => $rgbTriple) {
5052
                    $color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']);
5053
                    imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color);
5054
                    $x = ($x + 1) % $bcWidth;
5055
                    $y = $y + floor(($x + 1) / $bcWidth);
5056
                }
5057
                //imagepng($ih, 'image.png');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
5058
5059
                $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
5060
                $drawing->setPath($filename);
0 ignored issues
show
Bug introduced by
The variable $filename does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
5061
                $drawing->setWorksheet($this->phpSheet);
5062
                break;
5063
            case 0x02: // Windows metafile or Macintosh PICT format
5064
            case 0x0e: // native format
5065
            default:
5066
                break;
5067
        }
5068
5069
        // getSplicedRecordData() takes care of moving current position in data stream
5070
    }
5071
5072
    /**
5073
     * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5074
     * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5075
     * In this case, we must treat the CONTINUE record as a MSODRAWING record
5076
     */
5077
    private function readContinue()
5078
    {
5079
        $length = self::getInt2d($this->data, $this->pos + 2);
5080
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5081
5082
        // check if we are reading drawing data
5083
        // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5084
        if ($this->drawingData == '') {
5085
            // move stream pointer to next record
5086
            $this->pos += 4 + $length;
5087
5088
            return;
5089
        }
5090
5091
        // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5092
        if ($length < 4) {
5093
            // move stream pointer to next record
5094
            $this->pos += 4 + $length;
5095
5096
            return;
5097
        }
5098
5099
        // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5100
        // look inside CONTINUE record to see if it looks like a part of an Escher stream
5101
        // we know that Escher stream may be split at least at
5102
        //        0xF003 MsofbtSpgrContainer
5103
        //        0xF004 MsofbtSpContainer
5104
        //        0xF00D MsofbtClientTextbox
5105
        $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
5106
5107
        $splitPoint = self::getInt2d($recordData, 2);
5108
        if (in_array($splitPoint, $validSplitPoints)) {
5109
            // get spliced record data (and move pointer to next record)
5110
            $splicedRecordData = $this->getSplicedRecordData();
5111
            $this->drawingData .= $splicedRecordData['recordData'];
5112
5113
            return;
5114
        }
5115
5116
        // move stream pointer to next record
5117
        $this->pos += 4 + $length;
5118
    }
5119
5120
    /**
5121
     * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5122
     * records are found. Splices the record data pieces and returns the combined string as if record data
5123
     * is in one piece.
5124
     * Moves to next current position in data stream to start of next record different from a CONtINUE record
5125
     *
5126
     * @return array
5127
     */
5128 4
    private function getSplicedRecordData()
5129
    {
5130 4
        $data = '';
5131 4
        $spliceOffsets = [];
5132
5133 4
        $i = 0;
5134 4
        $spliceOffsets[0] = 0;
5135
5136
        do {
5137 4
            ++$i;
5138
5139
            // offset: 0; size: 2; identifier
5140 4
            $identifier = self::getInt2d($this->data, $this->pos);
0 ignored issues
show
Unused Code introduced by
$identifier is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5141
            // offset: 2; size: 2; length
5142 4
            $length = self::getInt2d($this->data, $this->pos + 2);
5143 4
            $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5144
5145 4
            $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5146
5147 4
            $this->pos += 4 + $length;
5148 4
            $nextIdentifier = self::getInt2d($this->data, $this->pos);
5149 4
        } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5150
5151
        $splicedData = [
5152 4
            'recordData' => $data,
5153 4
            'spliceOffsets' => $spliceOffsets,
5154
        ];
5155
5156 4
        return $splicedData;
5157
    }
5158
5159
    /**
5160
     * Convert formula structure into human readable Excel formula like 'A3+A5*5'
5161
     *
5162
     * @param string $formulaStructure The complete binary data for the formula
5163
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5164
     * @return string Human readable formula
5165
     */
5166 2
    private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5167
    {
5168
        // offset: 0; size: 2; size of the following formula data
5169 2
        $sz = self::getInt2d($formulaStructure, 0);
5170
5171
        // offset: 2; size: sz
5172 2
        $formulaData = substr($formulaStructure, 2, $sz);
5173
5174
        // offset: 2 + sz; size: variable (optional)
5175 2
        if (strlen($formulaStructure) > 2 + $sz) {
5176
            $additionalData = substr($formulaStructure, 2 + $sz);
5177
        } else {
5178 2
            $additionalData = '';
5179
        }
5180
5181 2
        return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5182
    }
5183
5184
    /**
5185
     * Take formula data and additional data for formula and return human readable formula
5186
     *
5187
     * @param string $formulaData The binary data for the formula itself
5188
     * @param string $additionalData Additional binary data going with the formula
5189
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5190
     * @return string Human readable formula
5191
     */
5192 2
    private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5193
    {
5194
        // start parsing the formula data
5195 2
        $tokens = [];
5196
5197 2
        while (strlen($formulaData) > 0 and $token = $this->getNextToken($formulaData, $baseCell)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
5198 2
            $tokens[] = $token;
5199 2
            $formulaData = substr($formulaData, $token['size']);
5200
        }
5201
5202 2
        $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5203
5204 2
        return $formulaString;
5205
    }
5206
5207
    /**
5208
     * Take array of tokens together with additional data for formula and return human readable formula
5209
     *
5210
     * @param array $tokens
5211
     * @param string $additionalData Additional binary data going with the formula
5212
     * @return string Human readable formula
5213
     */
5214 2
    private function createFormulaFromTokens($tokens, $additionalData)
5215
    {
5216
        // empty formula?
5217 2
        if (empty($tokens)) {
5218
            return '';
5219
        }
5220
5221 2
        $formulaStrings = [];
5222 2
        foreach ($tokens as $token) {
5223
            // initialize spaces
5224 2
            $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5225 2
            $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5226 2
            $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5227 2
            $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5228 2
            $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5229 2
            $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5230
5231 2
            switch ($token['name']) {
5232 2
                case 'tAdd': // addition
5233 2
                case 'tConcat': // addition
5234 2
                case 'tDiv': // division
5235 2
                case 'tEQ': // equality
5236 2
                case 'tGE': // greater than or equal
5237 2
                case 'tGT': // greater than
5238 2
                case 'tIsect': // intersection
5239 2
                case 'tLE': // less than or equal
5240 2
                case 'tList': // less than or equal
5241 2
                case 'tLT': // less than
5242 2
                case 'tMul': // multiplication
5243 2
                case 'tNE': // multiplication
5244 2
                case 'tPower': // power
5245 2
                case 'tRange': // range
5246 2
                case 'tSub': // subtraction
5247 2
                    $op2 = array_pop($formulaStrings);
5248 2
                    $op1 = array_pop($formulaStrings);
5249 2
                    $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5250 2
                    unset($space0, $space1);
5251 2
                    break;
5252 2
                case 'tUplus': // unary plus
5253 2 View Code Duplication
                case 'tUminus': // unary minus
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5254
                    $op = array_pop($formulaStrings);
5255
                    $formulaStrings[] = "$space1$space0{$token['data']}$op";
5256
                    unset($space0, $space1);
5257
                    break;
5258 2 View Code Duplication
                case 'tPercent': // percent sign
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5259
                    $op = array_pop($formulaStrings);
5260
                    $formulaStrings[] = "$op$space1$space0{$token['data']}";
5261
                    unset($space0, $space1);
5262
                    break;
5263 2
                case 'tAttrVolatile': // indicates volatile function
5264 2
                case 'tAttrIf':
5265 2
                case 'tAttrSkip':
5266 2
                case 'tAttrChoose':
5267
                    // token is only important for Excel formula evaluator
5268
                    // do nothing
5269
                    break;
5270 2
                case 'tAttrSpace': // space / carriage return
5271
                    // space will be used when next token arrives, do not alter formulaString stack
5272
                    switch ($token['data']['spacetype']) {
5273
                        case 'type0':
5274
                            $space0 = str_repeat(' ', $token['data']['spacecount']);
5275
                            break;
5276
                        case 'type1':
5277
                            $space1 = str_repeat("\n", $token['data']['spacecount']);
5278
                            break;
5279
                        case 'type2':
5280
                            $space2 = str_repeat(' ', $token['data']['spacecount']);
5281
                            break;
5282
                        case 'type3':
5283
                            $space3 = str_repeat("\n", $token['data']['spacecount']);
5284
                            break;
5285
                        case 'type4':
5286
                            $space4 = str_repeat(' ', $token['data']['spacecount']);
5287
                            break;
5288
                        case 'type5':
5289
                            $space5 = str_repeat("\n", $token['data']['spacecount']);
5290
                            break;
5291
                    }
5292
                    break;
5293 2
                case 'tAttrSum': // SUM function with one parameter
5294 1
                    $op = array_pop($formulaStrings);
5295 1
                    $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5296 1
                    unset($space0, $space1);
5297 1
                    break;
5298 2
                case 'tFunc': // function with fixed number of arguments
5299 2
                case 'tFuncV': // function with variable number of arguments
5300 1
                    if ($token['data']['function'] != '') {
5301
                        // normal function
5302 1
                        $ops = []; // array of operators
5303 1 View Code Duplication
                        for ($i = 0; $i < $token['data']['args']; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5304 1
                            $ops[] = array_pop($formulaStrings);
5305
                        }
5306 1
                        $ops = array_reverse($ops);
5307 1
                        $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
5308 1
                        unset($space0, $space1);
5309
                    } else {
5310
                        // add-in function
5311
                        $ops = []; // array of operators
5312 View Code Duplication
                        for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5313
                            $ops[] = array_pop($formulaStrings);
5314
                        }
5315
                        $ops = array_reverse($ops);
5316
                        $function = array_pop($formulaStrings);
5317
                        $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
5318
                        unset($space0, $space1);
5319
                    }
5320 1
                    break;
5321 2
                case 'tParen': // parenthesis
5322
                    $expression = array_pop($formulaStrings);
5323
                    $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5324
                    unset($space2, $space3, $space4, $space5);
5325
                    break;
5326 2
                case 'tArray': // array constant
5327
                    $constantArray = self::readBIFF8ConstantArray($additionalData);
5328
                    $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5329
                    $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5330
                    unset($space0, $space1);
5331
                    break;
5332 2
                case 'tMemArea':
5333
                    // bite off chunk of additional data
5334
                    $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5335
                    $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5336
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5337
                    unset($space0, $space1);
5338
                    break;
5339 2
                case 'tArea': // cell range address
5340 2
                case 'tBool': // boolean
5341 2
                case 'tErr': // error code
5342 2
                case 'tInt': // integer
5343 2
                case 'tMemErr':
5344 2
                case 'tMemFunc':
5345 2
                case 'tMissArg':
5346 2
                case 'tName':
5347 2
                case 'tNameX':
5348 2
                case 'tNum': // number
5349 2
                case 'tRef': // single cell reference
5350 2
                case 'tRef3d': // 3d cell reference
5351 2
                case 'tArea3d': // 3d cell range reference
5352 1
                case 'tRefN':
5353 1
                case 'tAreaN':
5354 1
                case 'tStr': // string
5355 2
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5356 2
                    unset($space0, $space1);
5357 2
                    break;
5358
            }
5359
        }
5360 2
        $formulaString = $formulaStrings[0];
5361
5362 2
        return $formulaString;
5363
    }
5364
5365
    /**
5366
     * Fetch next token from binary formula data
5367
     *
5368
     * @param string $formulaData Formula data
5369
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5370
     * @throws Exception
5371
     * @return array
5372
     */
5373 2
    private function getNextToken($formulaData, $baseCell = 'A1')
5374
    {
5375
        // offset: 0; size: 1; token id
5376 2
        $id = ord($formulaData[0]); // token id
5377 2
        $name = false; // initialize token name
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5378
5379
        switch ($id) {
5380 2
            case 0x03:
5381 1
                $name = 'tAdd';
5382 1
                $size = 1;
5383 1
                $data = '+';
5384 1
                break;
5385 2
            case 0x04:
5386
                $name = 'tSub';
5387
                $size = 1;
5388
                $data = '-';
5389
                break;
5390 2
            case 0x05:
5391 2
                $name = 'tMul';
5392 2
                $size = 1;
5393 2
                $data = '*';
5394 2
                break;
5395 2
            case 0x06:
5396
                $name = 'tDiv';
5397
                $size = 1;
5398
                $data = '/';
5399
                break;
5400 2
            case 0x07:
5401
                $name = 'tPower';
5402
                $size = 1;
5403
                $data = '^';
5404
                break;
5405 2
            case 0x08:
5406
                $name = 'tConcat';
5407
                $size = 1;
5408
                $data = '&';
5409
                break;
5410 2
            case 0x09:
5411
                $name = 'tLT';
5412
                $size = 1;
5413
                $data = '<';
5414
                break;
5415 2
            case 0x0A:
5416
                $name = 'tLE';
5417
                $size = 1;
5418
                $data = '<=';
5419
                break;
5420 2
            case 0x0B:
5421
                $name = 'tEQ';
5422
                $size = 1;
5423
                $data = '=';
5424
                break;
5425 2
            case 0x0C:
5426
                $name = 'tGE';
5427
                $size = 1;
5428
                $data = '>=';
5429
                break;
5430 2
            case 0x0D:
5431
                $name = 'tGT';
5432
                $size = 1;
5433
                $data = '>';
5434
                break;
5435 2
            case 0x0E:
5436 1
                $name = 'tNE';
5437 1
                $size = 1;
5438 1
                $data = '<>';
5439 1
                break;
5440 2
            case 0x0F:
5441
                $name = 'tIsect';
5442
                $size = 1;
5443
                $data = ' ';
5444
                break;
5445 2
            case 0x10:
5446
                $name = 'tList';
5447
                $size = 1;
5448
                $data = ',';
5449
                break;
5450 2
            case 0x11:
5451
                $name = 'tRange';
5452
                $size = 1;
5453
                $data = ':';
5454
                break;
5455 2
            case 0x12:
5456
                $name = 'tUplus';
5457
                $size = 1;
5458
                $data = '+';
5459
                break;
5460 2
            case 0x13:
5461
                $name = 'tUminus';
5462
                $size = 1;
5463
                $data = '-';
5464
                break;
5465 2
            case 0x14:
5466
                $name = 'tPercent';
5467
                $size = 1;
5468
                $data = '%';
5469
                break;
5470 2
            case 0x15:    //    parenthesis
5471
                $name = 'tParen';
5472
                $size = 1;
5473
                $data = null;
5474
                break;
5475 2
            case 0x16:    //    missing argument
5476
                $name = 'tMissArg';
5477
                $size = 1;
5478
                $data = '';
5479
                break;
5480 2
            case 0x17:    //    string
5481 1
                $name = 'tStr';
5482
                // offset: 1; size: var; Unicode string, 8-bit string length
5483 1
                $string = self::readUnicodeStringShort(substr($formulaData, 1));
5484 1
                $size = 1 + $string['size'];
5485 1
                $data = self::UTF8toExcelDoubleQuoted($string['value']);
5486 1
                break;
5487 2
            case 0x19:    //    Special attribute
5488
                // offset: 1; size: 1; attribute type flags:
5489 1
                switch (ord($formulaData[1])) {
5490 1
                    case 0x01:
5491
                        $name = 'tAttrVolatile';
5492
                        $size = 4;
5493
                        $data = null;
5494
                        break;
5495 1
                    case 0x02:
5496
                        $name = 'tAttrIf';
5497
                        $size = 4;
5498
                        $data = null;
5499
                        break;
5500 1
                    case 0x04:
5501
                        $name = 'tAttrChoose';
5502
                        // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5503
                        $nc = self::getInt2d($formulaData, 2);
5504
                        // offset: 4; size: 2 * $nc
5505
                        // offset: 4 + 2 * $nc; size: 2
5506
                        $size = 2 * $nc + 6;
5507
                        $data = null;
5508
                        break;
5509 1
                    case 0x08:
5510
                        $name = 'tAttrSkip';
5511
                        $size = 4;
5512
                        $data = null;
5513
                        break;
5514 1
                    case 0x10:
5515 1
                        $name = 'tAttrSum';
5516 1
                        $size = 4;
5517 1
                        $data = null;
5518 1
                        break;
5519
                    case 0x40:
5520
                    case 0x41:
5521
                        $name = 'tAttrSpace';
5522
                        $size = 4;
5523
                        // offset: 2; size: 2; space type and position
5524
                        switch (ord($formulaData[2])) {
5525
                            case 0x00:
5526
                                $spacetype = 'type0';
5527
                                break;
5528
                            case 0x01:
5529
                                $spacetype = 'type1';
5530
                                break;
5531
                            case 0x02:
5532
                                $spacetype = 'type2';
5533
                                break;
5534
                            case 0x03:
5535
                                $spacetype = 'type3';
5536
                                break;
5537
                            case 0x04:
5538
                                $spacetype = 'type4';
5539
                                break;
5540
                            case 0x05:
5541
                                $spacetype = 'type5';
5542
                                break;
5543
                            default:
5544
                                throw new Exception('Unrecognized space type in tAttrSpace token');
5545
                                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
5546
                        }
5547
                        // offset: 3; size: 1; number of inserted spaces/carriage returns
5548
                        $spacecount = ord($formulaData[3]);
5549
5550
                        $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
5551
                        break;
5552
                    default:
5553
                        throw new Exception('Unrecognized attribute flag in tAttr token');
5554
                        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
5555
                }
5556 1
                break;
5557 2
            case 0x1C:    //    error code
5558
                // offset: 1; size: 1; error code
5559
                $name = 'tErr';
5560
                $size = 2;
5561
                $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
5562
                break;
5563 2
            case 0x1D:    //    boolean
5564
                // offset: 1; size: 1; 0 = false, 1 = true;
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
5565
                $name = 'tBool';
5566
                $size = 2;
5567
                $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5568
                break;
5569 2
            case 0x1E:    //    integer
5570
                // offset: 1; size: 2; unsigned 16-bit integer
5571
                $name = 'tInt';
5572
                $size = 3;
5573
                $data = self::getInt2d($formulaData, 1);
5574
                break;
5575 2
            case 0x1F:    //    number
5576
                // offset: 1; size: 8;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
5577 2
                $name = 'tNum';
5578 2
                $size = 9;
5579 2
                $data = self::extractNumber(substr($formulaData, 1));
5580 2
                $data = str_replace(',', '.', (string) $data); // in case non-English locale
5581 2
                break;
5582 2
            case 0x20:    //    array constant
5583 2
            case 0x40:
5584 2
            case 0x60:
5585
                // offset: 1; size: 7; not used
5586
                $name = 'tArray';
5587
                $size = 8;
5588
                $data = null;
5589
                break;
5590 2
            case 0x21:    //    function with fixed number of arguments
5591 2
            case 0x41:
5592 2
            case 0x61:
5593
                $name = 'tFunc';
5594
                $size = 3;
5595
                // offset: 1; size: 2; index to built-in sheet function
5596
                switch (self::getInt2d($formulaData, 1)) {
5597
                    case 2:
5598
                        $function = 'ISNA';
5599
                        $args = 1;
5600
                        break;
5601
                    case 3:
5602
                        $function = 'ISERROR';
5603
                        $args = 1;
5604
                        break;
5605
                    case 10:
5606
                        $function = 'NA';
5607
                        $args = 0;
5608
                        break;
5609
                    case 15:
5610
                        $function = 'SIN';
5611
                        $args = 1;
5612
                        break;
5613
                    case 16:
5614
                        $function = 'COS';
5615
                        $args = 1;
5616
                        break;
5617
                    case 17:
5618
                        $function = 'TAN';
5619
                        $args = 1;
5620
                        break;
5621
                    case 18:
5622
                        $function = 'ATAN';
5623
                        $args = 1;
5624
                        break;
5625
                    case 19:
5626
                        $function = 'PI';
5627
                        $args = 0;
5628
                        break;
5629
                    case 20:
5630
                        $function = 'SQRT';
5631
                        $args = 1;
5632
                        break;
5633
                    case 21:
5634
                        $function = 'EXP';
5635
                        $args = 1;
5636
                        break;
5637
                    case 22:
5638
                        $function = 'LN';
5639
                        $args = 1;
5640
                        break;
5641
                    case 23:
5642
                        $function = 'LOG10';
5643
                        $args = 1;
5644
                        break;
5645
                    case 24:
5646
                        $function = 'ABS';
5647
                        $args = 1;
5648
                        break;
5649
                    case 25:
5650
                        $function = 'INT';
5651
                        $args = 1;
5652
                        break;
5653
                    case 26:
5654
                        $function = 'SIGN';
5655
                        $args = 1;
5656
                        break;
5657
                    case 27:
5658
                        $function = 'ROUND';
5659
                        $args = 2;
5660
                        break;
5661
                    case 30:
5662
                        $function = 'REPT';
5663
                        $args = 2;
5664
                        break;
5665
                    case 31:
5666
                        $function = 'MID';
5667
                        $args = 3;
5668
                        break;
5669
                    case 32:
5670
                        $function = 'LEN';
5671
                        $args = 1;
5672
                        break;
5673
                    case 33:
5674
                        $function = 'VALUE';
5675
                        $args = 1;
5676
                        break;
5677
                    case 34:
5678
                        $function = 'TRUE';
5679
                        $args = 0;
5680
                        break;
5681
                    case 35:
5682
                        $function = 'FALSE';
5683
                        $args = 0;
5684
                        break;
5685
                    case 38:
5686
                        $function = 'NOT';
5687
                        $args = 1;
5688
                        break;
5689
                    case 39:
5690
                        $function = 'MOD';
5691
                        $args = 2;
5692
                        break;
5693
                    case 40:
5694
                        $function = 'DCOUNT';
5695
                        $args = 3;
5696
                        break;
5697
                    case 41:
5698
                        $function = 'DSUM';
5699
                        $args = 3;
5700
                        break;
5701
                    case 42:
5702
                        $function = 'DAVERAGE';
5703
                        $args = 3;
5704
                        break;
5705
                    case 43:
5706
                        $function = 'DMIN';
5707
                        $args = 3;
5708
                        break;
5709
                    case 44:
5710
                        $function = 'DMAX';
5711
                        $args = 3;
5712
                        break;
5713
                    case 45:
5714
                        $function = 'DSTDEV';
5715
                        $args = 3;
5716
                        break;
5717
                    case 48:
5718
                        $function = 'TEXT';
5719
                        $args = 2;
5720
                        break;
5721
                    case 61:
5722
                        $function = 'MIRR';
5723
                        $args = 3;
5724
                        break;
5725
                    case 63:
5726
                        $function = 'RAND';
5727
                        $args = 0;
5728
                        break;
5729
                    case 65:
5730
                        $function = 'DATE';
5731
                        $args = 3;
5732
                        break;
5733
                    case 66:
5734
                        $function = 'TIME';
5735
                        $args = 3;
5736
                        break;
5737
                    case 67:
5738
                        $function = 'DAY';
5739
                        $args = 1;
5740
                        break;
5741
                    case 68:
5742
                        $function = 'MONTH';
5743
                        $args = 1;
5744
                        break;
5745
                    case 69:
5746
                        $function = 'YEAR';
5747
                        $args = 1;
5748
                        break;
5749
                    case 71:
5750
                        $function = 'HOUR';
5751
                        $args = 1;
5752
                        break;
5753
                    case 72:
5754
                        $function = 'MINUTE';
5755
                        $args = 1;
5756
                        break;
5757
                    case 73:
5758
                        $function = 'SECOND';
5759
                        $args = 1;
5760
                        break;
5761
                    case 74:
5762
                        $function = 'NOW';
5763
                        $args = 0;
5764
                        break;
5765
                    case 75:
5766
                        $function = 'AREAS';
5767
                        $args = 1;
5768
                        break;
5769
                    case 76:
5770
                        $function = 'ROWS';
5771
                        $args = 1;
5772
                        break;
5773
                    case 77:
5774
                        $function = 'COLUMNS';
5775
                        $args = 1;
5776
                        break;
5777
                    case 83:
5778
                        $function = 'TRANSPOSE';
5779
                        $args = 1;
5780
                        break;
5781
                    case 86:
5782
                        $function = 'TYPE';
5783
                        $args = 1;
5784
                        break;
5785
                    case 97:
5786
                        $function = 'ATAN2';
5787
                        $args = 2;
5788
                        break;
5789
                    case 98:
5790
                        $function = 'ASIN';
5791
                        $args = 1;
5792
                        break;
5793
                    case 99:
5794
                        $function = 'ACOS';
5795
                        $args = 1;
5796
                        break;
5797
                    case 105:
5798
                        $function = 'ISREF';
5799
                        $args = 1;
5800
                        break;
5801
                    case 111:
5802
                        $function = 'CHAR';
5803
                        $args = 1;
5804
                        break;
5805
                    case 112:
5806
                        $function = 'LOWER';
5807
                        $args = 1;
5808
                        break;
5809
                    case 113:
5810
                        $function = 'UPPER';
5811
                        $args = 1;
5812
                        break;
5813
                    case 114:
5814
                        $function = 'PROPER';
5815
                        $args = 1;
5816
                        break;
5817
                    case 117:
5818
                        $function = 'EXACT';
5819
                        $args = 2;
5820
                        break;
5821
                    case 118:
5822
                        $function = 'TRIM';
5823
                        $args = 1;
5824
                        break;
5825
                    case 119:
5826
                        $function = 'REPLACE';
5827
                        $args = 4;
5828
                        break;
5829
                    case 121:
5830
                        $function = 'CODE';
5831
                        $args = 1;
5832
                        break;
5833
                    case 126:
5834
                        $function = 'ISERR';
5835
                        $args = 1;
5836
                        break;
5837
                    case 127:
5838
                        $function = 'ISTEXT';
5839
                        $args = 1;
5840
                        break;
5841
                    case 128:
5842
                        $function = 'ISNUMBER';
5843
                        $args = 1;
5844
                        break;
5845
                    case 129:
5846
                        $function = 'ISBLANK';
5847
                        $args = 1;
5848
                        break;
5849
                    case 130:
5850
                        $function = 'T';
5851
                        $args = 1;
5852
                        break;
5853
                    case 131:
5854
                        $function = 'N';
5855
                        $args = 1;
5856
                        break;
5857
                    case 140:
5858
                        $function = 'DATEVALUE';
5859
                        $args = 1;
5860
                        break;
5861
                    case 141:
5862
                        $function = 'TIMEVALUE';
5863
                        $args = 1;
5864
                        break;
5865
                    case 142:
5866
                        $function = 'SLN';
5867
                        $args = 3;
5868
                        break;
5869
                    case 143:
5870
                        $function = 'SYD';
5871
                        $args = 4;
5872
                        break;
5873
                    case 162:
5874
                        $function = 'CLEAN';
5875
                        $args = 1;
5876
                        break;
5877
                    case 163:
5878
                        $function = 'MDETERM';
5879
                        $args = 1;
5880
                        break;
5881
                    case 164:
5882
                        $function = 'MINVERSE';
5883
                        $args = 1;
5884
                        break;
5885
                    case 165:
5886
                        $function = 'MMULT';
5887
                        $args = 2;
5888
                        break;
5889
                    case 184:
5890
                        $function = 'FACT';
5891
                        $args = 1;
5892
                        break;
5893
                    case 189:
5894
                        $function = 'DPRODUCT';
5895
                        $args = 3;
5896
                        break;
5897
                    case 190:
5898
                        $function = 'ISNONTEXT';
5899
                        $args = 1;
5900
                        break;
5901
                    case 195:
5902
                        $function = 'DSTDEVP';
5903
                        $args = 3;
5904
                        break;
5905
                    case 196:
5906
                        $function = 'DVARP';
5907
                        $args = 3;
5908
                        break;
5909
                    case 198:
5910
                        $function = 'ISLOGICAL';
5911
                        $args = 1;
5912
                        break;
5913
                    case 199:
5914
                        $function = 'DCOUNTA';
5915
                        $args = 3;
5916
                        break;
5917
                    case 207:
5918
                        $function = 'REPLACEB';
5919
                        $args = 4;
5920
                        break;
5921
                    case 210:
5922
                        $function = 'MIDB';
5923
                        $args = 3;
5924
                        break;
5925
                    case 211:
5926
                        $function = 'LENB';
5927
                        $args = 1;
5928
                        break;
5929
                    case 212:
5930
                        $function = 'ROUNDUP';
5931
                        $args = 2;
5932
                        break;
5933
                    case 213:
5934
                        $function = 'ROUNDDOWN';
5935
                        $args = 2;
5936
                        break;
5937
                    case 214:
5938
                        $function = 'ASC';
5939
                        $args = 1;
5940
                        break;
5941
                    case 215:
5942
                        $function = 'DBCS';
5943
                        $args = 1;
5944
                        break;
5945
                    case 221:
5946
                        $function = 'TODAY';
5947
                        $args = 0;
5948
                        break;
5949
                    case 229:
5950
                        $function = 'SINH';
5951
                        $args = 1;
5952
                        break;
5953
                    case 230:
5954
                        $function = 'COSH';
5955
                        $args = 1;
5956
                        break;
5957
                    case 231:
5958
                        $function = 'TANH';
5959
                        $args = 1;
5960
                        break;
5961
                    case 232:
5962
                        $function = 'ASINH';
5963
                        $args = 1;
5964
                        break;
5965
                    case 233:
5966
                        $function = 'ACOSH';
5967
                        $args = 1;
5968
                        break;
5969
                    case 234:
5970
                        $function = 'ATANH';
5971
                        $args = 1;
5972
                        break;
5973
                    case 235:
5974
                        $function = 'DGET';
5975
                        $args = 3;
5976
                        break;
5977
                    case 244:
5978
                        $function = 'INFO';
5979
                        $args = 1;
5980
                        break;
5981
                    case 252:
5982
                        $function = 'FREQUENCY';
5983
                        $args = 2;
5984
                        break;
5985
                    case 261:
5986
                        $function = 'ERROR.TYPE';
5987
                        $args = 1;
5988
                        break;
5989
                    case 271:
5990
                        $function = 'GAMMALN';
5991
                        $args = 1;
5992
                        break;
5993
                    case 273:
5994
                        $function = 'BINOMDIST';
5995
                        $args = 4;
5996
                        break;
5997
                    case 274:
5998
                        $function = 'CHIDIST';
5999
                        $args = 2;
6000
                        break;
6001
                    case 275:
6002
                        $function = 'CHIINV';
6003
                        $args = 2;
6004
                        break;
6005
                    case 276:
6006
                        $function = 'COMBIN';
6007
                        $args = 2;
6008
                        break;
6009
                    case 277:
6010
                        $function = 'CONFIDENCE';
6011
                        $args = 3;
6012
                        break;
6013
                    case 278:
6014
                        $function = 'CRITBINOM';
6015
                        $args = 3;
6016
                        break;
6017
                    case 279:
6018
                        $function = 'EVEN';
6019
                        $args = 1;
6020
                        break;
6021
                    case 280:
6022
                        $function = 'EXPONDIST';
6023
                        $args = 3;
6024
                        break;
6025
                    case 281:
6026
                        $function = 'FDIST';
6027
                        $args = 3;
6028
                        break;
6029
                    case 282:
6030
                        $function = 'FINV';
6031
                        $args = 3;
6032
                        break;
6033
                    case 283:
6034
                        $function = 'FISHER';
6035
                        $args = 1;
6036
                        break;
6037
                    case 284:
6038
                        $function = 'FISHERINV';
6039
                        $args = 1;
6040
                        break;
6041
                    case 285:
6042
                        $function = 'FLOOR';
6043
                        $args = 2;
6044
                        break;
6045
                    case 286:
6046
                        $function = 'GAMMADIST';
6047
                        $args = 4;
6048
                        break;
6049
                    case 287:
6050
                        $function = 'GAMMAINV';
6051
                        $args = 3;
6052
                        break;
6053
                    case 288:
6054
                        $function = 'CEILING';
6055
                        $args = 2;
6056
                        break;
6057
                    case 289:
6058
                        $function = 'HYPGEOMDIST';
6059
                        $args = 4;
6060
                        break;
6061
                    case 290:
6062
                        $function = 'LOGNORMDIST';
6063
                        $args = 3;
6064
                        break;
6065
                    case 291:
6066
                        $function = 'LOGINV';
6067
                        $args = 3;
6068
                        break;
6069
                    case 292:
6070
                        $function = 'NEGBINOMDIST';
6071
                        $args = 3;
6072
                        break;
6073
                    case 293:
6074
                        $function = 'NORMDIST';
6075
                        $args = 4;
6076
                        break;
6077
                    case 294:
6078
                        $function = 'NORMSDIST';
6079
                        $args = 1;
6080
                        break;
6081
                    case 295:
6082
                        $function = 'NORMINV';
6083
                        $args = 3;
6084
                        break;
6085
                    case 296:
6086
                        $function = 'NORMSINV';
6087
                        $args = 1;
6088
                        break;
6089
                    case 297:
6090
                        $function = 'STANDARDIZE';
6091
                        $args = 3;
6092
                        break;
6093
                    case 298:
6094
                        $function = 'ODD';
6095
                        $args = 1;
6096
                        break;
6097
                    case 299:
6098
                        $function = 'PERMUT';
6099
                        $args = 2;
6100
                        break;
6101
                    case 300:
6102
                        $function = 'POISSON';
6103
                        $args = 3;
6104
                        break;
6105
                    case 301:
6106
                        $function = 'TDIST';
6107
                        $args = 3;
6108
                        break;
6109
                    case 302:
6110
                        $function = 'WEIBULL';
6111
                        $args = 4;
6112
                        break;
6113
                    case 303:
6114
                        $function = 'SUMXMY2';
6115
                        $args = 2;
6116
                        break;
6117
                    case 304:
6118
                        $function = 'SUMX2MY2';
6119
                        $args = 2;
6120
                        break;
6121
                    case 305:
6122
                        $function = 'SUMX2PY2';
6123
                        $args = 2;
6124
                        break;
6125
                    case 306:
6126
                        $function = 'CHITEST';
6127
                        $args = 2;
6128
                        break;
6129
                    case 307:
6130
                        $function = 'CORREL';
6131
                        $args = 2;
6132
                        break;
6133
                    case 308:
6134
                        $function = 'COVAR';
6135
                        $args = 2;
6136
                        break;
6137
                    case 309:
6138
                        $function = 'FORECAST';
6139
                        $args = 3;
6140
                        break;
6141
                    case 310:
6142
                        $function = 'FTEST';
6143
                        $args = 2;
6144
                        break;
6145
                    case 311:
6146
                        $function = 'INTERCEPT';
6147
                        $args = 2;
6148
                        break;
6149
                    case 312:
6150
                        $function = 'PEARSON';
6151
                        $args = 2;
6152
                        break;
6153
                    case 313:
6154
                        $function = 'RSQ';
6155
                        $args = 2;
6156
                        break;
6157
                    case 314:
6158
                        $function = 'STEYX';
6159
                        $args = 2;
6160
                        break;
6161
                    case 315:
6162
                        $function = 'SLOPE';
6163
                        $args = 2;
6164
                        break;
6165
                    case 316:
6166
                        $function = 'TTEST';
6167
                        $args = 4;
6168
                        break;
6169
                    case 325:
6170
                        $function = 'LARGE';
6171
                        $args = 2;
6172
                        break;
6173
                    case 326:
6174
                        $function = 'SMALL';
6175
                        $args = 2;
6176
                        break;
6177
                    case 327:
6178
                        $function = 'QUARTILE';
6179
                        $args = 2;
6180
                        break;
6181
                    case 328:
6182
                        $function = 'PERCENTILE';
6183
                        $args = 2;
6184
                        break;
6185
                    case 331:
6186
                        $function = 'TRIMMEAN';
6187
                        $args = 2;
6188
                        break;
6189
                    case 332:
6190
                        $function = 'TINV';
6191
                        $args = 2;
6192
                        break;
6193
                    case 337:
6194
                        $function = 'POWER';
6195
                        $args = 2;
6196
                        break;
6197
                    case 342:
6198
                        $function = 'RADIANS';
6199
                        $args = 1;
6200
                        break;
6201
                    case 343:
6202
                        $function = 'DEGREES';
6203
                        $args = 1;
6204
                        break;
6205
                    case 346:
6206
                        $function = 'COUNTIF';
6207
                        $args = 2;
6208
                        break;
6209
                    case 347:
6210
                        $function = 'COUNTBLANK';
6211
                        $args = 1;
6212
                        break;
6213
                    case 350:
6214
                        $function = 'ISPMT';
6215
                        $args = 4;
6216
                        break;
6217
                    case 351:
6218
                        $function = 'DATEDIF';
6219
                        $args = 3;
6220
                        break;
6221
                    case 352:
6222
                        $function = 'DATESTRING';
6223
                        $args = 1;
6224
                        break;
6225
                    case 353:
6226
                        $function = 'NUMBERSTRING';
6227
                        $args = 2;
6228
                        break;
6229
                    case 360:
6230
                        $function = 'PHONETIC';
6231
                        $args = 1;
6232
                        break;
6233
                    case 368:
6234
                        $function = 'BAHTTEXT';
6235
                        $args = 1;
6236
                        break;
6237
                    default:
6238
                        throw new Exception('Unrecognized function in formula');
6239
                        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
6240
                }
6241
                $data = ['function' => $function, 'args' => $args];
6242
                break;
6243 2
            case 0x22:    //    function with variable number of arguments
6244 2
            case 0x42:
6245 2
            case 0x62:
6246 1
                $name = 'tFuncV';
6247 1
                $size = 4;
6248
                // offset: 1; size: 1; number of arguments
6249 1
                $args = ord($formulaData[1]);
6250
                // offset: 2: size: 2; index to built-in sheet function
6251 1
                $index = self::getInt2d($formulaData, 2);
6252
                switch ($index) {
6253 1
                    case 0:
6254
                        $function = 'COUNT';
6255
                        break;
6256 1
                    case 1:
6257 1
                        $function = 'IF';
6258 1
                        break;
6259 1
                    case 4:
6260 1
                        $function = 'SUM';
6261 1
                        break;
6262
                    case 5:
6263
                        $function = 'AVERAGE';
6264
                        break;
6265
                    case 6:
6266
                        $function = 'MIN';
6267
                        break;
6268
                    case 7:
6269
                        $function = 'MAX';
6270
                        break;
6271
                    case 8:
6272
                        $function = 'ROW';
6273
                        break;
6274
                    case 9:
6275
                        $function = 'COLUMN';
6276
                        break;
6277
                    case 11:
6278
                        $function = 'NPV';
6279
                        break;
6280
                    case 12:
6281
                        $function = 'STDEV';
6282
                        break;
6283
                    case 13:
6284
                        $function = 'DOLLAR';
6285
                        break;
6286
                    case 14:
6287
                        $function = 'FIXED';
6288
                        break;
6289
                    case 28:
6290
                        $function = 'LOOKUP';
6291
                        break;
6292
                    case 29:
6293
                        $function = 'INDEX';
6294
                        break;
6295
                    case 36:
6296
                        $function = 'AND';
6297
                        break;
6298
                    case 37:
6299
                        $function = 'OR';
6300
                        break;
6301
                    case 46:
6302
                        $function = 'VAR';
6303
                        break;
6304
                    case 49:
6305
                        $function = 'LINEST';
6306
                        break;
6307
                    case 50:
6308
                        $function = 'TREND';
6309
                        break;
6310
                    case 51:
6311
                        $function = 'LOGEST';
6312
                        break;
6313
                    case 52:
6314
                        $function = 'GROWTH';
6315
                        break;
6316
                    case 56:
6317
                        $function = 'PV';
6318
                        break;
6319
                    case 57:
6320
                        $function = 'FV';
6321
                        break;
6322
                    case 58:
6323
                        $function = 'NPER';
6324
                        break;
6325
                    case 59:
6326
                        $function = 'PMT';
6327
                        break;
6328
                    case 60:
6329
                        $function = 'RATE';
6330
                        break;
6331
                    case 62:
6332
                        $function = 'IRR';
6333
                        break;
6334
                    case 64:
6335
                        $function = 'MATCH';
6336
                        break;
6337
                    case 70:
6338
                        $function = 'WEEKDAY';
6339
                        break;
6340
                    case 78:
6341
                        $function = 'OFFSET';
6342
                        break;
6343
                    case 82:
6344
                        $function = 'SEARCH';
6345
                        break;
6346
                    case 100:
6347
                        $function = 'CHOOSE';
6348
                        break;
6349
                    case 101:
6350
                        $function = 'HLOOKUP';
6351
                        break;
6352
                    case 102:
6353
                        $function = 'VLOOKUP';
6354
                        break;
6355
                    case 109:
6356
                        $function = 'LOG';
6357
                        break;
6358
                    case 115:
6359
                        $function = 'LEFT';
6360
                        break;
6361
                    case 116:
6362
                        $function = 'RIGHT';
6363
                        break;
6364
                    case 120:
6365
                        $function = 'SUBSTITUTE';
6366
                        break;
6367
                    case 124:
6368
                        $function = 'FIND';
6369
                        break;
6370
                    case 125:
6371
                        $function = 'CELL';
6372
                        break;
6373
                    case 144:
6374
                        $function = 'DDB';
6375
                        break;
6376
                    case 148:
6377
                        $function = 'INDIRECT';
6378
                        break;
6379
                    case 167:
6380
                        $function = 'IPMT';
6381
                        break;
6382
                    case 168:
6383
                        $function = 'PPMT';
6384
                        break;
6385
                    case 169:
6386
                        $function = 'COUNTA';
6387
                        break;
6388
                    case 183:
6389
                        $function = 'PRODUCT';
6390
                        break;
6391
                    case 193:
6392
                        $function = 'STDEVP';
6393
                        break;
6394
                    case 194:
6395
                        $function = 'VARP';
6396
                        break;
6397
                    case 197:
6398
                        $function = 'TRUNC';
6399
                        break;
6400
                    case 204:
6401
                        $function = 'USDOLLAR';
6402
                        break;
6403
                    case 205:
6404
                        $function = 'FINDB';
6405
                        break;
6406
                    case 206:
6407
                        $function = 'SEARCHB';
6408
                        break;
6409
                    case 208:
6410
                        $function = 'LEFTB';
6411
                        break;
6412
                    case 209:
6413
                        $function = 'RIGHTB';
6414
                        break;
6415
                    case 216:
6416
                        $function = 'RANK';
6417
                        break;
6418
                    case 219:
6419
                        $function = 'ADDRESS';
6420
                        break;
6421
                    case 220:
6422
                        $function = 'DAYS360';
6423
                        break;
6424
                    case 222:
6425
                        $function = 'VDB';
6426
                        break;
6427
                    case 227:
6428
                        $function = 'MEDIAN';
6429
                        break;
6430
                    case 228:
6431
                        $function = 'SUMPRODUCT';
6432
                        break;
6433
                    case 247:
6434
                        $function = 'DB';
6435
                        break;
6436
                    case 255:
6437
                        $function = '';
6438
                        break;
6439
                    case 269:
6440
                        $function = 'AVEDEV';
6441
                        break;
6442
                    case 270:
6443
                        $function = 'BETADIST';
6444
                        break;
6445
                    case 272:
6446
                        $function = 'BETAINV';
6447
                        break;
6448
                    case 317:
6449
                        $function = 'PROB';
6450
                        break;
6451
                    case 318:
6452
                        $function = 'DEVSQ';
6453
                        break;
6454
                    case 319:
6455
                        $function = 'GEOMEAN';
6456
                        break;
6457
                    case 320:
6458
                        $function = 'HARMEAN';
6459
                        break;
6460
                    case 321:
6461
                        $function = 'SUMSQ';
6462
                        break;
6463
                    case 322:
6464
                        $function = 'KURT';
6465
                        break;
6466
                    case 323:
6467
                        $function = 'SKEW';
6468
                        break;
6469
                    case 324:
6470
                        $function = 'ZTEST';
6471
                        break;
6472
                    case 329:
6473
                        $function = 'PERCENTRANK';
6474
                        break;
6475
                    case 330:
6476
                        $function = 'MODE';
6477
                        break;
6478
                    case 336:
6479
                        $function = 'CONCATENATE';
6480
                        break;
6481
                    case 344:
6482
                        $function = 'SUBTOTAL';
6483
                        break;
6484
                    case 345:
6485
                        $function = 'SUMIF';
6486
                        break;
6487
                    case 354:
6488
                        $function = 'ROMAN';
6489
                        break;
6490
                    case 358:
6491
                        $function = 'GETPIVOTDATA';
6492
                        break;
6493
                    case 359:
6494
                        $function = 'HYPERLINK';
6495
                        break;
6496
                    case 361:
6497
                        $function = 'AVERAGEA';
6498
                        break;
6499
                    case 362:
6500
                        $function = 'MAXA';
6501
                        break;
6502
                    case 363:
6503
                        $function = 'MINA';
6504
                        break;
6505
                    case 364:
6506
                        $function = 'STDEVPA';
6507
                        break;
6508
                    case 365:
6509
                        $function = 'VARPA';
6510
                        break;
6511
                    case 366:
6512
                        $function = 'STDEVA';
6513
                        break;
6514
                    case 367:
6515
                        $function = 'VARA';
6516
                        break;
6517
                    default:
6518
                        throw new Exception('Unrecognized function in formula');
6519
                        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
6520
                }
6521 1
                $data = ['function' => $function, 'args' => $args];
6522 1
                break;
6523 2
            case 0x23:    //    index to defined name
6524 2
            case 0x43:
6525 2
            case 0x63:
6526
                $name = 'tName';
6527
                $size = 5;
6528
                // offset: 1; size: 2; one-based index to definedname record
6529
                $definedNameIndex = self::getInt2d($formulaData, 1) - 1;
6530
                // offset: 2; size: 2; not used
6531
                $data = $this->definedname[$definedNameIndex]['name'];
6532
                break;
6533 2
            case 0x24:    //    single cell reference e.g. A5
6534 2
            case 0x44:
6535 2
            case 0x64:
6536 2
                $name = 'tRef';
6537 2
                $size = 5;
6538 2
                $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6539 2
                break;
6540 2
            case 0x25:    //    cell range reference to cells in the same sheet (2d)
6541 1
            case 0x45:
6542 1
            case 0x65:
6543 2
                $name = 'tArea';
6544 2
                $size = 9;
6545 2
                $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6546 2
                break;
6547 1
            case 0x26:    //    Constant reference sub-expression
6548 1
            case 0x46:
6549 1 View Code Duplication
            case 0x66:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6550
                $name = 'tMemArea';
6551
                // offset: 1; size: 4; not used
6552
                // offset: 5; size: 2; size of the following subexpression
6553
                $subSize = self::getInt2d($formulaData, 5);
6554
                $size = 7 + $subSize;
6555
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6556
                break;
6557 1
            case 0x27:    //    Deleted constant reference sub-expression
6558 1
            case 0x47:
6559 1 View Code Duplication
            case 0x67:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6560
                $name = 'tMemErr';
6561
                // offset: 1; size: 4; not used
6562
                // offset: 5; size: 2; size of the following subexpression
6563
                $subSize = self::getInt2d($formulaData, 5);
6564
                $size = 7 + $subSize;
6565
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6566
                break;
6567 1
            case 0x29:    //    Variable reference sub-expression
6568 1
            case 0x49:
6569 1 View Code Duplication
            case 0x69:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6570
                $name = 'tMemFunc';
6571
                // offset: 1; size: 2; size of the following sub-expression
6572
                $subSize = self::getInt2d($formulaData, 1);
6573
                $size = 3 + $subSize;
6574
                $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
6575
                break;
6576 1
            case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
6577 1
            case 0x4C:
6578 1
            case 0x6C:
6579
                $name = 'tRefN';
6580
                $size = 5;
6581
                $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
6582
                break;
6583 1
            case 0x2D:    //    Relative 2d range reference
6584 1
            case 0x4D:
6585 1
            case 0x6D:
6586
                $name = 'tAreaN';
6587
                $size = 9;
6588
                $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
6589
                break;
6590 1
            case 0x39:    //    External name
6591 1
            case 0x59:
6592 1
            case 0x79:
6593
                $name = 'tNameX';
6594
                $size = 7;
6595
                // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
6596
                // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
6597
                $index = self::getInt2d($formulaData, 3);
6598
                // assume index is to EXTERNNAME record
6599
                $data = $this->externalNames[$index - 1]['name'];
6600
                // offset: 5; size: 2; not used
6601
                break;
6602 1
            case 0x3A:    //    3d reference to cell
6603 1
            case 0x5A:
6604 1 View Code Duplication
            case 0x7A:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6605
                $name = 'tRef3d';
6606
                $size = 7;
6607
6608
                try {
6609
                    // offset: 1; size: 2; index to REF entry
6610
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getInt2d($formulaData, 1));
6611
                    // offset: 3; size: 4; cell address
6612
                    $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
6613
6614
                    $data = "$sheetRange!$cellAddress";
6615
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
6616
                    // deleted sheet reference
6617
                    $data = '#REF!';
6618
                }
6619
                break;
6620 1
            case 0x3B:    //    3d reference to cell range
6621
            case 0x5B:
6622 View Code Duplication
            case 0x7B:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6623 1
                $name = 'tArea3d';
6624 1
                $size = 11;
6625
6626
                try {
6627
                    // offset: 1; size: 2; index to REF entry
6628 1
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getInt2d($formulaData, 1));
6629
                    // offset: 3; size: 8; cell address
6630 1
                    $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
6631
6632 1
                    $data = "$sheetRange!$cellRangeAddress";
6633
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
6634
                    // deleted sheet reference
6635
                    $data = '#REF!';
6636
                }
6637 1
                break;
6638
            // Unknown cases    // don't know how to deal with
6639
            default:
6640
                throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
6641
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
6642
        }
6643
6644
        return [
6645 2
            'id' => $id,
6646 2
            'name' => $name,
6647 2
            'size' => $size,
6648 2
            'data' => $data,
6649
        ];
6650
    }
6651
6652
    /**
6653
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
6654
     * section 3.3.4
6655
     *
6656
     * @param string $cellAddressStructure
6657
     * @return string
6658
     */
6659 2
    private function readBIFF8CellAddress($cellAddressStructure)
6660
    {
6661
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
6662 2
        $row = self::getInt2d($cellAddressStructure, 0) + 1;
6663
6664
        // offset: 2; size: 2; index to column or column offset + relative flags
6665
        // bit: 7-0; mask 0x00FF; column index
6666 2
        $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(0x00FF & self::getInt2d($cellAddressStructure, 2));
6667
6668
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6669 2
        if (!(0x4000 & self::getInt2d($cellAddressStructure, 2))) {
6670 1
            $column = '$' . $column;
6671
        }
6672
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6673 2
        if (!(0x8000 & self::getInt2d($cellAddressStructure, 2))) {
6674 1
            $row = '$' . $row;
6675
        }
6676
6677 2
        return $column . $row;
6678
    }
6679
6680
    /**
6681
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
6682
     * to indicate offsets from a base cell
6683
     * section 3.3.4
6684
     *
6685
     * @param string $cellAddressStructure
6686
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
6687
     * @return string
6688
     */
6689
    private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
6690
    {
6691
        list($baseCol, $baseRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($baseCell);
6692
        $baseCol = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($baseCol) - 1;
6693
6694
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
6695
        $rowIndex = self::getInt2d($cellAddressStructure, 0);
6696
        $row = self::getInt2d($cellAddressStructure, 0) + 1;
6697
6698
        // offset: 2; size: 2; index to column or column offset + relative flags
6699
        // bit: 7-0; mask 0x00FF; column index
6700
        $colIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
6701
6702
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6703 View Code Duplication
        if (!(0x4000 & self::getInt2d($cellAddressStructure, 2))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6704
            $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($colIndex);
6705
            $column = '$' . $column;
6706
        } else {
6707
            $colIndex = ($colIndex <= 127) ? $colIndex : $colIndex - 256;
6708
            $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $colIndex);
6709
        }
6710
6711
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6712
        if (!(0x8000 & self::getInt2d($cellAddressStructure, 2))) {
6713
            $row = '$' . $row;
6714
        } else {
6715
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
6716
            $row = $baseRow + $rowIndex;
6717
        }
6718
6719
        return $column . $row;
6720
    }
6721
6722
    /**
6723
     * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
6724
     * always fixed range
6725
     * section 2.5.14
6726
     *
6727
     * @param string $subData
6728
     * @throws Exception
6729
     * @return string
6730
     */
6731 4
    private function readBIFF5CellRangeAddressFixed($subData)
6732
    {
6733
        // offset: 0; size: 2; index to first row
6734 4
        $fr = self::getInt2d($subData, 0) + 1;
6735
6736
        // offset: 2; size: 2; index to last row
6737 4
        $lr = self::getInt2d($subData, 2) + 1;
6738
6739
        // offset: 4; size: 1; index to first column
6740 4
        $fc = ord($subData{4});
6741
6742
        // offset: 5; size: 1; index to last column
6743 4
        $lc = ord($subData{5});
6744
6745
        // check values
6746 4
        if ($fr > $lr || $fc > $lc) {
6747
            throw new Exception('Not a cell range address');
6748
        }
6749
6750
        // column index to letter
6751 4
        $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fc);
6752 4
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($lc);
6753
6754 4
        if ($fr == $lr and $fc == $lc) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
6755 4
            return "$fc$fr";
6756
        }
6757
6758 1
        return "$fc$fr:$lc$lr";
6759
    }
6760
6761
    /**
6762
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
6763
     * always fixed range
6764
     * section 2.5.14
6765
     *
6766
     * @param string $subData
6767
     * @throws Exception
6768
     * @return string
6769
     */
6770 2
    private function readBIFF8CellRangeAddressFixed($subData)
6771
    {
6772
        // offset: 0; size: 2; index to first row
6773 2
        $fr = self::getInt2d($subData, 0) + 1;
6774
6775
        // offset: 2; size: 2; index to last row
6776 2
        $lr = self::getInt2d($subData, 2) + 1;
6777
6778
        // offset: 4; size: 2; index to first column
6779 2
        $fc = self::getInt2d($subData, 4);
6780
6781
        // offset: 6; size: 2; index to last column
6782 2
        $lc = self::getInt2d($subData, 6);
6783
6784
        // check values
6785 2
        if ($fr > $lr || $fc > $lc) {
6786
            throw new Exception('Not a cell range address');
6787
        }
6788
6789
        // column index to letter
6790 2
        $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fc);
6791 2
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($lc);
6792
6793 2
        if ($fr == $lr and $fc == $lc) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
6794 2
            return "$fc$fr";
6795
        }
6796
6797 2
        return "$fc$fr:$lc$lr";
6798
    }
6799
6800
    /**
6801
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
6802
     * there are flags indicating whether column/row index is relative
6803
     * section 3.3.4
6804
     *
6805
     * @param string $subData
6806
     * @return string
6807
     */
6808 2
    private function readBIFF8CellRangeAddress($subData)
6809
    {
6810
        // todo: if cell range is just a single cell, should this funciton
6811
        // not just return e.g. 'A1' and not 'A1:A1' ?
6812
6813
        // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
6814 2
            $fr = self::getInt2d($subData, 0) + 1;
6815
6816
        // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
6817 2
            $lr = self::getInt2d($subData, 2) + 1;
6818
6819
        // offset: 4; size: 2; index to first column or column offset + relative flags
6820
6821
        // bit: 7-0; mask 0x00FF; column index
6822 2
        $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(0x00FF & self::getInt2d($subData, 4));
6823
6824
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6825 2
        if (!(0x4000 & self::getInt2d($subData, 4))) {
6826 1
            $fc = '$' . $fc;
6827
        }
6828
6829
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6830 2
        if (!(0x8000 & self::getInt2d($subData, 4))) {
6831 1
            $fr = '$' . $fr;
6832
        }
6833
6834
        // offset: 6; size: 2; index to last column or column offset + relative flags
6835
6836
        // bit: 7-0; mask 0x00FF; column index
6837 2
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(0x00FF & self::getInt2d($subData, 6));
6838
6839
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6840 2
        if (!(0x4000 & self::getInt2d($subData, 6))) {
6841 1
            $lc = '$' . $lc;
6842
        }
6843
6844
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6845 2
        if (!(0x8000 & self::getInt2d($subData, 6))) {
6846 1
            $lr = '$' . $lr;
6847
        }
6848
6849 2
        return "$fc$fr:$lc$lr";
6850
    }
6851
6852
    /**
6853
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
6854
     * to indicate offsets from a base cell
6855
     * section 3.3.4
6856
     *
6857
     * @param string $subData
6858
     * @param string $baseCell Base cell
6859
     * @return string Cell range address
6860
     */
6861
    private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
6862
    {
6863
        list($baseCol, $baseRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($baseCell);
6864
        $baseCol = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($baseCol) - 1;
6865
6866
        // TODO: if cell range is just a single cell, should this funciton
6867
        // not just return e.g. 'A1' and not 'A1:A1' ?
6868
6869
        // offset: 0; size: 2; first row
6870
        $frIndex = self::getInt2d($subData, 0); // adjust below
6871
6872
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
6873
        $lrIndex = self::getInt2d($subData, 2); // adjust below
6874
6875
        // offset: 4; size: 2; first column with relative/absolute flags
6876
6877
        // bit: 7-0; mask 0x00FF; column index
6878
        $fcIndex = 0x00FF & self::getInt2d($subData, 4);
6879
6880
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6881 View Code Duplication
        if (!(0x4000 & self::getInt2d($subData, 4))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6882
            // absolute column index
6883
            $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fcIndex);
6884
            $fc = '$' . $fc;
6885
        } else {
6886
            // column offset
6887
            $fcIndex = ($fcIndex <= 127) ? $fcIndex : $fcIndex - 256;
6888
            $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $fcIndex);
6889
        }
6890
6891
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6892 View Code Duplication
        if (!(0x8000 & self::getInt2d($subData, 4))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6893
            // absolute row index
6894
            $fr = $frIndex + 1;
6895
            $fr = '$' . $fr;
6896
        } else {
6897
            // row offset
6898
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
6899
            $fr = $baseRow + $frIndex;
6900
        }
6901
6902
        // offset: 6; size: 2; last column with relative/absolute flags
6903
6904
        // bit: 7-0; mask 0x00FF; column index
6905
        $lcIndex = 0x00FF & self::getInt2d($subData, 6);
6906
        $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
6907
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $lcIndex);
0 ignored issues
show
Unused Code introduced by
$lc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
6908
6909
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6910 View Code Duplication
        if (!(0x4000 & self::getInt2d($subData, 6))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6911
            // absolute column index
6912
            $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($lcIndex);
6913
            $lc = '$' . $lc;
6914
        } else {
6915
            // column offset
6916
            $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
6917
            $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $lcIndex);
6918
        }
6919
6920
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6921 View Code Duplication
        if (!(0x8000 & self::getInt2d($subData, 6))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6922
            // absolute row index
6923
            $lr = $lrIndex + 1;
6924
            $lr = '$' . $lr;
6925
        } else {
6926
            // row offset
6927
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
6928
            $lr = $baseRow + $lrIndex;
6929
        }
6930
6931
        return "$fc$fr:$lc$lr";
6932
    }
6933
6934
    /**
6935
     * Read BIFF8 cell range address list
6936
     * section 2.5.15
6937
     *
6938
     * @param string $subData
6939
     * @return array
6940
     */
6941 2 View Code Duplication
    private function readBIFF8CellRangeAddressList($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6942
    {
6943 2
        $cellRangeAddresses = [];
6944
6945
        // offset: 0; size: 2; number of the following cell range addresses
6946 2
        $nm = self::getInt2d($subData, 0);
6947
6948 2
        $offset = 2;
6949
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
6950 2
        for ($i = 0; $i < $nm; ++$i) {
6951 2
            $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
6952 2
            $offset += 8;
6953
        }
6954
6955
        return [
6956 2
            'size' => 2 + 8 * $nm,
6957 2
            'cellRangeAddresses' => $cellRangeAddresses,
6958
        ];
6959
    }
6960
6961
    /**
6962
     * Read BIFF5 cell range address list
6963
     * section 2.5.15
6964
     *
6965
     * @param string $subData
6966
     * @return array
6967
     */
6968 4 View Code Duplication
    private function readBIFF5CellRangeAddressList($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6969
    {
6970 4
        $cellRangeAddresses = [];
6971
6972
        // offset: 0; size: 2; number of the following cell range addresses
6973 4
        $nm = self::getInt2d($subData, 0);
6974
6975 4
        $offset = 2;
6976
        // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
6977 4
        for ($i = 0; $i < $nm; ++$i) {
6978 4
            $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
6979 4
            $offset += 6;
6980
        }
6981
6982
        return [
6983 4
            'size' => 2 + 6 * $nm,
6984 4
            'cellRangeAddresses' => $cellRangeAddresses,
6985
        ];
6986
    }
6987
6988
    /**
6989
     * Get a sheet range like Sheet1:Sheet3 from REF index
6990
     * Note: If there is only one sheet in the range, one gets e.g Sheet1
6991
     * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
6992
     * in which case an Exception is thrown
6993
     *
6994
     * @param int $index
6995
     * @throws Exception
6996
     * @return string|false
6997
     */
6998 1
    private function readSheetRangeByRefIndex($index)
6999
    {
7000 1
        if (isset($this->ref[$index])) {
7001 1
            $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
7002
7003
            switch ($type) {
7004 1
                case 'internal':
7005
                    // check if we have a deleted 3d reference
7006 1
                    if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF or $this->ref[$index]['lastSheetIndex'] == 0xFFFF) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
7007
                        throw new Exception('Deleted sheet reference');
7008
                    }
7009
7010
                    // we have normal sheet range (collapsed or uncollapsed)
7011 1
                    $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7012 1
                    $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7013
7014 1
                    if ($firstSheetName == $lastSheetName) {
7015
                        // collapsed sheet range
7016 1
                        $sheetRange = $firstSheetName;
7017
                    } else {
7018
                        $sheetRange = "$firstSheetName:$lastSheetName";
7019
                    }
7020
7021
                    // escape the single-quotes
7022 1
                    $sheetRange = str_replace("'", "''", $sheetRange);
7023
7024
                    // if there are special characters, we need to enclose the range in single-quotes
7025
                    // todo: check if we have identified the whole set of special characters
7026
                    // it seems that the following characters are not accepted for sheet names
7027
                    // and we may assume that they are not present: []*/:\?
7028 1
                    if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) {
7029
                        $sheetRange = "'$sheetRange'";
7030
                    }
7031
7032 1
                    return $sheetRange;
7033
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
7034
                default:
7035
                    // TODO: external sheet support
7036
                    throw new Exception('Xls reader only supports internal sheets in fomulas');
7037
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
7038
            }
7039
        }
7040
7041
        return false;
7042
    }
7043
7044
    /**
7045
     * read BIFF8 constant value array from array data
7046
     * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7047
     * section 2.5.8
7048
     *
7049
     * @param string $arrayData
7050
     * @return array
7051
     */
7052
    private static function readBIFF8ConstantArray($arrayData)
7053
    {
7054
        // offset: 0; size: 1; number of columns decreased by 1
7055
        $nc = ord($arrayData[0]);
7056
7057
        // offset: 1; size: 2; number of rows decreased by 1
7058
        $nr = self::getInt2d($arrayData, 1);
7059
        $size = 3; // initialize
7060
        $arrayData = substr($arrayData, 3);
7061
7062
        // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7063
        $matrixChunks = [];
7064
        for ($r = 1; $r <= $nr + 1; ++$r) {
7065
            $items = [];
7066
            for ($c = 1; $c <= $nc + 1; ++$c) {
7067
                $constant = self::readBIFF8Constant($arrayData);
7068
                $items[] = $constant['value'];
7069
                $arrayData = substr($arrayData, $constant['size']);
7070
                $size += $constant['size'];
7071
            }
7072
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7073
        }
7074
        $matrix = '{' . implode(';', $matrixChunks) . '}';
7075
7076
        return [
7077
            'value' => $matrix,
7078
            'size' => $size,
7079
        ];
7080
    }
7081
7082
    /**
7083
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7084
     * section 2.5.7
7085
     * returns e.g. array('value' => '5', 'size' => 9)
7086
     *
7087
     * @param string $valueData
7088
     * @return array
7089
     */
7090
    private static function readBIFF8Constant($valueData)
7091
    {
7092
        // offset: 0; size: 1; identifier for type of constant
7093
        $identifier = ord($valueData[0]);
7094
7095
        switch ($identifier) {
7096
            case 0x00: // empty constant (what is this?)
7097
                $value = '';
7098
                $size = 9;
7099
                break;
7100
            case 0x01: // number
7101
                // offset: 1; size: 8; IEEE 754 floating-point value
7102
                $value = self::extractNumber(substr($valueData, 1, 8));
7103
                $size = 9;
7104
                break;
7105
            case 0x02: // string value
7106
                // offset: 1; size: var; Unicode string, 16-bit string length
7107
                $string = self::readUnicodeStringLong(substr($valueData, 1));
7108
                $value = '"' . $string['value'] . '"';
7109
                $size = 1 + $string['size'];
7110
                break;
7111
            case 0x04: // boolean
7112
                // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7113
                if (ord($valueData[1])) {
7114
                    $value = 'TRUE';
7115
                } else {
7116
                    $value = 'FALSE';
7117
                }
7118
                $size = 9;
7119
                break;
7120
            case 0x10: // error code
7121
                // offset: 1; size: 1; error code
7122
                $value = Xls\ErrorCode::lookup(ord($valueData[1]));
7123
                $size = 9;
7124
                break;
7125
        }
7126
7127
        return [
7128
            'value' => $value,
0 ignored issues
show
Bug introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
7129
            'size' => $size,
0 ignored issues
show
Bug introduced by
The variable $size does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
7130
        ];
7131
    }
7132
7133
    /**
7134
     * Extract RGB color
7135
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4
7136
     *
7137
     * @param string $rgb Encoded RGB value (4 bytes)
7138
     * @return array
7139
     */
7140 2
    private static function readRGB($rgb)
7141
    {
7142
        // offset: 0; size 1; Red component
7143 2
        $r = ord($rgb{0});
7144
7145
        // offset: 1; size: 1; Green component
7146 2
        $g = ord($rgb{1});
7147
7148
        // offset: 2; size: 1; Blue component
7149 2
        $b = ord($rgb{2});
7150
7151
        // HEX notation, e.g. 'FF00FC'
7152 2
        $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7153
7154 2
        return ['rgb' => $rgb];
7155
    }
7156
7157
    /**
7158
     * Read byte string (8-bit string length)
7159
     * OpenOffice documentation: 2.5.2
7160
     *
7161
     * @param string $subData
7162
     * @return array
7163
     */
7164 View Code Duplication
    private function readByteStringShort($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7165
    {
7166
        // offset: 0; size: 1; length of the string (character count)
7167
        $ln = ord($subData[0]);
7168
7169
        // offset: 1: size: var; character array (8-bit characters)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7170
        $value = $this->decodeCodepage(substr($subData, 1, $ln));
7171
7172
        return [
7173
            'value' => $value,
7174
            'size' => 1 + $ln, // size in bytes of data structure
7175
        ];
7176
    }
7177
7178
    /**
7179
     * Read byte string (16-bit string length)
7180
     * OpenOffice documentation: 2.5.2
7181
     *
7182
     * @param string $subData
7183
     * @return array
7184
     */
7185 View Code Duplication
    private function readByteStringLong($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7186
    {
7187
        // offset: 0; size: 2; length of the string (character count)
7188
        $ln = self::getInt2d($subData, 0);
7189
7190
        // offset: 2: size: var; character array (8-bit characters)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7191
        $value = $this->decodeCodepage(substr($subData, 2));
7192
7193
        //return $string;
7194
        return [
7195
            'value' => $value,
7196
            'size' => 2 + $ln, // size in bytes of data structure
7197
        ];
7198
    }
7199
7200
    /**
7201
     * Extracts an Excel Unicode short string (8-bit string length)
7202
     * OpenOffice documentation: 2.5.3
7203
     * function will automatically find out where the Unicode string ends.
7204
     *
7205
     * @param string $subData
7206
     * @return array
7207
     */
7208 4 View Code Duplication
    private static function readUnicodeStringShort($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7209
    {
7210 4
        $value = '';
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
7211
7212
        // offset: 0: size: 1; length of the string (character count)
7213 4
        $characterCount = ord($subData[0]);
7214
7215 4
        $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7216
7217
        // add 1 for the string length
7218 4
        $string['size'] += 1;
7219
7220 4
        return $string;
7221
    }
7222
7223
    /**
7224
     * Extracts an Excel Unicode long string (16-bit string length)
7225
     * OpenOffice documentation: 2.5.3
7226
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7227
     *
7228
     * @param string $subData
7229
     * @return array
7230
     */
7231 4 View Code Duplication
    private static function readUnicodeStringLong($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7232
    {
7233 4
        $value = '';
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
7234
7235
        // offset: 0: size: 2; length of the string (character count)
7236 4
        $characterCount = self::getInt2d($subData, 0);
7237
7238 4
        $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7239
7240
        // add 2 for the string length
7241 4
        $string['size'] += 2;
7242
7243 4
        return $string;
7244
    }
7245
7246
    /**
7247
     * Read Unicode string with no string length field, but with known character count
7248
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7249
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3
7250
     *
7251
     * @param string $subData
7252
     * @param int $characterCount
7253
     * @return array
7254
     */
7255 4
    private static function readUnicodeString($subData, $characterCount)
7256
    {
7257 4
        $value = '';
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
7258
7259
        // offset: 0: size: 1; option flags
7260
        // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7261 4
        $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7262
7263
        // bit: 2; mask: 0x04; Asian phonetic settings
7264 4
        $hasAsian = (0x04) & ord($subData[0]) >> 2;
0 ignored issues
show
Unused Code introduced by
$hasAsian is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
7265
7266
        // bit: 3; mask: 0x08; Rich-Text settings
7267 4
        $hasRichText = (0x08) & ord($subData[0]) >> 3;
0 ignored issues
show
Unused Code introduced by
$hasRichText is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
7268
7269
        // offset: 1: size: var; character array
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7270
        // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7271
        // needs to be fixed
7272 4
        $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7273
7274
        return [
7275 4
            'value' => $value,
7276 4
            'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7277
        ];
7278
    }
7279
7280
    /**
7281
     * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7282
     * Example:  hello"world  -->  "hello""world"
7283
     *
7284
     * @param string $value UTF-8 encoded string
7285
     * @return string
7286
     */
7287 1
    private static function UTF8toExcelDoubleQuoted($value)
7288
    {
7289 1
        return '"' . str_replace('"', '""', $value) . '"';
7290
    }
7291
7292
    /**
7293
     * Reads first 8 bytes of a string and return IEEE 754 float
7294
     *
7295
     * @param string $data Binary string that is at least 8 bytes long
7296
     * @return float
7297
     */
7298 4
    private static function extractNumber($data)
7299
    {
7300 4
        $rknumhigh = self::getInt4d($data, 4);
7301 4
        $rknumlow = self::getInt4d($data, 0);
7302 4
        $sign = ($rknumhigh & 0x80000000) >> 31;
7303 4
        $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7304 4
        $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7305 4
        $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7306 4
        $mantissalow2 = ($rknumlow & 0x7fffffff);
7307 4
        $value = $mantissa / pow(2, (20 - $exp));
7308
7309 4
        if ($mantissalow1 != 0) {
7310 2
            $value += 1 / pow(2, (21 - $exp));
7311
        }
7312
7313 4
        $value += $mantissalow2 / pow(2, (52 - $exp));
7314 4
        if ($sign) {
7315
            $value *= -1;
7316
        }
7317
7318 4
        return $value;
7319
    }
7320
7321
    /**
7322
     * @param int $rknum
7323
     */
7324 1
    private static function getIEEE754($rknum)
7325
    {
7326 1
        if (($rknum & 0x02) != 0) {
7327
            $value = $rknum >> 2;
7328
        } else {
7329
            // changes by mmp, info on IEEE754 encoding from
7330
            // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7331
            // The RK format calls for using only the most significant 30 bits
7332
            // of the 64 bit floating point value. The other 34 bits are assumed
7333
            // to be 0 so we use the upper 30 bits of $rknum as follows...
7334 1
            $sign = ($rknum & 0x80000000) >> 31;
7335 1
            $exp = ($rknum & 0x7ff00000) >> 20;
7336 1
            $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7337 1
            $value = $mantissa / pow(2, (20 - ($exp - 1023)));
7338 1
            if ($sign) {
7339
                $value = -1 * $value;
7340
            }
7341
            //end of changes by mmp
7342
        }
7343 1
        if (($rknum & 0x01) != 0) {
7344
            $value /= 100;
7345
        }
7346
7347 1
        return $value;
7348
    }
7349
7350
    /**
7351
     * Get UTF-8 string from (compressed or uncompressed) UTF-16 string
7352
     *
7353
     * @param string $string
7354
     * @param bool $compressed
7355
     * @return string
7356
     */
7357 4
    private static function encodeUTF16($string, $compressed = false)
7358
    {
7359 4
        if ($compressed) {
7360 3
            $string = self::uncompressByteString($string);
7361
        }
7362
7363 4
        return \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
7364
    }
7365
7366
    /**
7367
     * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7368
     *
7369
     * @param string $string
7370
     * @return string
7371
     */
7372 3
    private static function uncompressByteString($string)
7373
    {
7374 3
        $uncompressedString = '';
7375 3
        $strLen = strlen($string);
7376 3
        for ($i = 0; $i < $strLen; ++$i) {
7377 3
            $uncompressedString .= $string[$i] . "\0";
7378
        }
7379
7380 3
        return $uncompressedString;
7381
    }
7382
7383
    /**
7384
     * Convert string to UTF-8. Only used for BIFF5.
7385
     *
7386
     * @param string $string
7387
     * @return string
7388
     */
7389
    private function decodeCodepage($string)
7390
    {
7391
        return \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
7392
    }
7393
7394
    /**
7395
     * Read 16-bit unsigned integer
7396
     *
7397
     * @param string $data
7398
     * @param int $pos
7399
     * @return int
7400
     */
7401 4
    public static function getInt2d($data, $pos)
7402
    {
7403 4
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
7404
    }
7405
7406
    /**
7407
     * Read 32-bit signed integer
7408
     *
7409
     * @param string $data
7410
     * @param int $pos
7411
     * @return int
7412
     */
7413 4
    public static function getInt4d($data, $pos)
7414
    {
7415
        // FIX: represent numbers correctly on 64-bit system
7416
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7417
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7418 4
        $_or_24 = ord($data[$pos + 3]);
7419 4
        if ($_or_24 >= 128) {
7420
            // negative number
7421 2
            $_ord_24 = -abs((256 - $_or_24) << 24);
7422
        } else {
7423 4
            $_ord_24 = ($_or_24 & 127) << 24;
7424
        }
7425
7426 4
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
7427
    }
7428
7429 1
    private function parseRichText($is = '')
7430
    {
7431 1
        $value = new \PhpOffice\PhpSpreadsheet\RichText();
7432 1
        $value->createText($is);
7433
7434 1
        return $value;
7435
    }
7436
}
7437