Completed
Push — develop ( 5e03e2...c8a8fd )
by Adrien
28:49
created

Xls::makeKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 2
dl 0
loc 23
ccs 0
cts 14
cp 0
crap 6
rs 9.0856
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Shared\File;
6
7
/**
8
 * Copyright (c) 2006 - 2016 PhpSpreadsheet
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23
 *
24
 * @category   PhpSpreadsheet
25
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
26
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
27
 */
28
29
// Original file header of ParseXL (used as the base for this class):
30
// --------------------------------------------------------------------------------
31
// Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
32
// trex005, and mmp11 (SourceForge.net)
33
// http://sourceforge.net/projects/phpexcelreader/
34
// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
35
//     Modelled moreso after Perl Excel Parse/Write modules
36
//     Added Parse_Excel_Spreadsheet object
37
//         Reads a whole worksheet or tab as row,column array or as
38
//         associated hash of indexed rows and named column fields
39
//     Added variables for worksheet (tab) indexes and names
40
//     Added an object call for loading individual woorksheets
41
//     Changed default indexing defaults to 0 based arrays
42
//     Fixed date/time and percent formats
43
//     Includes patches found at SourceForge...
44
//         unicode patch by nobody
45
//         unpack("d") machine depedency patch by matchy
46
//         boundsheet utf16 patch by bjaenichen
47
//     Renamed functions for shorter names
48
//     General code cleanup and rigor, including <80 column width
49
//     Included a testcase Excel file and PHP example calls
50
//     Code works for PHP 5.x
51
52
// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
53
// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
54
//     Decoding of formula conditions, results, and tokens.
55
//     Support for user-defined named cells added as an array "namedcells"
56
//         Patch code for user-defined named cells supports single cells only.
57
//         NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
58
//         external sheet reference structure
59
class Xls extends BaseReader implements IReader
60
{
61
    // ParseXL definitions
62
    const XLS_BIFF8 = 0x0600;
63
    const XLS_BIFF7 = 0x0500;
64
    const XLS_WORKBOOKGLOBALS = 0x0005;
65
    const XLS_WORKSHEET = 0x0010;
66
67
    // record identifiers
68
    const XLS_TYPE_FORMULA = 0x0006;
69
    const XLS_TYPE_EOF = 0x000a;
70
    const XLS_TYPE_PROTECT = 0x0012;
71
    const XLS_TYPE_OBJECTPROTECT = 0x0063;
72
    const XLS_TYPE_SCENPROTECT = 0x00dd;
73
    const XLS_TYPE_PASSWORD = 0x0013;
74
    const XLS_TYPE_HEADER = 0x0014;
75
    const XLS_TYPE_FOOTER = 0x0015;
76
    const XLS_TYPE_EXTERNSHEET = 0x0017;
77
    const XLS_TYPE_DEFINEDNAME = 0x0018;
78
    const XLS_TYPE_VERTICALPAGEBREAKS = 0x001a;
79
    const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
80
    const XLS_TYPE_NOTE = 0x001c;
81
    const XLS_TYPE_SELECTION = 0x001d;
82
    const XLS_TYPE_DATEMODE = 0x0022;
83
    const XLS_TYPE_EXTERNNAME = 0x0023;
84
    const XLS_TYPE_LEFTMARGIN = 0x0026;
85
    const XLS_TYPE_RIGHTMARGIN = 0x0027;
86
    const XLS_TYPE_TOPMARGIN = 0x0028;
87
    const XLS_TYPE_BOTTOMMARGIN = 0x0029;
88
    const XLS_TYPE_PRINTGRIDLINES = 0x002b;
89
    const XLS_TYPE_FILEPASS = 0x002f;
90
    const XLS_TYPE_FONT = 0x0031;
91
    const XLS_TYPE_CONTINUE = 0x003c;
92
    const XLS_TYPE_PANE = 0x0041;
93
    const XLS_TYPE_CODEPAGE = 0x0042;
94
    const XLS_TYPE_DEFCOLWIDTH = 0x0055;
95
    const XLS_TYPE_OBJ = 0x005d;
96
    const XLS_TYPE_COLINFO = 0x007d;
97
    const XLS_TYPE_IMDATA = 0x007f;
98
    const XLS_TYPE_SHEETPR = 0x0081;
99
    const XLS_TYPE_HCENTER = 0x0083;
100
    const XLS_TYPE_VCENTER = 0x0084;
101
    const XLS_TYPE_SHEET = 0x0085;
102
    const XLS_TYPE_PALETTE = 0x0092;
103
    const XLS_TYPE_SCL = 0x00a0;
104
    const XLS_TYPE_PAGESETUP = 0x00a1;
105
    const XLS_TYPE_MULRK = 0x00bd;
106
    const XLS_TYPE_MULBLANK = 0x00be;
107
    const XLS_TYPE_DBCELL = 0x00d7;
108
    const XLS_TYPE_XF = 0x00e0;
109
    const XLS_TYPE_MERGEDCELLS = 0x00e5;
110
    const XLS_TYPE_MSODRAWINGGROUP = 0x00eb;
111
    const XLS_TYPE_MSODRAWING = 0x00ec;
112
    const XLS_TYPE_SST = 0x00fc;
113
    const XLS_TYPE_LABELSST = 0x00fd;
114
    const XLS_TYPE_EXTSST = 0x00ff;
115
    const XLS_TYPE_EXTERNALBOOK = 0x01ae;
116
    const XLS_TYPE_DATAVALIDATIONS = 0x01b2;
117
    const XLS_TYPE_TXO = 0x01b6;
118
    const XLS_TYPE_HYPERLINK = 0x01b8;
119
    const XLS_TYPE_DATAVALIDATION = 0x01be;
120
    const XLS_TYPE_DIMENSION = 0x0200;
121
    const XLS_TYPE_BLANK = 0x0201;
122
    const XLS_TYPE_NUMBER = 0x0203;
123
    const XLS_TYPE_LABEL = 0x0204;
124
    const XLS_TYPE_BOOLERR = 0x0205;
125
    const XLS_TYPE_STRING = 0x0207;
126
    const XLS_TYPE_ROW = 0x0208;
127
    const XLS_TYPE_INDEX = 0x020b;
128
    const XLS_TYPE_ARRAY = 0x0221;
129
    const XLS_TYPE_DEFAULTROWHEIGHT = 0x0225;
130
    const XLS_TYPE_WINDOW2 = 0x023e;
131
    const XLS_TYPE_RK = 0x027e;
132
    const XLS_TYPE_STYLE = 0x0293;
133
    const XLS_TYPE_FORMAT = 0x041e;
134
    const XLS_TYPE_SHAREDFMLA = 0x04bc;
135
    const XLS_TYPE_BOF = 0x0809;
136
    const XLS_TYPE_SHEETPROTECTION = 0x0867;
137
    const XLS_TYPE_RANGEPROTECTION = 0x0868;
138
    const XLS_TYPE_SHEETLAYOUT = 0x0862;
139
    const XLS_TYPE_XFEXT = 0x087d;
140
    const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
141
    const XLS_TYPE_UNKNOWN = 0xffff;
142
143
    // Encryption type
144
    const MS_BIFF_CRYPTO_NONE = 0;
145
    const MS_BIFF_CRYPTO_XOR = 1;
146
    const MS_BIFF_CRYPTO_RC4 = 2;
147
148
    // Size of stream blocks when using RC4 encryption
149
    const REKEY_BLOCK = 0x400;
150
151
    /**
152
     * Summary Information stream data.
153
     *
154
     * @var string
155
     */
156
    private $summaryInformation;
157
158
    /**
159
     * Extended Summary Information stream data.
160
     *
161
     * @var string
162
     */
163
    private $documentSummaryInformation;
164
165
    /**
166
     * User-Defined Properties stream data.
167
     *
168
     * @var string
169
     */
170
    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...
171
172
    /**
173
     * Workbook stream data. (Includes workbook globals substream as well as sheet substreams)
174
     *
175
     * @var string
176
     */
177
    private $data;
178
179
    /**
180
     * Size in bytes of $this->data
181
     *
182
     * @var int
183
     */
184
    private $dataSize;
185
186
    /**
187
     * Current position in stream
188
     *
189
     * @var int
190
     */
191
    private $pos;
192
193
    /**
194
     * Workbook to be returned by the reader.
195
     *
196
     * @var \PhpOffice\PhpSpreadsheet\Spreadsheet
197
     */
198
    private $spreadsheet;
199
200
    /**
201
     * Worksheet that is currently being built by the reader.
202
     *
203
     * @var Worksheet
204
     */
205
    private $phpSheet;
206
207
    /**
208
     * BIFF version
209
     *
210
     * @var int
211
     */
212
    private $version;
213
214
    /**
215
     * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
216
     * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'
217
     *
218
     * @var string
219
     */
220
    private $codepage;
221
222
    /**
223
     * Shared formats
224
     *
225
     * @var array
226
     */
227
    private $formats;
228
229
    /**
230
     * Shared fonts
231
     *
232
     * @var array
233
     */
234
    private $objFonts;
235
236
    /**
237
     * Color palette
238
     *
239
     * @var array
240
     */
241
    private $palette;
242
243
    /**
244
     * Worksheets
245
     *
246
     * @var array
247
     */
248
    private $sheets;
249
250
    /**
251
     * External books
252
     *
253
     * @var array
254
     */
255
    private $externalBooks;
256
257
    /**
258
     * REF structures. Only applies to BIFF8.
259
     *
260
     * @var array
261
     */
262
    private $ref;
263
264
    /**
265
     * External names
266
     *
267
     * @var array
268
     */
269
    private $externalNames;
270
271
    /**
272
     * Defined names
273
     *
274
     * @var array
275
     */
276
    private $definedname;
277
278
    /**
279
     * Shared strings. Only applies to BIFF8.
280
     *
281
     * @var array
282
     */
283
    private $sst;
284
285
    /**
286
     * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
287
     *
288
     * @var bool
289
     */
290
    private $frozen;
291
292
    /**
293
     * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
294
     *
295
     * @var bool
296
     */
297
    private $isFitToPages;
298
299
    /**
300
     * Objects. One OBJ record contributes with one entry.
301
     *
302
     * @var array
303
     */
304
    private $objs;
305
306
    /**
307
     * Text Objects. One TXO record corresponds with one entry.
308
     *
309
     * @var array
310
     */
311
    private $textObjects;
312
313
    /**
314
     * Cell Annotations (BIFF8)
315
     *
316
     * @var array
317
     */
318
    private $cellNotes;
319
320
    /**
321
     * The combined MSODRAWINGGROUP data
322
     *
323
     * @var string
324
     */
325
    private $drawingGroupData;
326
327
    /**
328
     * The combined MSODRAWING data (per sheet)
329
     *
330
     * @var string
331
     */
332
    private $drawingData;
333
334
    /**
335
     * Keep track of XF index
336
     *
337
     * @var int
338
     */
339
    private $xfIndex;
340
341
    /**
342
     * Mapping of XF index (that is a cell XF) to final index in cellXf collection
343
     *
344
     * @var array
345
     */
346
    private $mapCellXfIndex;
347
348
    /**
349
     * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection
350
     *
351
     * @var array
352
     */
353
    private $mapCellStyleXfIndex;
354
355
    /**
356
     * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
357
     *
358
     * @var array
359
     */
360
    private $sharedFormulas;
361
362
    /**
363
     * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
364
     * refers to a shared formula.
365
     *
366
     * @var array
367
     */
368
    private $sharedFormulaParts;
369
370
    /**
371
     * The type of encryption in use
372
     *
373
     * @var int
374
     */
375
    private $encryption = 0;
376
377
    /**
378
     * The position in the stream after which contents are encrypted
379
     *
380
     * @var int
381
     */
382
    private $encryptionStartPos = false;
383
384
    /**
385
     * The current RC4 decryption object
386
     *
387
     * @var Xls\RC4
388
     */
389
    private $rc4Key = null;
390
391
    /**
392
     * The position in the stream that the RC4 decryption object was left at
393
     *
394
     * @var int
395
     */
396
    private $rc4Pos = 0;
397
398
    /**
399
     * The current MD5 context state
400
     *
401
     * @var string
402
     */
403
    private $md5Ctxt = null;
404
405
    /**
406
     * Create a new Xls Reader instance
407
     */
408 5
    public function __construct()
409
    {
410 5
        $this->readFilter = new DefaultReadFilter();
411 5
    }
412
413
    /**
414
     * Can the current IReader read the file?
415
     *
416
     * @param     string         $pFilename
417
     * @throws Exception
418
     * @return     bool
419
     */
420 3
    public function canRead($pFilename)
421
    {
422 3
        File::assertFile($pFilename);
423
424
        try {
425
            // Use ParseXL for the hard work.
426 3
            $ole = new \PhpOffice\PhpSpreadsheet\Shared\OLERead();
427
428
            // get excel data
429 3
            $ole->read($pFilename);
430
431 3
            return true;
432
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
433
            return false;
434
        }
435
    }
436
437
    /**
438
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object
439
     *
440
     * @param     string         $pFilename
441
     * @throws     Exception
442
     */
443
    public function listWorksheetNames($pFilename)
444
    {
445
        File::assertFile($pFilename);
446
447
        $worksheetNames = [];
448
449
        // Read the OLE file
450
        $this->loadOLE($pFilename);
451
452
        // total byte size of Excel data (workbook global substream + sheet substreams)
453
        $this->dataSize = strlen($this->data);
454
455
        $this->pos = 0;
456
        $this->sheets = [];
457
458
        // Parse Workbook Global Substream
459 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...
460
            $code = self::getInt2d($this->data, $this->pos);
461
462
            switch ($code) {
463
                case self::XLS_TYPE_BOF:
464
                    $this->readBof();
465
                    break;
466
                case self::XLS_TYPE_SHEET:
467
                    $this->readSheet();
468
                    break;
469
                case self::XLS_TYPE_EOF:
470
                    $this->readDefault();
471
                    break 2;
472
                default:
473
                    $this->readDefault();
474
                    break;
475
            }
476
        }
477
478
        foreach ($this->sheets as $sheet) {
479
            if ($sheet['sheetType'] != 0x00) {
480
                // 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...
481
                continue;
482
            }
483
484
            $worksheetNames[] = $sheet['name'];
485
        }
486
487
        return $worksheetNames;
488
    }
489
490
    /**
491
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
492
     *
493
     * @param   string     $pFilename
494
     * @throws   Exception
495
     */
496
    public function listWorksheetInfo($pFilename)
497
    {
498
        File::assertFile($pFilename);
499
500
        $worksheetInfo = [];
501
502
        // Read the OLE file
503
        $this->loadOLE($pFilename);
504
505
        // total byte size of Excel data (workbook global substream + sheet substreams)
506
        $this->dataSize = strlen($this->data);
507
508
        // initialize
509
        $this->pos = 0;
510
        $this->sheets = [];
511
512
        // Parse Workbook Global Substream
513 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...
514
            $code = self::getInt2d($this->data, $this->pos);
515
516
            switch ($code) {
517
                case self::XLS_TYPE_BOF:
518
                    $this->readBof();
519
                    break;
520
                case self::XLS_TYPE_SHEET:
521
                    $this->readSheet();
522
                    break;
523
                case self::XLS_TYPE_EOF:
524
                    $this->readDefault();
525
                    break 2;
526
                default:
527
                    $this->readDefault();
528
                    break;
529
            }
530
        }
531
532
        // Parse the individual sheets
533
        foreach ($this->sheets as $sheet) {
534
            if ($sheet['sheetType'] != 0x00) {
535
                // 0x00: Worksheet
536
                // 0x02: Chart
537
                // 0x06: Visual Basic module
538
                continue;
539
            }
540
541
            $tmpInfo = [];
542
            $tmpInfo['worksheetName'] = $sheet['name'];
543
            $tmpInfo['lastColumnLetter'] = 'A';
544
            $tmpInfo['lastColumnIndex'] = 0;
545
            $tmpInfo['totalRows'] = 0;
546
            $tmpInfo['totalColumns'] = 0;
547
548
            $this->pos = $sheet['offset'];
549
550
            while ($this->pos <= $this->dataSize - 4) {
551
                $code = self::getInt2d($this->data, $this->pos);
552
553
                switch ($code) {
554
                    case self::XLS_TYPE_RK:
555
                    case self::XLS_TYPE_LABELSST:
556
                    case self::XLS_TYPE_NUMBER:
557
                    case self::XLS_TYPE_FORMULA:
558
                    case self::XLS_TYPE_BOOLERR:
559
                    case self::XLS_TYPE_LABEL:
560
                        $length = self::getInt2d($this->data, $this->pos + 2);
561
                        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
562
563
                        // move stream pointer to next record
564
                        $this->pos += 4 + $length;
565
566
                        $rowIndex = self::getInt2d($recordData, 0) + 1;
567
                        $columnIndex = self::getInt2d($recordData, 2);
568
569
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
570
                        $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
571
                        break;
572
                    case self::XLS_TYPE_BOF:
573
                        $this->readBof();
574
                        break;
575
                    case self::XLS_TYPE_EOF:
576
                        $this->readDefault();
577
                        break 2;
578
                    default:
579
                        $this->readDefault();
580
                        break;
581
                }
582
            }
583
584
            $tmpInfo['lastColumnLetter'] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
585
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
586
587
            $worksheetInfo[] = $tmpInfo;
588
        }
589
590
        return $worksheetInfo;
591
    }
592
593
    /**
594
     * Loads PhpSpreadsheet from file
595
     *
596
     * @param     string         $pFilename
597
     * @throws    Exception
598
     * @return    \PhpOffice\PhpSpreadsheet\Spreadsheet
599
     */
600 4
    public function load($pFilename)
601
    {
602
        // Read the OLE file
603 4
        $this->loadOLE($pFilename);
604
605
        // Initialisations
606 4
        $this->spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
607 4
        $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
608 4
        if (!$this->readDataOnly) {
609 4
            $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
610 4
            $this->spreadsheet->removeCellXfByIndex(0); // remove the default style
611
        }
612
613
        // Read the summary information stream (containing meta data)
614 4
        $this->readSummaryInformation();
615
616
        // Read the Additional document summary information stream (containing application-specific meta data)
617 4
        $this->readDocumentSummaryInformation();
618
619
        // total byte size of Excel data (workbook global substream + sheet substreams)
620 4
        $this->dataSize = strlen($this->data);
621
622
        // initialize
623 4
        $this->pos = 0;
624 4
        $this->codepage = 'CP1252';
625 4
        $this->formats = [];
626 4
        $this->objFonts = [];
627 4
        $this->palette = [];
628 4
        $this->sheets = [];
629 4
        $this->externalBooks = [];
630 4
        $this->ref = [];
631 4
        $this->definedname = [];
632 4
        $this->sst = [];
633 4
        $this->drawingGroupData = '';
634 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...
635 4
        $this->mapCellXfIndex = [];
636 4
        $this->mapCellStyleXfIndex = [];
637
638
        // Parse Workbook Global Substream
639 4
        while ($this->pos < $this->dataSize) {
640 4
            $code = self::getInt2d($this->data, $this->pos);
641
642
            switch ($code) {
643 4
                case self::XLS_TYPE_BOF:
644 4
                    $this->readBof();
645 4
                    break;
646 4
                case self::XLS_TYPE_FILEPASS:
647
                    $this->readFilepass();
648
                    break;
649 4
                case self::XLS_TYPE_CODEPAGE:
650 4
                    $this->readCodepage();
651 4
                    break;
652 4
                case self::XLS_TYPE_DATEMODE:
653 4
                    $this->readDateMode();
654 4
                    break;
655 4
                case self::XLS_TYPE_FONT:
656 4
                    $this->readFont();
657 4
                    break;
658 4
                case self::XLS_TYPE_FORMAT:
659 4
                    $this->readFormat();
660 4
                    break;
661 4
                case self::XLS_TYPE_XF:
662 4
                    $this->readXf();
663 4
                    break;
664 4
                case self::XLS_TYPE_XFEXT:
665 3
                    $this->readXfExt();
666 3
                    break;
667 4
                case self::XLS_TYPE_STYLE:
668 4
                    $this->readStyle();
669 4
                    break;
670 4
                case self::XLS_TYPE_PALETTE:
671 2
                    $this->readPalette();
672 2
                    break;
673 4
                case self::XLS_TYPE_SHEET:
674 4
                    $this->readSheet();
675 4
                    break;
676 4
                case self::XLS_TYPE_EXTERNALBOOK:
677 3
                    $this->readExternalBook();
678 3
                    break;
679 4
                case self::XLS_TYPE_EXTERNNAME:
680
                    $this->readExternName();
681
                    break;
682 4
                case self::XLS_TYPE_EXTERNSHEET:
683 3
                    $this->readExternSheet();
684 3
                    break;
685 4
                case self::XLS_TYPE_DEFINEDNAME:
686 1
                    $this->readDefinedName();
687 1
                    break;
688 4
                case self::XLS_TYPE_MSODRAWINGGROUP:
689 3
                    $this->readMsoDrawingGroup();
690 3
                    break;
691 4
                case self::XLS_TYPE_SST:
692 4
                    $this->readSst();
693 4
                    break;
694 4
                case self::XLS_TYPE_EOF:
695 4
                    $this->readDefault();
696 4
                    break 2;
697
                default:
698 4
                    $this->readDefault();
699 4
                    break;
700
            }
701
        }
702
703
        // Resolve indexed colors for font, fill, and border colors
704
        // Cannot be resolved already in XF record, because PALETTE record comes afterwards
705 4
        if (!$this->readDataOnly) {
706 4
            foreach ($this->objFonts as $objFont) {
707 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...
708 4
                    $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
709 4
                    $objFont->getColor()->setRGB($color['rgb']);
710
                }
711
            }
712
713 4
            foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
714
                // fill start and end color
715 4
                $fill = $objStyle->getFill();
716
717 4
                if (isset($fill->startcolorIndex)) {
718 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...
719 4
                    $fill->getStartColor()->setRGB($startColor['rgb']);
720
                }
721 4
                if (isset($fill->endcolorIndex)) {
722 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...
723 4
                    $fill->getEndColor()->setRGB($endColor['rgb']);
724
                }
725
726
                // border colors
727 4
                $top = $objStyle->getBorders()->getTop();
728 4
                $right = $objStyle->getBorders()->getRight();
729 4
                $bottom = $objStyle->getBorders()->getBottom();
730 4
                $left = $objStyle->getBorders()->getLeft();
731 4
                $diagonal = $objStyle->getBorders()->getDiagonal();
732
733 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...
734 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...
735 4
                    $top->getColor()->setRGB($borderTopColor['rgb']);
736
                }
737 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...
738 4
                    $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
739 4
                    $right->getColor()->setRGB($borderRightColor['rgb']);
740
                }
741 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...
742 4
                    $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
743 4
                    $bottom->getColor()->setRGB($borderBottomColor['rgb']);
744
                }
745 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...
746 4
                    $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
747 4
                    $left->getColor()->setRGB($borderLeftColor['rgb']);
748
                }
749 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...
750 4
                    $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
751 4
                    $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
752
                }
753
            }
754
        }
755
756
        // treat MSODRAWINGGROUP records, workbook-level Escher
757 4
        if (!$this->readDataOnly && $this->drawingGroupData) {
758 2
            $escherWorkbook = new \PhpOffice\PhpSpreadsheet\Shared\Escher();
759 2
            $reader = new Xls\Escher($escherWorkbook);
760 2
            $escherWorkbook = $reader->load($this->drawingGroupData);
761
        }
762
763
        // Parse the individual sheets
764 4
        foreach ($this->sheets as $sheet) {
765 4
            if ($sheet['sheetType'] != 0x00) {
766
                // 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...
767
                continue;
768
            }
769
770
            // check if sheet should be skipped
771 4
            if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
772
                continue;
773
            }
774
775
            // add sheet to PhpSpreadsheet object
776 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...
777
            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
778
            //        cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
779
            //        name in line with the formula, not the reverse
780 4
            $this->phpSheet->setTitle($sheet['name'], false);
781 4
            $this->phpSheet->setSheetState($sheet['sheetState']);
782
783 4
            $this->pos = $sheet['offset'];
784
785
            // Initialize isFitToPages. May change after reading SHEETPR record.
786 4
            $this->isFitToPages = false;
787
788
            // Initialize drawingData
789 4
            $this->drawingData = '';
790
791
            // Initialize objs
792 4
            $this->objs = [];
793
794
            // Initialize shared formula parts
795 4
            $this->sharedFormulaParts = [];
796
797
            // Initialize shared formulas
798 4
            $this->sharedFormulas = [];
799
800
            // Initialize text objs
801 4
            $this->textObjects = [];
802
803
            // Initialize cell annotations
804 4
            $this->cellNotes = [];
805 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...
806
807 4
            while ($this->pos <= $this->dataSize - 4) {
808 4
                $code = self::getInt2d($this->data, $this->pos);
809
810
                switch ($code) {
811 4
                    case self::XLS_TYPE_BOF:
812 4
                        $this->readBof();
813 4
                        break;
814 4
                    case self::XLS_TYPE_PRINTGRIDLINES:
815 4
                        $this->readPrintGridlines();
816 4
                        break;
817 4
                    case self::XLS_TYPE_DEFAULTROWHEIGHT:
818 3
                        $this->readDefaultRowHeight();
819 3
                        break;
820 4
                    case self::XLS_TYPE_SHEETPR:
821 4
                        $this->readSheetPr();
822 4
                        break;
823 4
                    case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
824
                        $this->readHorizontalPageBreaks();
825
                        break;
826 4
                    case self::XLS_TYPE_VERTICALPAGEBREAKS:
827
                        $this->readVerticalPageBreaks();
828
                        break;
829 4
                    case self::XLS_TYPE_HEADER:
830 4
                        $this->readHeader();
831 4
                        break;
832 4
                    case self::XLS_TYPE_FOOTER:
833 4
                        $this->readFooter();
834 4
                        break;
835 4
                    case self::XLS_TYPE_HCENTER:
836 4
                        $this->readHcenter();
837 4
                        break;
838 4
                    case self::XLS_TYPE_VCENTER:
839 4
                        $this->readVcenter();
840 4
                        break;
841 4
                    case self::XLS_TYPE_LEFTMARGIN:
842 4
                        $this->readLeftMargin();
843 4
                        break;
844 4
                    case self::XLS_TYPE_RIGHTMARGIN:
845 4
                        $this->readRightMargin();
846 4
                        break;
847 4
                    case self::XLS_TYPE_TOPMARGIN:
848 4
                        $this->readTopMargin();
849 4
                        break;
850 4
                    case self::XLS_TYPE_BOTTOMMARGIN:
851 4
                        $this->readBottomMargin();
852 4
                        break;
853 4
                    case self::XLS_TYPE_PAGESETUP:
854 4
                        $this->readPageSetup();
855 4
                        break;
856 4
                    case self::XLS_TYPE_PROTECT:
857 1
                        $this->readProtect();
858 1
                        break;
859 4
                    case self::XLS_TYPE_SCENPROTECT:
860
                        $this->readScenProtect();
861
                        break;
862 4
                    case self::XLS_TYPE_OBJECTPROTECT:
863
                        $this->readObjectProtect();
864
                        break;
865 4
                    case self::XLS_TYPE_PASSWORD:
866
                        $this->readPassword();
867
                        break;
868 4
                    case self::XLS_TYPE_DEFCOLWIDTH:
869 4
                        $this->readDefColWidth();
870 4
                        break;
871 4
                    case self::XLS_TYPE_COLINFO:
872 3
                        $this->readColInfo();
873 3
                        break;
874 4
                    case self::XLS_TYPE_DIMENSION:
875 4
                        $this->readDefault();
876 4
                        break;
877 4
                    case self::XLS_TYPE_ROW:
878 3
                        $this->readRow();
879 3
                        break;
880 4
                    case self::XLS_TYPE_DBCELL:
881 3
                        $this->readDefault();
882 3
                        break;
883 4
                    case self::XLS_TYPE_RK:
884 1
                        $this->readRk();
885 1
                        break;
886 4
                    case self::XLS_TYPE_LABELSST:
887 4
                        $this->readLabelSst();
888 4
                        break;
889 4
                    case self::XLS_TYPE_MULRK:
890
                        $this->readMulRk();
891
                        break;
892 4
                    case self::XLS_TYPE_NUMBER:
893 1
                        $this->readNumber();
894 1
                        break;
895 4
                    case self::XLS_TYPE_FORMULA:
896 2
                        $this->readFormula();
897 2
                        break;
898 4
                    case self::XLS_TYPE_SHAREDFMLA:
899
                        $this->readSharedFmla();
900
                        break;
901 4
                    case self::XLS_TYPE_BOOLERR:
902
                        $this->readBoolErr();
903
                        break;
904 4
                    case self::XLS_TYPE_MULBLANK:
905 1
                        $this->readMulBlank();
906 1
                        break;
907 4
                    case self::XLS_TYPE_LABEL:
908
                        $this->readLabel();
909
                        break;
910 4
                    case self::XLS_TYPE_BLANK:
911 2
                        $this->readBlank();
912 2
                        break;
913 4
                    case self::XLS_TYPE_MSODRAWING:
914 2
                        $this->readMsoDrawing();
915 2
                        break;
916 4
                    case self::XLS_TYPE_OBJ:
917 2
                        $this->readObj();
918 2
                        break;
919 4
                    case self::XLS_TYPE_WINDOW2:
920 4
                        $this->readWindow2();
921 4
                        break;
922 4
                    case self::XLS_TYPE_PAGELAYOUTVIEW:
923 4
                        $this->readPageLayoutView();
924 4
                        break;
925 4
                    case self::XLS_TYPE_SCL:
926
                        $this->readScl();
927
                        break;
928 4
                    case self::XLS_TYPE_PANE:
929
                        $this->readPane();
930
                        break;
931 4
                    case self::XLS_TYPE_SELECTION:
932 4
                        $this->readSelection();
933 4
                        break;
934 4
                    case self::XLS_TYPE_MERGEDCELLS:
935 2
                        $this->readMergedCells();
936 2
                        break;
937 4
                    case self::XLS_TYPE_HYPERLINK:
938 2
                        $this->readHyperLink();
939 2
                        break;
940 4
                    case self::XLS_TYPE_DATAVALIDATIONS:
941
                        $this->readDataValidations();
942
                        break;
943 4
                    case self::XLS_TYPE_DATAVALIDATION:
944
                        $this->readDataValidation();
945
                        break;
946 4
                    case self::XLS_TYPE_SHEETLAYOUT:
947 2
                        $this->readSheetLayout();
948 2
                        break;
949 4
                    case self::XLS_TYPE_SHEETPROTECTION:
950 4
                        $this->readSheetProtection();
951 4
                        break;
952 4
                    case self::XLS_TYPE_RANGEPROTECTION:
953 1
                        $this->readRangeProtection();
954 1
                        break;
955 4
                    case self::XLS_TYPE_NOTE:
956 1
                        $this->readNote();
957 1
                        break;
958
                    //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...
959 4
                    case self::XLS_TYPE_TXO:
960 1
                        $this->readTextObject();
961 1
                        break;
962 4
                    case self::XLS_TYPE_CONTINUE:
963
                        $this->readContinue();
964
                        break;
965 4
                    case self::XLS_TYPE_EOF:
966 4
                        $this->readDefault();
967 4
                        break 2;
968
                    default:
969 4
                        $this->readDefault();
970 4
                        break;
971
                }
972
            }
973
974
            // treat MSODRAWING records, sheet-level Escher
975 4
            if (!$this->readDataOnly && $this->drawingData) {
976 2
                $escherWorksheet = new \PhpOffice\PhpSpreadsheet\Shared\Escher();
977 2
                $reader = new Xls\Escher($escherWorksheet);
978 2
                $escherWorksheet = $reader->load($this->drawingData);
979
980
                // get all spContainers in one long array, so they can be mapped to OBJ records
981 2
                $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
982
            }
983
984
            // treat OBJ records
985 4
            foreach ($this->objs as $n => $obj) {
986
                // the first shape container never has a corresponding OBJ record, hence $n + 1
987 2
                if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
988 2
                    $spContainer = $allSpContainers[$n + 1];
989
990
                    // we skip all spContainers that are a part of a group shape since we cannot yet handle those
991 2
                    if ($spContainer->getNestingLevel() > 1) {
992
                        continue;
993
                    }
994
995
                    // calculate the width and height of the shape
996 2
                    list($startColumn, $startRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($spContainer->getStartCoordinates());
997 2
                    list($endColumn, $endRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($spContainer->getEndCoordinates());
998
999 2
                    $startOffsetX = $spContainer->getStartOffsetX();
1000 2
                    $startOffsetY = $spContainer->getStartOffsetY();
1001 2
                    $endOffsetX = $spContainer->getEndOffsetX();
1002 2
                    $endOffsetY = $spContainer->getEndOffsetY();
1003
1004 2
                    $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
1005 2
                    $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
1006
1007
                    // calculate offsetX and offsetY of the shape
1008 2
                    $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024;
1009 2
                    $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256;
1010
1011 2
                    switch ($obj['otObjType']) {
1012 2
                        case 0x19:
1013
                            // Note
1014 1
                            if (isset($this->cellNotes[$obj['idObjID']])) {
1015 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...
1016
1017 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...
1018 1
                                    $textObject = $this->textObjects[$obj['idObjID']];
1019 1
                                    $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
1020
                                }
1021
                            }
1022 1
                            break;
1023 2
                        case 0x08:
1024
                            // picture
1025
                            // get index to BSE entry (1-based)
1026 2
                            $BSEindex = $spContainer->getOPT(0x0104);
1027
1028
                            // If there is no BSE Index, we will fail here and other fields are not read.
1029
                            // Fix by checking here.
1030
                            // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field?
1031
                            // More likely : a uncompatible picture
1032 2
                            if (!$BSEindex) {
1033
                                continue;
1034
                            }
1035
1036 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...
1037 2
                            $BSE = $BSECollection[$BSEindex - 1];
1038 2
                            $blipType = $BSE->getBlipType();
1039
1040
                            // need check because some blip types are not supported by Escher reader such as EMF
1041 2
                            if ($blip = $BSE->getBlip()) {
1042 2
                                $ih = imagecreatefromstring($blip->getData());
1043 2
                                $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing();
1044 2
                                $drawing->setImageResource($ih);
1045
1046
                                // width, height, offsetX, offsetY
1047 2
                                $drawing->setResizeProportional(false);
1048 2
                                $drawing->setWidth($width);
1049 2
                                $drawing->setHeight($height);
1050 2
                                $drawing->setOffsetX($offsetX);
1051 2
                                $drawing->setOffsetY($offsetY);
1052
1053
                                switch ($blipType) {
1054 2
                                    case \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE::BLIPTYPE_JPEG:
1055 2
                                        $drawing->setRenderingFunction(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_JPEG);
1056 2
                                        $drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_JPEG);
1057 2
                                        break;
1058 2
                                    case \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE::BLIPTYPE_PNG:
1059 2
                                        $drawing->setRenderingFunction(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_PNG);
1060 2
                                        $drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_PNG);
1061 2
                                        break;
1062
                                }
1063
1064 2
                                $drawing->setWorksheet($this->phpSheet);
1065 2
                                $drawing->setCoordinates($spContainer->getStartCoordinates());
1066
                            }
1067 2
                            break;
1068
                        default:
1069
                            // other object type
1070 2
                            break;
1071
                    }
1072
                }
1073
            }
1074
1075
            // treat SHAREDFMLA records
1076 4
            if ($this->version == self::XLS_BIFF8) {
1077 4
                foreach ($this->sharedFormulaParts as $cell => $baseCell) {
1078
                    list($column, $row) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($cell);
1079
                    if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
1080
                        $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
1081
                        $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA);
1082
                    }
1083
                }
1084
            }
1085
1086 4
            if (!empty($this->cellNotes)) {
1087 1
                foreach ($this->cellNotes as $note => $noteDetails) {
1088 1
                    if (!isset($noteDetails['objTextData'])) {
1089 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...
1090
                            $textObject = $this->textObjects[$note];
1091
                            $noteDetails['objTextData'] = $textObject;
1092
                        } else {
1093
                            $noteDetails['objTextData']['text'] = '';
1094
                        }
1095
                    }
1096 1
                    $cellAddress = str_replace('$', '', $noteDetails['cellRef']);
1097 4
                    $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
1098
                }
1099
            }
1100
        }
1101
1102
        // add the named ranges (defined names)
1103 4
        foreach ($this->definedname as $definedName) {
1104 1
            if ($definedName['isBuiltInName']) {
1105
                switch ($definedName['name']) {
1106
                    case pack('C', 0x06):
1107
                        // print area
1108
                        //    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...
1109
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1110
1111
                        $extractedRanges = [];
1112
                        foreach ($ranges as $range) {
1113
                            // $range should look like one of these
1114
                            //        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...
1115
                            //        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...
1116
                            $explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
1117
                            $sheetName = trim($explodes[0], "'");
1118
                            if (count($explodes) == 2) {
1119
                                if (strpos($explodes[1], ':') === false) {
1120
                                    $explodes[1] = $explodes[1] . ':' . $explodes[1];
1121
                                }
1122
                                $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
1123
                            }
1124
                        }
1125
                        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...
1126
                            $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
1127
                        }
1128
                        break;
1129
                    case pack('C', 0x07):
1130
                        // print titles (repeating rows)
1131
                        // Assuming BIFF8, there are 3 cases
1132
                        // 1. repeating rows
1133
                        //        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...
1134
                        //        rows 1-2 repeat
1135
                        // 2. repeating columns
1136
                        //        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...
1137
                        //        columns A-B repeat
1138
                        // 3. both repeating rows and repeating columns
1139
                        //        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...
1140
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1141
                        foreach ($ranges as $range) {
1142
                            // $range should look like this one of these
1143
                            //        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...
1144
                            //        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...
1145
                            $explodes = explode('!', $range);
1146
                            if (count($explodes) == 2) {
1147
                                if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
1148
                                    $extractedRange = $explodes[1];
1149
                                    $extractedRange = str_replace('$', '', $extractedRange);
1150
1151
                                    $coordinateStrings = explode(':', $extractedRange);
1152
                                    if (count($coordinateStrings) == 2) {
1153
                                        list($firstColumn, $firstRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($coordinateStrings[0]);
1154
                                        list($lastColumn, $lastRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($coordinateStrings[1]);
1155
1156
                                        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...
1157
                                            // then we have repeating rows
1158
                                            $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
1159
                                        } 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...
1160
                                            // then we have repeating columns
1161
                                            $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]);
1162
                                        }
1163
                                    }
1164
                                }
1165
                            }
1166
                        }
1167
                        break;
1168
                }
1169
            } else {
1170
                // Extract range
1171 1
                $explodes = explode('!', $definedName['formula']);
1172
1173 1
                if (count($explodes) == 2) {
1174 1
                    if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
1175 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...
1176 1
                        $extractedRange = $explodes[1];
1177 1
                        $extractedRange = str_replace('$', '', $extractedRange);
1178
1179 1
                        $localOnly = ($definedName['scope'] == 0) ? false : true;
1180
1181 1
                        $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
1182
1183 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...
1184
                    }
1185 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...
1186
                    //    Named Value
1187
                    //    TODO Provide support for named values
1188
                }
1189
            }
1190
        }
1191 4
        $this->data = null;
1192
1193 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...
1194
    }
1195
1196
    /**
1197
     * Read record data from stream, decrypting as required
1198
     *
1199
     * @param string $data   Data stream to read from
1200
     * @param int    $pos    Position to start reading from
1201
     * @param int    $len    Record data length
1202
     *
1203
     * @return string Record data
1204
     */
1205 4
    private function readRecordData($data, $pos, $len)
1206
    {
1207 4
        $data = substr($data, $pos, $len);
1208
1209
        // File not encrypted, or record before encryption start point
1210 4
        if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
1211 4
            return $data;
1212
        }
1213
1214
        $recordData = '';
1215
        if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
1216
            $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
1217
            $block = floor($pos / self::REKEY_BLOCK);
1218
            $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
1219
1220
            // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
1221
            // at a point earlier in the current block, re-use it as we can save some time.
1222
            if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
1223
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1224
                $step = $pos % self::REKEY_BLOCK;
1225
            } else {
1226
                $step = $pos - $this->rc4Pos;
1227
            }
1228
            $this->rc4Key->RC4(str_repeat("\0", $step));
1229
1230
            // Decrypt record data (re-keying at the end of every block)
1231
            while ($block != $endBlock) {
1232
                $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
1233
                $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
1234
                $data = substr($data, $step);
1235
                $pos += $step;
1236
                $len -= $step;
1237
                ++$block;
1238
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1239
            }
1240
            $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
1241
1242
            // Keep track of the position of this decryptor.
1243
            // We'll try and re-use it later if we can to speed things up
1244
            $this->rc4Pos = $pos + $len;
1245
        } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
1246
            throw new Exception('XOr encryption not supported');
1247
        }
1248
1249
        return $recordData;
1250
    }
1251
1252
    /**
1253
     * Use OLE reader to extract the relevant data streams from the OLE file
1254
     *
1255
     * @param string $pFilename
1256
     */
1257 4
    private function loadOLE($pFilename)
1258
    {
1259
        // OLE reader
1260 4
        $ole = new \PhpOffice\PhpSpreadsheet\Shared\OLERead();
1261
        // get excel data,
1262 4
        $ole->read($pFilename);
1263
        // Get workbook data: workbook stream + sheet streams
1264 4
        $this->data = $ole->getStream($ole->wrkbook);
1265
        // Get summary information data
1266 4
        $this->summaryInformation = $ole->getStream($ole->summaryInformation);
1267
        // Get additional document summary information data
1268 4
        $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1269 4
    }
1270
1271
    /**
1272
     * Read summary information
1273
     */
1274 4
    private function readSummaryInformation()
1275
    {
1276 4
        if (!isset($this->summaryInformation)) {
1277
            return;
1278
        }
1279
1280
        // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1281
        // 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...
1282
        // offset: 4; size: 2; OS version
1283
        // offset: 6; size: 2; OS indicator
1284
        // offset: 8; size: 16
1285
        // offset: 24; size: 4; section count
1286 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...
1287
1288
        // 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
1289
        // offset: 44; size: 4
1290 4
        $secOffset = self::getInt4d($this->summaryInformation, 44);
1291
1292
        // section header
1293
        // offset: $secOffset; size: 4; section length
1294 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...
1295
1296
        // offset: $secOffset+4; size: 4; property count
1297 4
        $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
1298
1299
        // initialize code page (used to resolve string values)
1300 4
        $codePage = 'CP1252';
1301
1302
        // 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...
1303
        // loop through property decarations and properties
1304 4
        for ($i = 0; $i < $countProperties; ++$i) {
1305
            // 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...
1306 4
            $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i));
1307
1308
            // Use value of property id as appropriate
1309
            // 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...
1310 4
            $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
1311
1312 4
            $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
1313
1314
            // initialize property value
1315 4
            $value = null;
1316
1317
            // extract property value based on property type
1318
            switch ($type) {
1319 4
                case 0x02: // 2 byte signed integer
1320 4
                    $value = self::getInt2d($this->summaryInformation, $secOffset + 4 + $offset);
1321 4
                    break;
1322 4
                case 0x03: // 4 byte signed integer
1323 4
                    $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1324 4
                    break;
1325 4
                case 0x13: // 4 byte unsigned integer
1326
                    // not needed yet, fix later if necessary
1327
                    break;
1328 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...
1329 4
                    $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1330 4
                    $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
1331 4
                    $value = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1332 4
                    $value = rtrim($value);
1333 4
                    break;
1334 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...
1335
                    // PHP-time
1336 4
                    $value = \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
1337 4
                    break;
1338
                case 0x47: // Clipboard format
1339
                    // not needed yet, fix later if necessary
1340
                    break;
1341
            }
1342
1343
            switch ($id) {
1344 4
                case 0x01:    //    Code Page
1345 4
                    $codePage = \PhpOffice\PhpSpreadsheet\Shared\CodePage::numberToName($value);
1346 4
                    break;
1347 4
                case 0x02:    //    Title
1348 3
                    $this->spreadsheet->getProperties()->setTitle($value);
1349 3
                    break;
1350 4
                case 0x03:    //    Subject
1351 3
                    $this->spreadsheet->getProperties()->setSubject($value);
1352 3
                    break;
1353 4
                case 0x04:    //    Author (Creator)
1354 4
                    $this->spreadsheet->getProperties()->setCreator($value);
1355 4
                    break;
1356 4
                case 0x05:    //    Keywords
1357 3
                    $this->spreadsheet->getProperties()->setKeywords($value);
1358 3
                    break;
1359 4
                case 0x06:    //    Comments (Description)
1360 3
                    $this->spreadsheet->getProperties()->setDescription($value);
1361 3
                    break;
1362 4
                case 0x07:    //    Template
1363
                    //    Not supported by PhpSpreadsheet
1364
                    break;
1365 4
                case 0x08:    //    Last Saved By (LastModifiedBy)
1366 4
                    $this->spreadsheet->getProperties()->setLastModifiedBy($value);
1367 4
                    break;
1368 4
                case 0x09:    //    Revision
1369
                    //    Not supported by PhpSpreadsheet
1370
                    break;
1371 4
                case 0x0A:    //    Total Editing Time
1372
                    //    Not supported by PhpSpreadsheet
1373
                    break;
1374 4
                case 0x0B:    //    Last Printed
1375
                    //    Not supported by PhpSpreadsheet
1376
                    break;
1377 4
                case 0x0C:    //    Created Date/Time
1378 4
                    $this->spreadsheet->getProperties()->setCreated($value);
1379 4
                    break;
1380 4
                case 0x0D:    //    Modified Date/Time
1381 4
                    $this->spreadsheet->getProperties()->setModified($value);
1382 4
                    break;
1383 4
                case 0x0E:    //    Number of Pages
1384
                    //    Not supported by PhpSpreadsheet
1385
                    break;
1386 4
                case 0x0F:    //    Number of Words
1387
                    //    Not supported by PhpSpreadsheet
1388
                    break;
1389 4
                case 0x10:    //    Number of Characters
1390
                    //    Not supported by PhpSpreadsheet
1391
                    break;
1392 4
                case 0x11:    //    Thumbnail
1393
                    //    Not supported by PhpSpreadsheet
1394
                    break;
1395 4
                case 0x12:    //    Name of creating application
1396
                    //    Not supported by PhpSpreadsheet
1397 2
                    break;
1398 4
                case 0x13:    //    Security
1399
                    //    Not supported by PhpSpreadsheet
1400 4
                    break;
1401
            }
1402
        }
1403 4
    }
1404
1405
    /**
1406
     * Read additional document summary information
1407
     */
1408 4
    private function readDocumentSummaryInformation()
1409
    {
1410 4
        if (!isset($this->documentSummaryInformation)) {
1411
            return;
1412
        }
1413
1414
        //    offset: 0;    size: 2;    must be 0xFE 0xFF (UTF-16 LE byte order mark)
1415
        //    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...
1416
        //    offset: 4;    size: 2;    OS version
1417
        //    offset: 6;    size: 2;    OS indicator
1418
        //    offset: 8;    size: 16
1419
        //    offset: 24;    size: 4;    section count
1420 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...
1421
1422
        // 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
1423
        // offset: 44;    size: 4;    first section offset
1424 4
        $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
1425
1426
        //    section header
1427
        //    offset: $secOffset;    size: 4;    section length
1428 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...
1429
1430
        //    offset: $secOffset+4;    size: 4;    property count
1431 4
        $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
1432
1433
        // initialize code page (used to resolve string values)
1434 4
        $codePage = 'CP1252';
1435
1436
        //    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...
1437
        //    loop through property decarations and properties
1438 4
        for ($i = 0; $i < $countProperties; ++$i) {
1439
            //    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...
1440 4
            $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i));
1441
1442
            // Use value of property id as appropriate
1443
            // offset: 60 + 8 * $i;    size: 4;    offset from beginning of section (48)
1444 4
            $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
1445
1446 4
            $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
1447
1448
            // initialize property value
1449 4
            $value = null;
1450
1451
            // extract property value based on property type
1452
            switch ($type) {
1453 4
                case 0x02:    //    2 byte signed integer
1454 4
                    $value = self::getInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1455 4
                    break;
1456 4
                case 0x03:    //    4 byte signed integer
1457 4
                    $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1458 4
                    break;
1459 4
                case 0x0B:  // Boolean
1460 4
                    $value = self::getInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1461 4
                    $value = ($value == 0 ? false : true);
1462 4
                    break;
1463 4
                case 0x13:    //    4 byte unsigned integer
1464
                    // not needed yet, fix later if necessary
1465
                    break;
1466 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...
1467 4
                    $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1468 4
                    $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1469 4
                    $value = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1470 4
                    $value = rtrim($value);
1471 4
                    break;
1472 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...
1473
                    // PHP-Time
1474
                    $value = \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
1475
                    break;
1476 4
                case 0x47:    //    Clipboard format
1477
                    // not needed yet, fix later if necessary
1478
                    break;
1479
            }
1480
1481
            switch ($id) {
1482 4
                case 0x01:    //    Code Page
1483 4
                    $codePage = \PhpOffice\PhpSpreadsheet\Shared\CodePage::numberToName($value);
1484 4
                    break;
1485 4
                case 0x02:    //    Category
1486 3
                    $this->spreadsheet->getProperties()->setCategory($value);
1487 3
                    break;
1488 4
                case 0x03:    //    Presentation Target
1489
                    //    Not supported by PhpSpreadsheet
1490
                    break;
1491 4
                case 0x04:    //    Bytes
1492
                    //    Not supported by PhpSpreadsheet
1493
                    break;
1494 4
                case 0x05:    //    Lines
1495
                    //    Not supported by PhpSpreadsheet
1496
                    break;
1497 4
                case 0x06:    //    Paragraphs
1498
                    //    Not supported by PhpSpreadsheet
1499
                    break;
1500 4
                case 0x07:    //    Slides
1501
                    //    Not supported by PhpSpreadsheet
1502
                    break;
1503 4
                case 0x08:    //    Notes
1504
                    //    Not supported by PhpSpreadsheet
1505
                    break;
1506 4
                case 0x09:    //    Hidden Slides
1507
                    //    Not supported by PhpSpreadsheet
1508
                    break;
1509 4
                case 0x0A:    //    MM Clips
1510
                    //    Not supported by PhpSpreadsheet
1511
                    break;
1512 4
                case 0x0B:    //    Scale Crop
1513
                    //    Not supported by PhpSpreadsheet
1514 4
                    break;
1515 4
                case 0x0C:    //    Heading Pairs
1516
                    //    Not supported by PhpSpreadsheet
1517 4
                    break;
1518 4
                case 0x0D:    //    Titles of Parts
1519
                    //    Not supported by PhpSpreadsheet
1520 4
                    break;
1521 4
                case 0x0E:    //    Manager
1522 1
                    $this->spreadsheet->getProperties()->setManager($value);
1523 1
                    break;
1524 4
                case 0x0F:    //    Company
1525 3
                    $this->spreadsheet->getProperties()->setCompany($value);
1526 3
                    break;
1527 4
                case 0x10:    //    Links up-to-date
1528
                    //    Not supported by PhpSpreadsheet
1529 4
                    break;
1530
            }
1531
        }
1532 4
    }
1533
1534
    /**
1535
     * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
1536
     */
1537 4
    private function readDefault()
1538
    {
1539 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1540
1541
        // move stream pointer to next record
1542 4
        $this->pos += 4 + $length;
1543 4
    }
1544
1545
    /**
1546
     *    The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
1547
     *        this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
1548
     */
1549 1
    private function readNote()
1550
    {
1551 1
        $length = self::getInt2d($this->data, $this->pos + 2);
1552 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1553
1554
        // move stream pointer to next record
1555 1
        $this->pos += 4 + $length;
1556
1557 1
        if ($this->readDataOnly) {
1558
            return;
1559
        }
1560
1561 1
        $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
1562 1
        if ($this->version == self::XLS_BIFF8) {
1563 1
            $noteObjID = self::getInt2d($recordData, 6);
1564 1
            $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
1565 1
            $noteAuthor = $noteAuthor['value'];
1566 1
            $this->cellNotes[$noteObjID] = [
1567 1
                'cellRef' => $cellAddress,
1568 1
                'objectID' => $noteObjID,
1569 1
                'author' => $noteAuthor,
1570
            ];
1571
        } else {
1572
            $extension = false;
1573
            if ($cellAddress == '$B$65536') {
1574
                //    If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
1575
                //        note from the previous cell annotation. We're not yet handling this, so annotations longer than the
1576
                //        max 2048 bytes will probably throw a wobbly.
1577
                $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...
1578
                $extension = true;
1579
                $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...
1580
            }
1581
1582
            $cellAddress = str_replace('$', '', $cellAddress);
1583
            $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...
1584
            $noteText = trim(substr($recordData, 6));
1585
1586
            if ($extension) {
1587
                //    Concatenate this extension with the currently set comment for the cell
1588
                $comment = $this->phpSheet->getComment($cellAddress);
1589
                $commentText = $comment->getText()->getPlainText();
1590
                $comment->setText($this->parseRichText($commentText . $noteText));
1591
            } else {
1592
                //    Set comment for the cell
1593
                $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
1594
//                                                    ->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...
1595
            }
1596
        }
1597 1
    }
1598
1599
    /**
1600
     *    The TEXT Object record contains the text associated with a cell annotation.
1601
     */
1602 1
    private function readTextObject()
1603
    {
1604 1
        $length = self::getInt2d($this->data, $this->pos + 2);
1605 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1606
1607
        // move stream pointer to next record
1608 1
        $this->pos += 4 + $length;
1609
1610 1
        if ($this->readDataOnly) {
1611
            return;
1612
        }
1613
1614
        // recordData consists of an array of subrecords looking like this:
1615
        //    grbit: 2 bytes; Option Flags
1616
        //    rot: 2 bytes; rotation
1617
        //    cchText: 2 bytes; length of the text (in the first continue record)
1618
        //    cbRuns: 2 bytes; length of the formatting (in the second continue record)
1619
        // followed by the continuation records containing the actual text and formatting
1620 1
        $grbitOpts = self::getInt2d($recordData, 0);
1621 1
        $rot = self::getInt2d($recordData, 2);
1622 1
        $cchText = self::getInt2d($recordData, 10);
1623 1
        $cbRuns = self::getInt2d($recordData, 12);
1624 1
        $text = $this->getSplicedRecordData();
1625
1626 1
        $this->textObjects[$this->textObjRef] = [
1627 1
            'text' => substr($text['recordData'], $text['spliceOffsets'][0] + 1, $cchText),
1628 1
            'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns),
1629 1
            'alignment' => $grbitOpts,
1630 1
            'rotation' => $rot,
1631
        ];
1632 1
    }
1633
1634
    /**
1635
     * Read BOF
1636
     */
1637 4
    private function readBof()
1638
    {
1639 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1640 4
        $recordData = substr($this->data, $this->pos + 4, $length);
1641
1642
        // move stream pointer to next record
1643 4
        $this->pos += 4 + $length;
1644
1645
        // offset: 2; size: 2; type of the following data
1646 4
        $substreamType = self::getInt2d($recordData, 2);
1647
1648
        switch ($substreamType) {
1649 4
            case self::XLS_WORKBOOKGLOBALS:
1650 4
                $version = self::getInt2d($recordData, 0);
1651 4
                if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1652
                    throw new Exception('Cannot read this Excel file. Version is too old.');
1653
                }
1654 4
                $this->version = $version;
1655 4
                break;
1656 4
            case self::XLS_WORKSHEET:
1657
                // do not use this version information for anything
1658
                // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
1659 4
                break;
1660
            default:
1661
                // substream, e.g. chart
1662
                // just skip the entire substream
1663
                do {
1664
                    $code = self::getInt2d($this->data, $this->pos);
1665
                    $this->readDefault();
1666
                } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
1667
                break;
1668
        }
1669 4
    }
1670
1671
    /**
1672
     * FILEPASS
1673
     *
1674
     * This record is part of the File Protection Block. It
1675
     * contains information about the read/write password of the
1676
     * file. All record contents following this record will be
1677
     * encrypted.
1678
     *
1679
     * --    "OpenOffice.org's Documentation of the Microsoft
1680
     *         Excel File Format"
1681
     *
1682
     * The decryption functions and objects used from here on in
1683
     * are based on the source of Spreadsheet-ParseExcel:
1684
     * http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/
1685
     */
1686
    private function readFilepass()
1687
    {
1688
        $length = self::getInt2d($this->data, $this->pos + 2);
1689
1690
        if ($length != 54) {
1691
            throw new Exception('Unexpected file pass record length');
1692
        }
1693
1694
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1695
1696
        // move stream pointer to next record
1697
        $this->pos += 4 + $length;
1698
1699
        if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
1700
            throw new Exception('Decryption password incorrect');
1701
        }
1702
1703
        $this->encryption = self::MS_BIFF_CRYPTO_RC4;
1704
1705
        // Decryption required from the record after next onwards
1706
        $this->encryptionStartPos = $this->pos + self::getInt2d($this->data, $this->pos + 2);
1707
    }
1708
1709
    /**
1710
     * Make an RC4 decryptor for the given block
1711
     *
1712
     * @param int         Block for which to create decrypto
1713
     * @param string $valContext MD5 context state
1714
     *
1715
     * @return Xls\RC4
1716
     */
1717
    private function makeKey($block, $valContext)
1718
    {
1719
        $pwarray = str_repeat("\0", 64);
1720
1721
        for ($i = 0; $i < 5; ++$i) {
1722
            $pwarray[$i] = $valContext[$i];
1723
        }
1724
1725
        $pwarray[5] = chr($block & 0xff);
1726
        $pwarray[6] = chr(($block >> 8) & 0xff);
1727
        $pwarray[7] = chr(($block >> 16) & 0xff);
1728
        $pwarray[8] = chr(($block >> 24) & 0xff);
1729
1730
        $pwarray[9] = "\x80";
1731
        $pwarray[56] = "\x48";
1732
1733
        $md5 = new Xls\MD5();
1734
        $md5->add($pwarray);
1735
1736
        $s = $md5->getContext();
1737
1738
        return new Xls\RC4($s);
1739
    }
1740
1741
    /**
1742
     * Verify RC4 file password
1743
     *
1744
     * @param string $password        Password to check
1745
     * @param string $docid           Document id
1746
     * @param string $salt_data       Salt data
1747
     * @param string $hashedsalt_data Hashed salt data
1748
     * @param string $valContext     Set to the MD5 context of the value
1749
     *
1750
     * @return bool Success
1751
     */
1752
    private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
1753
    {
1754
        $pwarray = str_repeat("\0", 64);
1755
1756
        for ($i = 0; $i < strlen($password); ++$i) {
1757
            $o = ord(substr($password, $i, 1));
1758
            $pwarray[2 * $i] = chr($o & 0xff);
1759
            $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1760
        }
1761
        $pwarray[2 * $i] = chr(0x80);
1762
        $pwarray[56] = chr(($i << 4) & 0xff);
1763
1764
        $md5 = new Xls\MD5();
1765
        $md5->add($pwarray);
1766
1767
        $mdContext1 = $md5->getContext();
1768
1769
        $offset = 0;
1770
        $keyoffset = 0;
1771
        $tocopy = 5;
1772
1773
        $md5->reset();
1774
1775
        while ($offset != 16) {
1776
            if ((64 - $offset) < 5) {
1777
                $tocopy = 64 - $offset;
1778
            }
1779
            for ($i = 0; $i <= $tocopy; ++$i) {
1780
                $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1781
            }
1782
            $offset += $tocopy;
1783
1784
            if ($offset == 64) {
1785
                $md5->add($pwarray);
1786
                $keyoffset = $tocopy;
1787
                $tocopy = 5 - $tocopy;
1788
                $offset = 0;
1789
                continue;
1790
            }
1791
1792
            $keyoffset = 0;
1793
            $tocopy = 5;
1794
            for ($i = 0; $i < 16; ++$i) {
1795
                $pwarray[$offset + $i] = $docid[$i];
1796
            }
1797
            $offset += 16;
1798
        }
1799
1800
        $pwarray[16] = "\x80";
1801
        for ($i = 0; $i < 47; ++$i) {
1802
            $pwarray[17 + $i] = "\0";
1803
        }
1804
        $pwarray[56] = "\x80";
1805
        $pwarray[57] = "\x0a";
1806
1807
        $md5->add($pwarray);
1808
        $valContext = $md5->getContext();
1809
1810
        $key = $this->makeKey(0, $valContext);
1811
1812
        $salt = $key->RC4($salt_data);
1813
        $hashedsalt = $key->RC4($hashedsalt_data);
1814
1815
        $salt .= "\x80" . str_repeat("\0", 47);
1816
        $salt[56] = "\x80";
1817
1818
        $md5->reset();
1819
        $md5->add($salt);
1820
        $mdContext2 = $md5->getContext();
1821
1822
        return $mdContext2 == $hashedsalt;
1823
    }
1824
1825
    /**
1826
     * CODEPAGE
1827
     *
1828
     * This record stores the text encoding used to write byte
1829
     * strings, stored as MS Windows code page identifier.
1830
     *
1831
     * --    "OpenOffice.org's Documentation of the Microsoft
1832
     *         Excel File Format"
1833
     */
1834 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...
1835
    {
1836 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1837 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1838
1839
        // move stream pointer to next record
1840 4
        $this->pos += 4 + $length;
1841
1842
        // offset: 0; size: 2; code page identifier
1843 4
        $codepage = self::getInt2d($recordData, 0);
1844
1845 4
        $this->codepage = \PhpOffice\PhpSpreadsheet\Shared\CodePage::numberToName($codepage);
1846 4
    }
1847
1848
    /**
1849
     * DATEMODE
1850
     *
1851
     * This record specifies the base date for displaying date
1852
     * values. All dates are stored as count of days past this
1853
     * base date. In BIFF2-BIFF4 this record is part of the
1854
     * Calculation Settings Block. In BIFF5-BIFF8 it is
1855
     * stored in the Workbook Globals Substream.
1856
     *
1857
     * --    "OpenOffice.org's Documentation of the Microsoft
1858
     *         Excel File Format"
1859
     */
1860 4
    private function readDateMode()
1861
    {
1862 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1863 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1864
1865
        // move stream pointer to next record
1866 4
        $this->pos += 4 + $length;
1867
1868
        // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
1869 4
        \PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar(\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_WINDOWS_1900);
1870 4
        if (ord($recordData{0}) == 1) {
1871
            \PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar(\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904);
1872
        }
1873 4
    }
1874
1875
    /**
1876
     * Read a FONT record
1877
     */
1878 4
    private function readFont()
1879
    {
1880 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1881 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1882
1883
        // move stream pointer to next record
1884 4
        $this->pos += 4 + $length;
1885
1886 4
        if (!$this->readDataOnly) {
1887 4
            $objFont = new \PhpOffice\PhpSpreadsheet\Style\Font();
1888
1889
            // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
1890 4
            $size = self::getInt2d($recordData, 0);
1891 4
            $objFont->setSize($size / 20);
1892
1893
            // offset: 2; size: 2; option flags
1894
            // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
1895
            // bit: 1; mask 0x0002; italic
1896 4
            $isItalic = (0x0002 & self::getInt2d($recordData, 2)) >> 1;
1897 4
            if ($isItalic) {
1898 4
                $objFont->setItalic(true);
1899
            }
1900
1901
            // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
1902
            // bit: 3; mask 0x0008; strike
1903 4
            $isStrike = (0x0008 & self::getInt2d($recordData, 2)) >> 3;
1904 4
            if ($isStrike) {
1905
                $objFont->setStrikethrough(true);
1906
            }
1907
1908
            // offset: 4; size: 2; colour index
1909 4
            $colorIndex = self::getInt2d($recordData, 4);
1910 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...
1911
1912
            // offset: 6; size: 2; font weight
1913 4
            $weight = self::getInt2d($recordData, 6);
1914
            switch ($weight) {
1915 4
                case 0x02BC:
1916 4
                    $objFont->setBold(true);
1917 4
                    break;
1918
            }
1919
1920
            // offset: 8; size: 2; escapement type
1921 4
            $escapement = self::getInt2d($recordData, 8);
1922
            switch ($escapement) {
1923 4
                case 0x0001:
1924
                    $objFont->setSuperScript(true);
1925
                    break;
1926 4
                case 0x0002:
1927
                    $objFont->setSubScript(true);
1928
                    break;
1929
            }
1930
1931
            // offset: 10; size: 1; underline type
1932 4
            $underlineType = ord($recordData{10});
1933
            switch ($underlineType) {
1934 4
                case 0x00:
1935 4
                    break; // no underline
1936 2
                case 0x01:
1937 2
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
1938 2
                    break;
1939
                case 0x02:
1940
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE);
1941
                    break;
1942
                case 0x21:
1943
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING);
1944
                    break;
1945
                case 0x22:
1946
                    $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING);
1947
                    break;
1948
            }
1949
1950
            // offset: 11; size: 1; font family
1951
            // offset: 12; size: 1; character set
1952
            // offset: 13; size: 1; not used
1953
            // offset: 14; size: var; font name
1954 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...
1955 4
                $string = self::readUnicodeStringShort(substr($recordData, 14));
1956
            } else {
1957
                $string = $this->readByteStringShort(substr($recordData, 14));
1958
            }
1959 4
            $objFont->setName($string['value']);
1960
1961 4
            $this->objFonts[] = $objFont;
1962
        }
1963 4
    }
1964
1965
    /**
1966
     * FORMAT
1967
     *
1968
     * This record contains information about a number format.
1969
     * All FORMAT records occur together in a sequential list.
1970
     *
1971
     * In BIFF2-BIFF4 other records referencing a FORMAT record
1972
     * contain a zero-based index into this list. From BIFF5 on
1973
     * the FORMAT record contains the index itself that will be
1974
     * used by other records.
1975
     *
1976
     * --    "OpenOffice.org's Documentation of the Microsoft
1977
     *         Excel File Format"
1978
     */
1979 4
    private function readFormat()
1980
    {
1981 4
        $length = self::getInt2d($this->data, $this->pos + 2);
1982 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1983
1984
        // move stream pointer to next record
1985 4
        $this->pos += 4 + $length;
1986
1987 4
        if (!$this->readDataOnly) {
1988 4
            $indexCode = self::getInt2d($recordData, 0);
1989
1990 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...
1991 4
                $string = self::readUnicodeStringLong(substr($recordData, 2));
1992
            } else {
1993
                // BIFF7
1994
                $string = $this->readByteStringShort(substr($recordData, 2));
1995
            }
1996
1997 4
            $formatString = $string['value'];
1998 4
            $this->formats[$indexCode] = $formatString;
1999
        }
2000 4
    }
2001
2002
    /**
2003
     * XF - Extended Format
2004
     *
2005
     * This record contains formatting information for cells, rows, columns or styles.
2006
     * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2007
     * and 1 cell XF.
2008
     * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2009
     * and XF record 15 is a cell XF
2010
     * We only read the first cell style XF and skip the remaining cell style XF records
2011
     * We read all cell XF records.
2012
     *
2013
     * --    "OpenOffice.org's Documentation of the Microsoft
2014
     *         Excel File Format"
2015
     */
2016 4
    private function readXf()
2017
    {
2018 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2019 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2020
2021
        // move stream pointer to next record
2022 4
        $this->pos += 4 + $length;
2023
2024 4
        $objStyle = new \PhpOffice\PhpSpreadsheet\Style();
2025
2026 4
        if (!$this->readDataOnly) {
2027
            // offset:  0; size: 2; Index to FONT record
2028 4
            if (self::getInt2d($recordData, 0) < 4) {
2029 4
                $fontIndex = self::getInt2d($recordData, 0);
2030
            } else {
2031
                // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2032
                // check the OpenOffice documentation of the FONT record
2033 4
                $fontIndex = self::getInt2d($recordData, 0) - 1;
2034
            }
2035 4
            $objStyle->setFont($this->objFonts[$fontIndex]);
2036
2037
            // offset:  2; size: 2; Index to FORMAT record
2038 4
            $numberFormatIndex = self::getInt2d($recordData, 2);
2039 4
            if (isset($this->formats[$numberFormatIndex])) {
2040
                // then we have user-defined format code
2041 3
                $numberformat = ['code' => $this->formats[$numberFormatIndex]];
2042 4
            } elseif (($code = \PhpOffice\PhpSpreadsheet\Style\NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2043
                // then we have built-in format code
2044 4
                $numberformat = ['code' => $code];
2045
            } else {
2046
                // we set the general format code
2047
                $numberformat = ['code' => 'General'];
2048
            }
2049 4
            $objStyle->getNumberFormat()->setFormatCode($numberformat['code']);
2050
2051
            // offset:  4; size: 2; XF type, cell protection, and parent style XF
2052
            // 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...
2053 4
            $xfTypeProt = self::getInt2d($recordData, 4);
2054
            // bit 0; mask 0x01; 1 = cell is locked
2055 4
            $isLocked = (0x01 & $xfTypeProt) >> 0;
2056 4
            $objStyle->getProtection()->setLocked($isLocked ? \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_INHERIT : \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED);
2057
2058
            // bit 1; mask 0x02; 1 = Formula is hidden
2059 4
            $isHidden = (0x02 & $xfTypeProt) >> 1;
2060 4
            $objStyle->getProtection()->setHidden($isHidden ? \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_PROTECTED : \PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED);
2061
2062
            // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2063 4
            $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2064
2065
            // offset:  6; size: 1; Alignment and text break
2066
            // bit 2-0, mask 0x07; horizontal alignment
2067 4
            $horAlign = (0x07 & ord($recordData{6})) >> 0;
2068
            switch ($horAlign) {
2069 4
                case 0:
2070 4
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_GENERAL);
2071 4
                    break;
2072 2
                case 1:
2073 2
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
2074 2
                    break;
2075 2
                case 2:
2076
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
2077
                    break;
2078 2
                case 3:
2079 2
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT);
2080 2
                    break;
2081 2
                case 4:
2082
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_FILL);
2083
                    break;
2084 2
                case 5:
2085 2
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_JUSTIFY);
2086 2
                    break;
2087
                case 6:
2088
                    $objStyle->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2089
                    break;
2090
            }
2091
            // bit 3, mask 0x08; wrap text
2092 4
            $wrapText = (0x08 & ord($recordData{6})) >> 3;
2093
            switch ($wrapText) {
2094 4
                case 0:
2095 4
                    $objStyle->getAlignment()->setWrapText(false);
2096 4
                    break;
2097 2
                case 1:
2098 2
                    $objStyle->getAlignment()->setWrapText(true);
2099 2
                    break;
2100
            }
2101
            // bit 6-4, mask 0x70; vertical alignment
2102 4
            $vertAlign = (0x70 & ord($recordData{6})) >> 4;
2103
            switch ($vertAlign) {
2104 4
                case 0:
2105
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP);
2106
                    break;
2107 4
                case 1:
2108 2
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
2109 2
                    break;
2110 4
                case 2:
2111 4
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_BOTTOM);
2112 4
                    break;
2113
                case 3:
2114
                    $objStyle->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_JUSTIFY);
2115
                    break;
2116
            }
2117
2118 4
            if ($this->version == self::XLS_BIFF8) {
2119
                // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2120 4
                $angle = ord($recordData{7});
2121 4
                $rotation = 0;
2122 4
                if ($angle <= 90) {
2123 4
                    $rotation = $angle;
2124
                } elseif ($angle <= 180) {
2125
                    $rotation = 90 - $angle;
2126
                } elseif ($angle == 255) {
2127
                    $rotation = -165;
2128
                }
2129 4
                $objStyle->getAlignment()->setTextRotation($rotation);
2130
2131
                // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2132
                // bit: 3-0; mask: 0x0F; indent level
2133 4
                $indent = (0x0F & ord($recordData{8})) >> 0;
2134 4
                $objStyle->getAlignment()->setIndent($indent);
2135
2136
                // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2137 4
                $shrinkToFit = (0x10 & ord($recordData{8})) >> 4;
2138
                switch ($shrinkToFit) {
2139 4
                    case 0:
2140 4
                        $objStyle->getAlignment()->setShrinkToFit(false);
2141 4
                        break;
2142 1
                    case 1:
2143 1
                        $objStyle->getAlignment()->setShrinkToFit(true);
2144 1
                        break;
2145
                }
2146
2147
                // offset:  9; size: 1; Flags used for attribute groups
2148
2149
                // offset: 10; size: 4; Cell border lines and background area
2150
                // bit: 3-0; mask: 0x0000000F; left style
2151 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...
2152 4
                    $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2153
                }
2154
                // bit: 7-4; mask: 0x000000F0; right style
2155 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...
2156 4
                    $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2157
                }
2158
                // bit: 11-8; mask: 0x00000F00; top style
2159 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...
2160 4
                    $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2161
                }
2162
                // bit: 15-12; mask: 0x0000F000; bottom style
2163 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...
2164 4
                    $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2165
                }
2166
                // bit: 22-16; mask: 0x007F0000; left color
2167 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...
2168
2169
                // bit: 29-23; mask: 0x3F800000; right color
2170 4
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2171
2172
                // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2173 4
                $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2174
2175
                // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2176 4
                $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2177
2178 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...
2179 4
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_NONE);
2180
                } 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...
2181
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_UP);
2182
                } 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...
2183
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_DOWN);
2184
                } 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...
2185
                    $objStyle->getBorders()->setDiagonalDirection(\PhpOffice\PhpSpreadsheet\Style\Borders::DIAGONAL_BOTH);
2186
                }
2187
2188
                // 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...
2189
                // bit: 6-0; mask: 0x0000007F; top color
2190 4
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2191
2192
                // bit: 13-7; mask: 0x00003F80; bottom color
2193 4
                $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2194
2195
                // bit: 20-14; mask: 0x001FC000; diagonal color
2196 4
                $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2197
2198
                // bit: 24-21; mask: 0x01E00000; diagonal style
2199 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...
2200 4
                    $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2201
                }
2202
2203
                // bit: 31-26; mask: 0xFC000000 fill pattern
2204 4
                if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2205 4
                    $objStyle->getFill()->setFillType($fillType);
2206
                }
2207
                // offset: 18; size: 2; pattern and background colour
2208
                // bit: 6-0; mask: 0x007F; color index for pattern color
2209 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...
2210
2211
                // bit: 13-7; mask: 0x3F80; color index for pattern background
2212 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...
2213
            } else {
2214
                // BIFF5
2215
2216
                // offset: 7; size: 1; Text orientation and flags
2217
                $orientationAndFlags = ord($recordData{7});
2218
2219
                // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2220
                $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2221
                switch ($xfOrientation) {
2222
                    case 0:
2223
                        $objStyle->getAlignment()->setTextRotation(0);
2224
                        break;
2225
                    case 1:
2226
                        $objStyle->getAlignment()->setTextRotation(-165);
2227
                        break;
2228
                    case 2:
2229
                        $objStyle->getAlignment()->setTextRotation(90);
2230
                        break;
2231
                    case 3:
2232
                        $objStyle->getAlignment()->setTextRotation(-90);
2233
                        break;
2234
                }
2235
2236
                // offset: 8; size: 4; cell border lines and background area
2237
                $borderAndBackground = self::getInt4d($recordData, 8);
2238
2239
                // bit: 6-0; mask: 0x0000007F; color index for pattern color
2240
                $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...
2241
2242
                // bit: 13-7; mask: 0x00003F80; color index for pattern background
2243
                $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...
2244
2245
                // bit: 21-16; mask: 0x003F0000; fill pattern
2246
                $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
2247
2248
                // bit: 24-22; mask: 0x01C00000; bottom line style
2249
                $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
2250
2251
                // bit: 31-25; mask: 0xFE000000; bottom line color
2252
                $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2253
2254
                // offset: 12; size: 4; cell border lines
2255
                $borderLines = self::getInt4d($recordData, 12);
2256
2257
                // bit: 2-0; mask: 0x00000007; top line style
2258
                $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
2259
2260
                // bit: 5-3; mask: 0x00000038; left line style
2261
                $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
2262
2263
                // bit: 8-6; mask: 0x000001C0; right line style
2264
                $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
2265
2266
                // bit: 15-9; mask: 0x0000FE00; top line color index
2267
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2268
2269
                // bit: 22-16; mask: 0x007F0000; left line color index
2270
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2271
2272
                // bit: 29-23; mask: 0x3F800000; right line color index
2273
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2274
            }
2275
2276
            // add cellStyleXf or cellXf and update mapping
2277 4
            if ($isCellStyleXf) {
2278
                // we only read one style XF record which is always the first
2279 4
                if ($this->xfIndex == 0) {
2280 4
                    $this->spreadsheet->addCellStyleXf($objStyle);
2281 4
                    $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2282
                }
2283
            } else {
2284
                // we read all cell XF records
2285 4
                $this->spreadsheet->addCellXf($objStyle);
2286 4
                $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
2287
            }
2288
2289
            // update XF index for when we read next record
2290 4
            ++$this->xfIndex;
2291
        }
2292 4
    }
2293
2294
    /**
2295
     *
2296
     */
2297 3
    private function readXfExt()
2298
    {
2299 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2300 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2301
2302
        // move stream pointer to next record
2303 3
        $this->pos += 4 + $length;
2304
2305 3
        if (!$this->readDataOnly) {
2306
            // offset: 0; size: 2; 0x087D = repeated header
2307
2308
            // offset: 2; size: 2
2309
2310
            // offset: 4; size: 8; not used
2311
2312
            // offset: 12; size: 2; record version
2313
2314
            // offset: 14; size: 2; index to XF record which this record modifies
2315 3
            $ixfe = self::getInt2d($recordData, 14);
2316
2317
            // offset: 16; size: 2; not used
2318
2319
            // offset: 18; size: 2; number of extension properties that follow
2320 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...
2321
2322
            // start reading the actual extension data
2323 3
            $offset = 20;
2324 3
            while ($offset < $length) {
2325
                // extension type
2326 3
                $extType = self::getInt2d($recordData, $offset);
2327
2328
                // extension length
2329 3
                $cb = self::getInt2d($recordData, $offset + 2);
2330
2331
                // extension data
2332 3
                $extData = substr($recordData, $offset + 4, $cb);
2333
2334
                switch ($extType) {
2335 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...
2336 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2337 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2338
2339 3
                        if ($xclfType == 2) {
2340 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2341
2342
                            // modify the relevant style property
2343 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2344 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2345 1
                                $fill->getStartColor()->setRGB($rgb);
2346 1
                                unset($fill->startcolorIndex); // normal color index does not apply, discard
2347
                            }
2348
                        }
2349 3
                        break;
2350 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...
2351 1
                        $xclfType = self::getInt2d($extData, 0); // color type
2352 1
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2353
2354 1
                        if ($xclfType == 2) {
2355 1
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2356
2357
                            // modify the relevant style property
2358 1
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2359 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2360 1
                                $fill->getEndColor()->setRGB($rgb);
2361 1
                                unset($fill->endcolorIndex); // normal color index does not apply, discard
2362
                            }
2363
                        }
2364 1
                        break;
2365 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...
2366 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2367 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2368
2369 3
                        if ($xclfType == 2) {
2370 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2371
2372
                            // modify the relevant style property
2373 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2374 1
                                $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
2375 1
                                $top->getColor()->setRGB($rgb);
2376 1
                                unset($top->colorIndex); // normal color index does not apply, discard
2377
                            }
2378
                        }
2379 3
                        break;
2380 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...
2381 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2382 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2383
2384 3
                        if ($xclfType == 2) {
2385 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2386
2387
                            // modify the relevant style property
2388 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2389 1
                                $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2390 1
                                $bottom->getColor()->setRGB($rgb);
2391 1
                                unset($bottom->colorIndex); // normal color index does not apply, discard
2392
                            }
2393
                        }
2394 3
                        break;
2395 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...
2396 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2397 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2398
2399 3
                        if ($xclfType == 2) {
2400 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2401
2402
                            // modify the relevant style property
2403 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2404 1
                                $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2405 1
                                $left->getColor()->setRGB($rgb);
2406 1
                                unset($left->colorIndex); // normal color index does not apply, discard
2407
                            }
2408
                        }
2409 3
                        break;
2410 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...
2411 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2412 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2413
2414 3
                        if ($xclfType == 2) {
2415 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2416
2417
                            // modify the relevant style property
2418 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2419 1
                                $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
2420 1
                                $right->getColor()->setRGB($rgb);
2421 1
                                unset($right->colorIndex); // normal color index does not apply, discard
2422
                            }
2423
                        }
2424 3
                        break;
2425 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...
2426
                        $xclfType = self::getInt2d($extData, 0); // color type
2427
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2428
2429
                        if ($xclfType == 2) {
2430
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2431
2432
                            // modify the relevant style property
2433
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2434
                                $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2435
                                $diagonal->getColor()->setRGB($rgb);
2436
                                unset($diagonal->colorIndex); // normal color index does not apply, discard
2437
                            }
2438
                        }
2439
                        break;
2440 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...
2441 3
                        $xclfType = self::getInt2d($extData, 0); // color type
2442 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2443
2444 3
                        if ($xclfType == 2) {
2445 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2446
2447
                            // modify the relevant style property
2448 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2449 1
                                $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2450 1
                                $font->getColor()->setRGB($rgb);
2451 1
                                unset($font->colorIndex); // normal color index does not apply, discard
2452
                            }
2453
                        }
2454 3
                        break;
2455
                }
2456
2457 3
                $offset += $cb;
2458
            }
2459
        }
2460 3
    }
2461
2462
    /**
2463
     * Read STYLE record
2464
     */
2465 4
    private function readStyle()
2466
    {
2467 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2468 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2469
2470
        // move stream pointer to next record
2471 4
        $this->pos += 4 + $length;
2472
2473 4
        if (!$this->readDataOnly) {
2474
            // offset: 0; size: 2; index to XF record and flag for built-in style
2475 4
            $ixfe = self::getInt2d($recordData, 0);
2476
2477
            // bit: 11-0; mask 0x0FFF; index to XF record
2478 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...
2479
2480
            // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2481 4
            $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2482
2483 4
            if ($isBuiltIn) {
2484
                // offset: 2; size: 1; identifier for built-in style
2485 4
                $builtInId = ord($recordData{2});
2486
2487
                switch ($builtInId) {
2488 4
                    case 0x00:
2489
                        // currently, we are not using this for anything
2490 4
                        break;
2491
                    default:
2492 4
                        break;
2493
                }
2494
            } 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...
2495
                // user-defined; not supported by PhpSpreadsheet
2496
            }
2497
        }
2498 4
    }
2499
2500
    /**
2501
     * Read PALETTE record
2502
     */
2503 2
    private function readPalette()
2504
    {
2505 2
        $length = self::getInt2d($this->data, $this->pos + 2);
2506 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2507
2508
        // move stream pointer to next record
2509 2
        $this->pos += 4 + $length;
2510
2511 2
        if (!$this->readDataOnly) {
2512
            // offset: 0; size: 2; number of following colors
2513 2
            $nm = self::getInt2d($recordData, 0);
2514
2515
            // list of RGB colors
2516 2
            for ($i = 0; $i < $nm; ++$i) {
2517 2
                $rgb = substr($recordData, 2 + 4 * $i, 4);
2518 2
                $this->palette[] = self::readRGB($rgb);
2519
            }
2520
        }
2521 2
    }
2522
2523
    /**
2524
     * SHEET
2525
     *
2526
     * This record is  located in the  Workbook Globals
2527
     * Substream  and represents a sheet inside the workbook.
2528
     * One SHEET record is written for each sheet. It stores the
2529
     * sheet name and a stream offset to the BOF record of the
2530
     * respective Sheet Substream within the Workbook Stream.
2531
     *
2532
     * --    "OpenOffice.org's Documentation of the Microsoft
2533
     *         Excel File Format"
2534
     */
2535 4
    private function readSheet()
2536
    {
2537 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2538 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2539
2540
        // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2541
        // NOTE: not encrypted
2542 4
        $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2543
2544
        // move stream pointer to next record
2545 4
        $this->pos += 4 + $length;
2546
2547
        // offset: 4; size: 1; sheet state
2548 4
        switch (ord($recordData{4})) {
2549 4
            case 0x00:
2550 4
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE;
2551 4
                break;
2552
            case 0x01:
2553
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_HIDDEN;
2554
                break;
2555
            case 0x02:
2556
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VERYHIDDEN;
2557
                break;
2558
            default:
2559
                $sheetState = \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE;
2560
                break;
2561
        }
2562
2563
        // offset: 5; size: 1; sheet type
2564 4
        $sheetType = ord($recordData{5});
2565
2566
        // offset: 6; size: var; sheet name
2567 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...
2568 4
            $string = self::readUnicodeStringShort(substr($recordData, 6));
2569 4
            $rec_name = $string['value'];
2570
        } elseif ($this->version == self::XLS_BIFF7) {
2571
            $string = $this->readByteStringShort(substr($recordData, 6));
2572
            $rec_name = $string['value'];
2573
        }
2574
2575 4
        $this->sheets[] = [
2576 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...
2577 4
            'offset' => $rec_offset,
2578 4
            'sheetState' => $sheetState,
2579 4
            'sheetType' => $sheetType,
2580
        ];
2581 4
    }
2582
2583
    /**
2584
     * Read EXTERNALBOOK record
2585
     */
2586 3
    private function readExternalBook()
2587
    {
2588 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2589 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2590
2591
        // move stream pointer to next record
2592 3
        $this->pos += 4 + $length;
2593
2594
        // offset within record data
2595 3
        $offset = 0;
2596
2597
        // there are 4 types of records
2598 3
        if (strlen($recordData) > 4) {
2599
            // external reference
2600
            // offset: 0; size: 2; number of sheet names ($nm)
2601
            $nm = self::getInt2d($recordData, 0);
2602
            $offset += 2;
2603
2604
            // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2605
            $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2606
            $offset += $encodedUrlString['size'];
2607
2608
            // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2609
            $externalSheetNames = [];
2610
            for ($i = 0; $i < $nm; ++$i) {
2611
                $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2612
                $externalSheetNames[] = $externalSheetNameString['value'];
2613
                $offset += $externalSheetNameString['size'];
2614
            }
2615
2616
            // store the record data
2617
            $this->externalBooks[] = [
2618
                'type' => 'external',
2619
                'encodedUrl' => $encodedUrlString['value'],
2620
                'externalSheetNames' => $externalSheetNames,
2621
            ];
2622 3
        } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2623
            // internal reference
2624
            // offset: 0; size: 2; number of sheet in this document
2625
            // 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...
2626 3
            $this->externalBooks[] = [
2627 3
                'type' => 'internal',
2628
            ];
2629
        } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2630
            // add-in function
2631
            // 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...
2632
            $this->externalBooks[] = [
2633
                'type' => 'addInFunction',
2634
            ];
2635
        } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2636
            // DDE links, OLE links
2637
            // 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...
2638
            // offset: 2; size: var; encoded source document name
2639
            $this->externalBooks[] = [
2640
                'type' => 'DDEorOLE',
2641
            ];
2642
        }
2643 3
    }
2644
2645
    /**
2646
     * Read EXTERNNAME record.
2647
     */
2648
    private function readExternName()
2649
    {
2650
        $length = self::getInt2d($this->data, $this->pos + 2);
2651
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2652
2653
        // move stream pointer to next record
2654
        $this->pos += 4 + $length;
2655
2656
        // external sheet references provided for named cells
2657
        if ($this->version == self::XLS_BIFF8) {
2658
            // offset: 0; size: 2; options
2659
            $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...
2660
2661
            // 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...
2662
2663
            // offset: 4; size: 2; not used
2664
2665
            // offset: 6; size: var
2666
            $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2667
2668
            // offset: var; size: var; formula data
2669
            $offset = 6 + $nameString['size'];
2670
            $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2671
2672
            $this->externalNames[] = [
2673
                'name' => $nameString['value'],
2674
                'formula' => $formula,
2675
            ];
2676
        }
2677
    }
2678
2679
    /**
2680
     * Read EXTERNSHEET record
2681
     */
2682 3
    private function readExternSheet()
2683
    {
2684 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2685 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2686
2687
        // move stream pointer to next record
2688 3
        $this->pos += 4 + $length;
2689
2690
        // external sheet references provided for named cells
2691 3
        if ($this->version == self::XLS_BIFF8) {
2692
            // offset: 0; size: 2; number of following ref structures
2693 3
            $nm = self::getInt2d($recordData, 0);
2694 3
            for ($i = 0; $i < $nm; ++$i) {
2695 3
                $this->ref[] = [
2696
                    // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2697 3
                    'externalBookIndex' => self::getInt2d($recordData, 2 + 6 * $i),
2698
                    // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2699 3
                    'firstSheetIndex' => self::getInt2d($recordData, 4 + 6 * $i),
2700
                    // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2701 3
                    'lastSheetIndex' => self::getInt2d($recordData, 6 + 6 * $i),
2702
                ];
2703
            }
2704
        }
2705 3
    }
2706
2707
    /**
2708
     * DEFINEDNAME
2709
     *
2710
     * This record is part of a Link Table. It contains the name
2711
     * and the token array of an internal defined name. Token
2712
     * arrays of defined names contain tokens with aberrant
2713
     * token classes.
2714
     *
2715
     * --    "OpenOffice.org's Documentation of the Microsoft
2716
     *         Excel File Format"
2717
     */
2718 1
    private function readDefinedName()
2719
    {
2720 1
        $length = self::getInt2d($this->data, $this->pos + 2);
2721 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2722
2723
        // move stream pointer to next record
2724 1
        $this->pos += 4 + $length;
2725
2726 1
        if ($this->version == self::XLS_BIFF8) {
2727
            // retrieves named cells
2728
2729
            // offset: 0; size: 2; option flags
2730 1
            $opts = self::getInt2d($recordData, 0);
2731
2732
            // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2733 1
            $isBuiltInName = (0x0020 & $opts) >> 5;
2734
2735
            // offset: 2; size: 1; keyboard shortcut
2736
2737
            // offset: 3; size: 1; length of the name (character count)
2738 1
            $nlen = ord($recordData{3});
2739
2740
            // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2741
            // note: there can also be additional data, this is not included in $flen
2742 1
            $flen = self::getInt2d($recordData, 4);
2743
2744
            // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2745 1
            $scope = self::getInt2d($recordData, 8);
2746
2747
            // offset: 14; size: var; Name (Unicode string without length field)
2748 1
            $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2749
2750
            // offset: var; size: $flen; formula data
2751 1
            $offset = 14 + $string['size'];
2752 1
            $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2753
2754
            try {
2755 1
                $formula = $this->getFormulaFromStructure($formulaStructure);
2756
            } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
2757
                $formula = '';
2758
            }
2759
2760 1
            $this->definedname[] = [
2761 1
                'isBuiltInName' => $isBuiltInName,
2762 1
                'name' => $string['value'],
2763 1
                'formula' => $formula,
2764 1
                'scope' => $scope,
2765
            ];
2766
        }
2767 1
    }
2768
2769
    /**
2770
     * Read MSODRAWINGGROUP record
2771
     */
2772 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...
2773
    {
2774 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...
2775
2776
        // get spliced record data
2777 3
        $splicedRecordData = $this->getSplicedRecordData();
2778 3
        $recordData = $splicedRecordData['recordData'];
2779
2780 3
        $this->drawingGroupData .= $recordData;
2781 3
    }
2782
2783
    /**
2784
     * SST - Shared String Table
2785
     *
2786
     * This record contains a list of all strings used anywhere
2787
     * in the workbook. Each string occurs only once. The
2788
     * workbook uses indexes into the list to reference the
2789
     * strings.
2790
     *
2791
     * --    "OpenOffice.org's Documentation of the Microsoft
2792
     *         Excel File Format"
2793
     **/
2794 4
    private function readSst()
2795
    {
2796
        // offset within (spliced) record data
2797 4
        $pos = 0;
2798
2799
        // get spliced record data
2800 4
        $splicedRecordData = $this->getSplicedRecordData();
2801
2802 4
        $recordData = $splicedRecordData['recordData'];
2803 4
        $spliceOffsets = $splicedRecordData['spliceOffsets'];
2804
2805
        // offset: 0; size: 4; total number of strings in the workbook
2806 4
        $pos += 4;
2807
2808
        // offset: 4; size: 4; number of following strings ($nm)
2809 4
        $nm = self::getInt4d($recordData, 4);
2810 4
        $pos += 4;
2811
2812
        // loop through the Unicode strings (16-bit length)
2813 4
        for ($i = 0; $i < $nm; ++$i) {
2814
            // number of characters in the Unicode string
2815 4
            $numChars = self::getInt2d($recordData, $pos);
2816 4
            $pos += 2;
2817
2818
            // option flags
2819 4
            $optionFlags = ord($recordData{$pos});
2820 4
            ++$pos;
2821
2822
            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2823 4
            $isCompressed = (($optionFlags & 0x01) == 0);
2824
2825
            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2826 4
            $hasAsian = (($optionFlags & 0x04) != 0);
2827
2828
            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
2829 4
            $hasRichText = (($optionFlags & 0x08) != 0);
2830
2831 4
            if ($hasRichText) {
2832
                // number of Rich-Text formatting runs
2833 2
                $formattingRuns = self::getInt2d($recordData, $pos);
2834 2
                $pos += 2;
2835
            }
2836
2837 4
            if ($hasAsian) {
2838
                // size of Asian phonetic setting
2839
                $extendedRunLength = self::getInt4d($recordData, $pos);
2840
                $pos += 4;
2841
            }
2842
2843
            // expected byte length of character array if not split
2844 4
            $len = ($isCompressed) ? $numChars : $numChars * 2;
2845
2846
            // look up limit position
2847 4
            foreach ($spliceOffsets as $spliceOffset) {
2848
                // it can happen that the string is empty, therefore we need
2849
                // <= and not just <
2850 4
                if ($pos <= $spliceOffset) {
2851 4
                    $limitpos = $spliceOffset;
2852 4
                    break;
2853
                }
2854
            }
2855
2856 4
            if ($pos + $len <= $limitpos) {
2857
                // character array is not split between records
2858
2859 4
                $retstr = substr($recordData, $pos, $len);
2860 4
                $pos += $len;
2861
            } else {
2862
                // character array is split between records
2863
2864
                // first part of character array
2865
                $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...
2866
2867
                $bytesRead = $limitpos - $pos;
2868
2869
                // remaining characters in Unicode string
2870
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
2871
2872
                $pos = $limitpos;
2873
2874
                // keep reading the characters
2875
                while ($charsLeft > 0) {
2876
                    // look up next limit position, in case the string span more than one continue record
2877
                    foreach ($spliceOffsets as $spliceOffset) {
2878
                        if ($pos < $spliceOffset) {
2879
                            $limitpos = $spliceOffset;
2880
                            break;
2881
                        }
2882
                    }
2883
2884
                    // repeated option flags
2885
                    // OpenOffice.org documentation 5.21
2886
                    $option = ord($recordData{$pos});
2887
                    ++$pos;
2888
2889
                    if ($isCompressed && ($option == 0)) {
2890
                        // 1st fragment compressed
2891
                        // this fragment compressed
2892
                        $len = min($charsLeft, $limitpos - $pos);
2893
                        $retstr .= substr($recordData, $pos, $len);
2894
                        $charsLeft -= $len;
2895
                        $isCompressed = true;
2896
                    } elseif (!$isCompressed && ($option != 0)) {
2897
                        // 1st fragment uncompressed
2898
                        // this fragment uncompressed
2899
                        $len = min($charsLeft * 2, $limitpos - $pos);
2900
                        $retstr .= substr($recordData, $pos, $len);
2901
                        $charsLeft -= $len / 2;
2902
                        $isCompressed = false;
2903
                    } elseif (!$isCompressed && ($option == 0)) {
2904
                        // 1st fragment uncompressed
2905
                        // this fragment compressed
2906
                        $len = min($charsLeft, $limitpos - $pos);
2907
                        for ($j = 0; $j < $len; ++$j) {
2908
                            $retstr .= $recordData{$pos + $j}
2909
                            . chr(0);
2910
                        }
2911
                        $charsLeft -= $len;
2912
                        $isCompressed = false;
2913
                    } else {
2914
                        // 1st fragment compressed
2915
                        // this fragment uncompressed
2916
                        $newstr = '';
2917
                        for ($j = 0; $j < strlen($retstr); ++$j) {
2918
                            $newstr .= $retstr[$j] . chr(0);
2919
                        }
2920
                        $retstr = $newstr;
2921
                        $len = min($charsLeft * 2, $limitpos - $pos);
2922
                        $retstr .= substr($recordData, $pos, $len);
2923
                        $charsLeft -= $len / 2;
2924
                        $isCompressed = false;
2925
                    }
2926
2927
                    $pos += $len;
2928
                }
2929
            }
2930
2931
            // convert to UTF-8
2932 4
            $retstr = self::encodeUTF16($retstr, $isCompressed);
2933
2934
            // read additional Rich-Text information, if any
2935 4
            $fmtRuns = [];
2936 4
            if ($hasRichText) {
2937
                // list of formatting runs
2938 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...
2939
                    // first formatted character; zero-based
2940 2
                    $charPos = self::getInt2d($recordData, $pos + $j * 4);
2941
2942
                    // index to font record
2943 2
                    $fontIndex = self::getInt2d($recordData, $pos + 2 + $j * 4);
2944
2945 2
                    $fmtRuns[] = [
2946 2
                        'charPos' => $charPos,
2947 2
                        'fontIndex' => $fontIndex,
2948
                    ];
2949
                }
2950 2
                $pos += 4 * $formattingRuns;
2951
            }
2952
2953
            // read additional Asian phonetics information, if any
2954 4
            if ($hasAsian) {
2955
                // For Asian phonetic settings, we skip the extended string data
2956
                $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...
2957
            }
2958
2959
            // store the shared sting
2960 4
            $this->sst[] = [
2961 4
                'value' => $retstr,
2962 4
                'fmtRuns' => $fmtRuns,
2963
            ];
2964
        }
2965
2966
        // getSplicedRecordData() takes care of moving current position in data stream
2967 4
    }
2968
2969
    /**
2970
     * Read PRINTGRIDLINES record
2971
     */
2972 4
    private function readPrintGridlines()
2973
    {
2974 4
        $length = self::getInt2d($this->data, $this->pos + 2);
2975 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2976
2977
        // move stream pointer to next record
2978 4
        $this->pos += 4 + $length;
2979
2980 4
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
2981
            // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
2982 4
            $printGridlines = (bool) self::getInt2d($recordData, 0);
2983 4
            $this->phpSheet->setPrintGridlines($printGridlines);
2984
        }
2985 4
    }
2986
2987
    /**
2988
     * Read DEFAULTROWHEIGHT record
2989
     */
2990 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...
2991
    {
2992 3
        $length = self::getInt2d($this->data, $this->pos + 2);
2993 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2994
2995
        // move stream pointer to next record
2996 3
        $this->pos += 4 + $length;
2997
2998
        // offset: 0; size: 2; option flags
2999
        // 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...
3000 3
        $height = self::getInt2d($recordData, 2);
3001 3
        $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3002 3
    }
3003
3004
    /**
3005
     * Read SHEETPR record
3006
     */
3007 4
    private function readSheetPr()
3008
    {
3009 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3010 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3011
3012
        // move stream pointer to next record
3013 4
        $this->pos += 4 + $length;
3014
3015
        // offset: 0; size: 2
3016
3017
        // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
3018 4
        $isSummaryBelow = (0x0040 & self::getInt2d($recordData, 0)) >> 6;
3019 4
        $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
3020
3021
        // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
3022 4
        $isSummaryRight = (0x0080 & self::getInt2d($recordData, 0)) >> 7;
3023 4
        $this->phpSheet->setShowSummaryRight($isSummaryRight);
3024
3025
        // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
3026
        // this corresponds to radio button setting in page setup dialog in Excel
3027 4
        $this->isFitToPages = (bool) ((0x0100 & self::getInt2d($recordData, 0)) >> 8);
3028 4
    }
3029
3030
    /**
3031
     * Read HORIZONTALPAGEBREAKS record
3032
     */
3033 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...
3034
    {
3035
        $length = self::getInt2d($this->data, $this->pos + 2);
3036
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3037
3038
        // move stream pointer to next record
3039
        $this->pos += 4 + $length;
3040
3041
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3042
            // offset: 0; size: 2; number of the following row index structures
3043
            $nm = self::getInt2d($recordData, 0);
3044
3045
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3046
            for ($i = 0; $i < $nm; ++$i) {
3047
                $r = self::getInt2d($recordData, 2 + 6 * $i);
3048
                $cf = self::getInt2d($recordData, 2 + 6 * $i + 2);
3049
                $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...
3050
3051
                // not sure why two column indexes are necessary?
3052
                $this->phpSheet->setBreakByColumnAndRow($cf, $r, \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_ROW);
3053
            }
3054
        }
3055
    }
3056
3057
    /**
3058
     * Read VERTICALPAGEBREAKS record
3059
     */
3060 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...
3061
    {
3062
        $length = self::getInt2d($this->data, $this->pos + 2);
3063
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3064
3065
        // move stream pointer to next record
3066
        $this->pos += 4 + $length;
3067
3068
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3069
            // offset: 0; size: 2; number of the following column index structures
3070
            $nm = self::getInt2d($recordData, 0);
3071
3072
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3073
            for ($i = 0; $i < $nm; ++$i) {
3074
                $c = self::getInt2d($recordData, 2 + 6 * $i);
3075
                $rf = self::getInt2d($recordData, 2 + 6 * $i + 2);
3076
                $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...
3077
3078
                // not sure why two row indexes are necessary?
3079
                $this->phpSheet->setBreakByColumnAndRow($c, $rf, \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_COLUMN);
3080
            }
3081
        }
3082
    }
3083
3084
    /**
3085
     * Read HEADER record
3086
     */
3087 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...
3088
    {
3089 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3090 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3091
3092
        // move stream pointer to next record
3093 4
        $this->pos += 4 + $length;
3094
3095 4
        if (!$this->readDataOnly) {
3096
            // offset: 0; size: var
3097
            // realized that $recordData can be empty even when record exists
3098 4
            if ($recordData) {
3099 3
                if ($this->version == self::XLS_BIFF8) {
3100 3
                    $string = self::readUnicodeStringLong($recordData);
3101
                } else {
3102
                    $string = $this->readByteStringShort($recordData);
3103
                }
3104
3105 3
                $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3106 3
                $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3107
            }
3108
        }
3109 4
    }
3110
3111
    /**
3112
     * Read FOOTER record
3113
     */
3114 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...
3115
    {
3116 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3117 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3118
3119
        // move stream pointer to next record
3120 4
        $this->pos += 4 + $length;
3121
3122 4
        if (!$this->readDataOnly) {
3123
            // offset: 0; size: var
3124
            // realized that $recordData can be empty even when record exists
3125 4
            if ($recordData) {
3126 3
                if ($this->version == self::XLS_BIFF8) {
3127 3
                    $string = self::readUnicodeStringLong($recordData);
3128
                } else {
3129
                    $string = $this->readByteStringShort($recordData);
3130
                }
3131 3
                $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3132 3
                $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3133
            }
3134
        }
3135 4
    }
3136
3137
    /**
3138
     * Read HCENTER record
3139
     */
3140 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...
3141
    {
3142 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3143 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3144
3145
        // move stream pointer to next record
3146 4
        $this->pos += 4 + $length;
3147
3148 4
        if (!$this->readDataOnly) {
3149
            // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3150 4
            $isHorizontalCentered = (bool) self::getInt2d($recordData, 0);
3151
3152 4
            $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3153
        }
3154 4
    }
3155
3156
    /**
3157
     * Read VCENTER record
3158
     */
3159 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...
3160
    {
3161 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3162 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3163
3164
        // move stream pointer to next record
3165 4
        $this->pos += 4 + $length;
3166
3167 4
        if (!$this->readDataOnly) {
3168
            // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3169 4
            $isVerticalCentered = (bool) self::getInt2d($recordData, 0);
3170
3171 4
            $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3172
        }
3173 4
    }
3174
3175
    /**
3176
     * Read LEFTMARGIN record
3177
     */
3178 4
    private function readLeftMargin()
3179
    {
3180 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3181 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3182
3183
        // move stream pointer to next record
3184 4
        $this->pos += 4 + $length;
3185
3186 4
        if (!$this->readDataOnly) {
3187
            // offset: 0; size: 8
3188 4
            $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3189
        }
3190 4
    }
3191
3192
    /**
3193
     * Read RIGHTMARGIN record
3194
     */
3195 4
    private function readRightMargin()
3196
    {
3197 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3198 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3199
3200
        // move stream pointer to next record
3201 4
        $this->pos += 4 + $length;
3202
3203 4
        if (!$this->readDataOnly) {
3204
            // offset: 0; size: 8
3205 4
            $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3206
        }
3207 4
    }
3208
3209
    /**
3210
     * Read TOPMARGIN record
3211
     */
3212 4
    private function readTopMargin()
3213
    {
3214 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3215 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3216
3217
        // move stream pointer to next record
3218 4
        $this->pos += 4 + $length;
3219
3220 4
        if (!$this->readDataOnly) {
3221
            // offset: 0; size: 8
3222 4
            $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3223
        }
3224 4
    }
3225
3226
    /**
3227
     * Read BOTTOMMARGIN record
3228
     */
3229 4
    private function readBottomMargin()
3230
    {
3231 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3232 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3233
3234
        // move stream pointer to next record
3235 4
        $this->pos += 4 + $length;
3236
3237 4
        if (!$this->readDataOnly) {
3238
            // offset: 0; size: 8
3239 4
            $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3240
        }
3241 4
    }
3242
3243
    /**
3244
     * Read PAGESETUP record
3245
     */
3246 4
    private function readPageSetup()
3247
    {
3248 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3249 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3250
3251
        // move stream pointer to next record
3252 4
        $this->pos += 4 + $length;
3253
3254 4
        if (!$this->readDataOnly) {
3255
            // offset: 0; size: 2; paper size
3256 4
            $paperSize = self::getInt2d($recordData, 0);
3257
3258
            // offset: 2; size: 2; scaling factor
3259 4
            $scale = self::getInt2d($recordData, 2);
3260
3261
            // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3262 4
            $fitToWidth = self::getInt2d($recordData, 6);
3263
3264
            // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3265 4
            $fitToHeight = self::getInt2d($recordData, 8);
3266
3267
            // offset: 10; size: 2; option flags
3268
3269
            // 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...
3270 4
            $isPortrait = (0x0002 & self::getInt2d($recordData, 10)) >> 1;
3271
3272
            // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3273
            // when this bit is set, do not use flags for those properties
3274 4
            $isNotInit = (0x0004 & self::getInt2d($recordData, 10)) >> 2;
3275
3276 4
            if (!$isNotInit) {
3277 4
                $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3278
                switch ($isPortrait) {
3279 4
                    case 0:
3280 2
                        $this->phpSheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE);
3281 2
                        break;
3282 4
                    case 1:
3283 4
                        $this->phpSheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT);
3284 4
                        break;
3285
                }
3286
3287 4
                $this->phpSheet->getPageSetup()->setScale($scale, false);
3288 4
                $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3289 4
                $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3290 4
                $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3291
            }
3292
3293
            // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3294 4
            $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3295 4
            $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3296
3297
            // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3298 4
            $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3299 4
            $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3300
        }
3301 4
    }
3302
3303
    /**
3304
     * PROTECT - Sheet protection (BIFF2 through BIFF8)
3305
     *   if this record is omitted, then it also means no sheet protection
3306
     */
3307 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...
3308
    {
3309 1
        $length = self::getInt2d($this->data, $this->pos + 2);
3310 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3311
3312
        // move stream pointer to next record
3313 1
        $this->pos += 4 + $length;
3314
3315 1
        if ($this->readDataOnly) {
3316
            return;
3317
        }
3318
3319
        // 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...
3320
3321
        // bit 0, mask 0x01; 1 = sheet is protected
3322 1
        $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3323 1
        $this->phpSheet->getProtection()->setSheet((bool) $bool);
3324 1
    }
3325
3326
    /**
3327
     * SCENPROTECT
3328
     */
3329 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...
3330
    {
3331
        $length = self::getInt2d($this->data, $this->pos + 2);
3332
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3333
3334
        // move stream pointer to next record
3335
        $this->pos += 4 + $length;
3336
3337
        if ($this->readDataOnly) {
3338
            return;
3339
        }
3340
3341
        // 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...
3342
3343
        // bit: 0, mask 0x01; 1 = scenarios are protected
3344
        $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3345
3346
        $this->phpSheet->getProtection()->setScenarios((bool) $bool);
3347
    }
3348
3349
    /**
3350
     * OBJECTPROTECT
3351
     */
3352 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...
3353
    {
3354
        $length = self::getInt2d($this->data, $this->pos + 2);
3355
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3356
3357
        // move stream pointer to next record
3358
        $this->pos += 4 + $length;
3359
3360
        if ($this->readDataOnly) {
3361
            return;
3362
        }
3363
3364
        // 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...
3365
3366
        // bit: 0, mask 0x01; 1 = objects are protected
3367
        $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3368
3369
        $this->phpSheet->getProtection()->setObjects((bool) $bool);
3370
    }
3371
3372
    /**
3373
     * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8)
3374
     */
3375
    private function readPassword()
3376
    {
3377
        $length = self::getInt2d($this->data, $this->pos + 2);
3378
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3379
3380
        // move stream pointer to next record
3381
        $this->pos += 4 + $length;
3382
3383
        if (!$this->readDataOnly) {
3384
            // offset: 0; size: 2; 16-bit hash value of password
3385
            $password = strtoupper(dechex(self::getInt2d($recordData, 0))); // the hashed password
3386
            $this->phpSheet->getProtection()->setPassword($password, true);
3387
        }
3388
    }
3389
3390
    /**
3391
     * Read DEFCOLWIDTH record
3392
     */
3393 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...
3394
    {
3395 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3396 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3397
3398
        // move stream pointer to next record
3399 4
        $this->pos += 4 + $length;
3400
3401
        // offset: 0; size: 2; default column width
3402 4
        $width = self::getInt2d($recordData, 0);
3403 4
        if ($width != 8) {
3404
            $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
3405
        }
3406 4
    }
3407
3408
    /**
3409
     * Read COLINFO record
3410
     */
3411 3
    private function readColInfo()
3412
    {
3413 3
        $length = self::getInt2d($this->data, $this->pos + 2);
3414 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3415
3416
        // move stream pointer to next record
3417 3
        $this->pos += 4 + $length;
3418
3419 3
        if (!$this->readDataOnly) {
3420
            // offset: 0; size: 2; index to first column in range
3421 3
            $fc = self::getInt2d($recordData, 0); // first column index
3422
3423
            // offset: 2; size: 2; index to last column in range
3424 3
            $lc = self::getInt2d($recordData, 2); // first column index
3425
3426
            // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3427 3
            $width = self::getInt2d($recordData, 4);
3428
3429
            // offset: 6; size: 2; index to XF record for default column formatting
3430 3
            $xfIndex = self::getInt2d($recordData, 6);
3431
3432
            // offset: 8; size: 2; option flags
3433
            // bit: 0; mask: 0x0001; 1= columns are hidden
3434 3
            $isHidden = (0x0001 & self::getInt2d($recordData, 8)) >> 0;
3435
3436
            // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3437 3
            $level = (0x0700 & self::getInt2d($recordData, 8)) >> 8;
3438
3439
            // bit: 12; mask: 0x1000; 1 = collapsed
3440 3
            $isCollapsed = (0x1000 & self::getInt2d($recordData, 8)) >> 12;
3441
3442
            // offset: 10; size: 2; not used
3443
3444 3
            for ($i = $fc; $i <= $lc; ++$i) {
3445 3
                if ($lc == 255 || $lc == 256) {
3446
                    $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3447
                    break;
3448
                }
3449 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3450 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3451 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3452 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3453 3
                $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3454
            }
3455
        }
3456 3
    }
3457
3458
    /**
3459
     * ROW
3460
     *
3461
     * This record contains the properties of a single row in a
3462
     * sheet. Rows and cells in a sheet are divided into blocks
3463
     * of 32 rows.
3464
     *
3465
     * --    "OpenOffice.org's Documentation of the Microsoft
3466
     *         Excel File Format"
3467
     */
3468 3
    private function readRow()
3469
    {
3470 3
        $length = self::getInt2d($this->data, $this->pos + 2);
3471 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3472
3473
        // move stream pointer to next record
3474 3
        $this->pos += 4 + $length;
3475
3476 3
        if (!$this->readDataOnly) {
3477
            // offset: 0; size: 2; index of this row
3478 3
            $r = self::getInt2d($recordData, 0);
3479
3480
            // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3481
3482
            // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3483
3484
            // 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...
3485
3486
            // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3487 3
            $height = (0x7FFF & self::getInt2d($recordData, 6)) >> 0;
3488
3489
            // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3490 3
            $useDefaultHeight = (0x8000 & self::getInt2d($recordData, 6)) >> 15;
3491
3492 3
            if (!$useDefaultHeight) {
3493 3
                $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3494
            }
3495
3496
            // offset: 8; size: 2; not used
3497
3498
            // offset: 10; size: 2; not used in BIFF5-BIFF8
3499
3500
            // offset: 12; size: 4; option flags and default row formatting
3501
3502
            // bit: 2-0: mask: 0x00000007; outline level of the row
3503 3
            $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
3504 3
            $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3505
3506
            // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3507 3
            $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
3508 3
            $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3509
3510
            // bit: 5; mask: 0x00000020; 1 = row is hidden
3511 3
            $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
3512 3
            $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3513
3514
            // bit: 7; mask: 0x00000080; 1 = row has explicit format
3515 3
            $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
3516
3517
            // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3518 3
            $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
3519
3520 3
            if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
3521
                $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3522
            }
3523
        }
3524 3
    }
3525
3526
    /**
3527
     * Read RK record
3528
     * This record represents a cell that contains an RK value
3529
     * (encoded integer or floating-point value). If a
3530
     * floating-point value cannot be encoded to an RK value,
3531
     * a NUMBER record will be written. This record replaces the
3532
     * record INTEGER written in BIFF2.
3533
     *
3534
     * --    "OpenOffice.org's Documentation of the Microsoft
3535
     *         Excel File Format"
3536
     */
3537 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...
3538
    {
3539 1
        $length = self::getInt2d($this->data, $this->pos + 2);
3540 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3541
3542
        // move stream pointer to next record
3543 1
        $this->pos += 4 + $length;
3544
3545
        // offset: 0; size: 2; index to row
3546 1
        $row = self::getInt2d($recordData, 0);
3547
3548
        // offset: 2; size: 2; index to column
3549 1
        $column = self::getInt2d($recordData, 2);
3550 1
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3551
3552
        // Read cell?
3553 1
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3554
            // offset: 4; size: 2; index to XF record
3555 1
            $xfIndex = self::getInt2d($recordData, 4);
3556
3557
            // offset: 6; size: 4; RK value
3558 1
            $rknum = self::getInt4d($recordData, 6);
3559 1
            $numValue = self::getIEEE754($rknum);
3560
3561 1
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3562 1
            if (!$this->readDataOnly) {
3563
                // add style information
3564 1
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3565
            }
3566
3567
            // add cell
3568 1
            $cell->setValueExplicit($numValue, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
3569
        }
3570 1
    }
3571
3572
    /**
3573
     * Read LABELSST record
3574
     * This record represents a cell that contains a string. It
3575
     * replaces the LABEL record and RSTRING record used in
3576
     * BIFF2-BIFF5.
3577
     *
3578
     * --    "OpenOffice.org's Documentation of the Microsoft
3579
     *         Excel File Format"
3580
     */
3581 4
    private function readLabelSst()
3582
    {
3583 4
        $length = self::getInt2d($this->data, $this->pos + 2);
3584 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3585
3586
        // move stream pointer to next record
3587 4
        $this->pos += 4 + $length;
3588
3589
        // offset: 0; size: 2; index to row
3590 4
        $row = self::getInt2d($recordData, 0);
3591
3592
        // offset: 2; size: 2; index to column
3593 4
        $column = self::getInt2d($recordData, 2);
3594 4
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3595
3596 4
        $emptyCell = true;
3597
        // Read cell?
3598 4
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3599
            // offset: 4; size: 2; index to XF record
3600 4
            $xfIndex = self::getInt2d($recordData, 4);
3601
3602
            // offset: 6; size: 4; index to SST record
3603 4
            $index = self::getInt4d($recordData, 6);
3604
3605
            // add cell
3606 4
            if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
3607
                // then we should treat as rich text
3608 2
                $richText = new \PhpOffice\PhpSpreadsheet\RichText();
3609 2
                $charPos = 0;
3610 2
                $sstCount = count($this->sst[$index]['fmtRuns']);
3611 2
                for ($i = 0; $i <= $sstCount; ++$i) {
3612 2
                    if (isset($fmtRuns[$i])) {
3613 2
                        $text = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3614 2
                        $charPos = $fmtRuns[$i]['charPos'];
3615
                    } else {
3616 2
                        $text = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::substring($this->sst[$index]['value'], $charPos, \PhpOffice\PhpSpreadsheet\Shared\StringHelper::countCharacters($this->sst[$index]['value']));
3617
                    }
3618
3619 2
                    if (\PhpOffice\PhpSpreadsheet\Shared\StringHelper::countCharacters($text) > 0) {
3620 2
                        if ($i == 0) { // first text run, no style
3621 1
                            $richText->createText($text);
3622
                        } else {
3623 2
                            $textRun = $richText->createTextRun($text);
3624 2
                            if (isset($fmtRuns[$i - 1])) {
3625 2
                                if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3626 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3627
                                } else {
3628
                                    // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3629
                                    // check the OpenOffice documentation of the FONT record
3630 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3631
                                }
3632 2
                                $textRun->setFont(clone $this->objFonts[$fontIndex]);
3633
                            }
3634
                        }
3635
                    }
3636
                }
3637 2
                if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
3638 2
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3639 2
                    $cell->setValueExplicit($richText, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
3640 2
                    $emptyCell = false;
3641
                }
3642
            } else {
3643 4
                if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
3644 4
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3645 4
                    $cell->setValueExplicit($this->sst[$index]['value'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
3646 4
                    $emptyCell = false;
3647
                }
3648
            }
3649
3650 4
            if (!$this->readDataOnly && !$emptyCell) {
3651
                // add style information
3652 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...
3653
            }
3654
        }
3655 4
    }
3656
3657
    /**
3658
     * Read MULRK record
3659
     * This record represents a cell range containing RK value
3660
     * cells. All cells are located in the same row.
3661
     *
3662
     * --    "OpenOffice.org's Documentation of the Microsoft
3663
     *         Excel File Format"
3664
     */
3665
    private function readMulRk()
3666
    {
3667
        $length = self::getInt2d($this->data, $this->pos + 2);
3668
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3669
3670
        // move stream pointer to next record
3671
        $this->pos += 4 + $length;
3672
3673
        // offset: 0; size: 2; index to row
3674
        $row = self::getInt2d($recordData, 0);
3675
3676
        // offset: 2; size: 2; index to first column
3677
        $colFirst = self::getInt2d($recordData, 2);
3678
3679
        // offset: var; size: 2; index to last column
3680
        $colLast = self::getInt2d($recordData, $length - 2);
3681
        $columns = $colLast - $colFirst + 1;
3682
3683
        // offset within record data
3684
        $offset = 4;
3685
3686
        for ($i = 0; $i < $columns; ++$i) {
3687
            $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($colFirst + $i);
3688
3689
            // Read cell?
3690
            if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3691
                // offset: var; size: 2; index to XF record
3692
                $xfIndex = self::getInt2d($recordData, $offset);
3693
3694
                // offset: var; size: 4; RK value
3695
                $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
3696
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3697
                if (!$this->readDataOnly) {
3698
                    // add style
3699
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3700
                }
3701
3702
                // add cell value
3703
                $cell->setValueExplicit($numValue, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
3704
            }
3705
3706
            $offset += 6;
3707
        }
3708
    }
3709
3710
    /**
3711
     * Read NUMBER record
3712
     * This record represents a cell that contains a
3713
     * floating-point value.
3714
     *
3715
     * --    "OpenOffice.org's Documentation of the Microsoft
3716
     *         Excel File Format"
3717
     */
3718 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...
3719
    {
3720 1
        $length = self::getInt2d($this->data, $this->pos + 2);
3721 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3722
3723
        // move stream pointer to next record
3724 1
        $this->pos += 4 + $length;
3725
3726
        // offset: 0; size: 2; index to row
3727 1
        $row = self::getInt2d($recordData, 0);
3728
3729
        // offset: 2; size 2; index to column
3730 1
        $column = self::getInt2d($recordData, 2);
3731 1
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3732
3733
        // Read cell?
3734 1
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3735
            // offset 4; size: 2; index to XF record
3736 1
            $xfIndex = self::getInt2d($recordData, 4);
3737
3738 1
            $numValue = self::extractNumber(substr($recordData, 6, 8));
3739
3740 1
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3741 1
            if (!$this->readDataOnly) {
3742
                // add cell style
3743 1
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3744
            }
3745
3746
            // add cell value
3747 1
            $cell->setValueExplicit($numValue, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
3748
        }
3749 1
    }
3750
3751
    /**
3752
     * Read FORMULA record + perhaps a following STRING record if formula result is a string
3753
     * This record contains the token array and the result of a
3754
     * formula cell.
3755
     *
3756
     * --    "OpenOffice.org's Documentation of the Microsoft
3757
     *         Excel File Format"
3758
     */
3759 2
    private function readFormula()
3760
    {
3761 2
        $length = self::getInt2d($this->data, $this->pos + 2);
3762 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3763
3764
        // move stream pointer to next record
3765 2
        $this->pos += 4 + $length;
3766
3767
        // offset: 0; size: 2; row index
3768 2
        $row = self::getInt2d($recordData, 0);
3769
3770
        // offset: 2; size: 2; col index
3771 2
        $column = self::getInt2d($recordData, 2);
3772 2
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3773
3774
        // offset: 20: size: variable; formula structure
3775 2
        $formulaStructure = substr($recordData, 20);
3776
3777
        // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3778 2
        $options = self::getInt2d($recordData, 14);
3779
3780
        // bit: 0; mask: 0x0001; 1 = recalculate always
3781
        // bit: 1; mask: 0x0002; 1 = calculate on open
3782
        // bit: 2; mask: 0x0008; 1 = part of a shared formula
3783 2
        $isPartOfSharedFormula = (bool) (0x0008 & $options);
3784
3785
        // WARNING:
3786
        // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3787
        // the formula data may be ordinary formula data, therefore we need to check
3788
        // explicitly for the tExp token (0x01)
3789 2
        $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure{2}) == 0x01;
3790
3791 2
        if ($isPartOfSharedFormula) {
3792
            // part of shared formula which means there will be a formula with a tExp token and nothing else
3793
            // get the base cell, grab tExp token
3794
            $baseRow = self::getInt2d($formulaStructure, 3);
3795
            $baseCol = self::getInt2d($formulaStructure, 5);
3796
            $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...
3797
        }
3798
3799
        // Read cell?
3800 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3801 2
            if ($isPartOfSharedFormula) {
3802
                // formula is added to this cell after the sheet has been read
3803
                $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->_baseCell;
3804
            }
3805
3806
            // offset: 16: size: 4; not used
3807
3808
            // offset: 4; size: 2; XF index
3809 2
            $xfIndex = self::getInt2d($recordData, 4);
3810
3811
            // offset: 6; size: 8; result of the formula
3812 2
            if ((ord($recordData{6}) == 0) && (ord($recordData{12}) == 255) && (ord($recordData{13}) == 255)) {
3813
                // String formula. Result follows in appended STRING record
3814
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING;
3815
3816
                // read possible SHAREDFMLA record
3817
                $code = self::getInt2d($this->data, $this->pos);
3818
                if ($code == self::XLS_TYPE_SHAREDFMLA) {
3819
                    $this->readSharedFmla();
3820
                }
3821
3822
                // read STRING record
3823
                $value = $this->readString();
3824 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...
3825 2
                && (ord($recordData{12}) == 255)
3826 2
                && (ord($recordData{13}) == 255)) {
3827
                // Boolean formula. Result is in +2; 0=false, 1=true
3828
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_BOOL;
3829
                $value = (bool) ord($recordData{8});
3830 2
            } elseif ((ord($recordData{6}) == 2)
3831 2
                && (ord($recordData{12}) == 255)
3832 2
                && (ord($recordData{13}) == 255)) {
3833
                // Error formula. Error code is in +2
3834
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_ERROR;
3835
                $value = Xls\ErrorCode::lookup(ord($recordData{8}));
3836 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...
3837 2
                && (ord($recordData{12}) == 255)
3838 2
                && (ord($recordData{13}) == 255)) {
3839
                // Formula result is a null string
3840 1
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NULL;
3841 1
                $value = '';
3842
            } else {
3843
                // forumla result is a number, first 14 bytes like _NUMBER record
3844 2
                $dataType = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC;
3845 2
                $value = self::extractNumber(substr($recordData, 6, 8));
3846
            }
3847
3848 2
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3849 2
            if (!$this->readDataOnly) {
3850
                // add cell style
3851 2
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3852
            }
3853
3854
            // store the formula
3855 2
            if (!$isPartOfSharedFormula) {
3856
                // not part of shared formula
3857
                // add cell value. If we can read formula, populate with formula, otherwise just used cached value
3858
                try {
3859 2
                    if ($this->version != self::XLS_BIFF8) {
3860
                        throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
3861
                    }
3862 2
                    $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
3863 2
                    $cell->setValueExplicit('=' . $formula, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA);
3864
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
3865 2
                    $cell->setValueExplicit($value, $dataType);
3866
                }
3867
            } else {
3868
                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...
3869
                    // do nothing at this point, formula id added later in the code
3870
                } else {
3871
                    $cell->setValueExplicit($value, $dataType);
3872
                }
3873
            }
3874
3875
            // store the cached calculated value
3876 2
            $cell->setCalculatedValue($value);
3877
        }
3878 2
    }
3879
3880
    /**
3881
     * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
3882
     * which usually contains relative references.
3883
     * These will be used to construct the formula in each shared formula part after the sheet is read.
3884
     */
3885
    private function readSharedFmla()
3886
    {
3887
        $length = self::getInt2d($this->data, $this->pos + 2);
3888
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3889
3890
        // move stream pointer to next record
3891
        $this->pos += 4 + $length;
3892
3893
        // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
3894
        $cellRange = substr($recordData, 0, 6);
3895
        $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...
3896
3897
        // offset: 6, size: 1; not used
3898
3899
        // offset: 7, size: 1; number of existing FORMULA records for this shared formula
3900
        $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...
3901
3902
        // offset: 8, size: var; Binary token array of the shared formula
3903
        $formula = substr($recordData, 8);
3904
3905
        // at this point we only store the shared formula for later use
3906
        $this->sharedFormulas[$this->_baseCell] = $formula;
3907
    }
3908
3909
    /**
3910
     * Read a STRING record from current stream position and advance the stream pointer to next record
3911
     * This record is used for storing result from FORMULA record when it is a string, and
3912
     * it occurs directly after the FORMULA record
3913
     *
3914
     * @return string The string contents as UTF-8
3915
     */
3916
    private function readString()
3917
    {
3918
        $length = self::getInt2d($this->data, $this->pos + 2);
3919
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3920
3921
        // move stream pointer to next record
3922
        $this->pos += 4 + $length;
3923
3924
        if ($this->version == self::XLS_BIFF8) {
3925
            $string = self::readUnicodeStringLong($recordData);
3926
            $value = $string['value'];
3927
        } else {
3928
            $string = $this->readByteStringLong($recordData);
3929
            $value = $string['value'];
3930
        }
3931
3932
        return $value;
3933
    }
3934
3935
    /**
3936
     * Read BOOLERR record
3937
     * This record represents a Boolean value or error value
3938
     * cell.
3939
     *
3940
     * --    "OpenOffice.org's Documentation of the Microsoft
3941
     *         Excel File Format"
3942
     */
3943
    private function readBoolErr()
3944
    {
3945
        $length = self::getInt2d($this->data, $this->pos + 2);
3946
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3947
3948
        // move stream pointer to next record
3949
        $this->pos += 4 + $length;
3950
3951
        // offset: 0; size: 2; row index
3952
        $row = self::getInt2d($recordData, 0);
3953
3954
        // offset: 2; size: 2; column index
3955
        $column = self::getInt2d($recordData, 2);
3956
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
3957
3958
        // Read cell?
3959
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3960
            // offset: 4; size: 2; index to XF record
3961
            $xfIndex = self::getInt2d($recordData, 4);
3962
3963
            // offset: 6; size: 1; the boolean value or error value
3964
            $boolErr = ord($recordData{6});
3965
3966
            // 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...
3967
            $isError = ord($recordData{7});
3968
3969
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3970
            switch ($isError) {
3971
                case 0: // boolean
3972
                    $value = (bool) $boolErr;
3973
3974
                    // add cell value
3975
                    $cell->setValueExplicit($value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_BOOL);
3976
                    break;
3977
                case 1: // error type
3978
                    $value = Xls\ErrorCode::lookup($boolErr);
3979
3980
                    // add cell value
3981
                    $cell->setValueExplicit($value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_ERROR);
3982
                    break;
3983
            }
3984
3985
            if (!$this->readDataOnly) {
3986
                // add cell style
3987
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3988
            }
3989
        }
3990
    }
3991
3992
    /**
3993
     * Read MULBLANK record
3994
     * This record represents a cell range of empty cells. All
3995
     * cells are located in the same row
3996
     *
3997
     * --    "OpenOffice.org's Documentation of the Microsoft
3998
     *         Excel File Format"
3999
     */
4000 1
    private function readMulBlank()
4001
    {
4002 1
        $length = self::getInt2d($this->data, $this->pos + 2);
4003 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4004
4005
        // move stream pointer to next record
4006 1
        $this->pos += 4 + $length;
4007
4008
        // offset: 0; size: 2; index to row
4009 1
        $row = self::getInt2d($recordData, 0);
4010
4011
        // offset: 2; size: 2; index to first column
4012 1
        $fc = self::getInt2d($recordData, 2);
4013
4014
        // offset: 4; size: 2 x nc; list of indexes to XF records
4015
        // add style information
4016 1
        if (!$this->readDataOnly && $this->readEmptyCells) {
4017 1
            for ($i = 0; $i < $length / 2 - 3; ++$i) {
4018 1
                $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fc + $i);
4019
4020
                // Read cell?
4021 1
                if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4022 1
                    $xfIndex = self::getInt2d($recordData, 4 + 2 * $i);
4023 1
                    $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4024
                }
4025
            }
4026
        }
4027
4028
        // offset: 6; size 2; index to last column (not needed)
4029 1
    }
4030
4031
    /**
4032
     * Read LABEL record
4033
     * This record represents a cell that contains a string. In
4034
     * BIFF8 it is usually replaced by the LABELSST record.
4035
     * Excel still uses this record, if it copies unformatted
4036
     * text cells to the clipboard.
4037
     *
4038
     * --    "OpenOffice.org's Documentation of the Microsoft
4039
     *         Excel File Format"
4040
     */
4041
    private function readLabel()
4042
    {
4043
        $length = self::getInt2d($this->data, $this->pos + 2);
4044
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4045
4046
        // move stream pointer to next record
4047
        $this->pos += 4 + $length;
4048
4049
        // offset: 0; size: 2; index to row
4050
        $row = self::getInt2d($recordData, 0);
4051
4052
        // offset: 2; size: 2; index to column
4053
        $column = self::getInt2d($recordData, 2);
4054
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column);
4055
4056
        // Read cell?
4057
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4058
            // offset: 4; size: 2; XF index
4059
            $xfIndex = self::getInt2d($recordData, 4);
4060
4061
            // add cell value
4062
            // todo: what if string is very long? continue record
4063 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...
4064
                $string = self::readUnicodeStringLong(substr($recordData, 6));
4065
                $value = $string['value'];
4066
            } else {
4067
                $string = $this->readByteStringLong(substr($recordData, 6));
4068
                $value = $string['value'];
4069
            }
4070
            if ($this->readEmptyCells || trim($value) !== '') {
4071
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4072
                $cell->setValueExplicit($value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
4073
4074
                if (!$this->readDataOnly) {
4075
                    // add cell style
4076
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4077
                }
4078
            }
4079
        }
4080
    }
4081
4082
    /**
4083
     * Read BLANK record
4084
     */
4085 2
    private function readBlank()
4086
    {
4087 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4088 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4089
4090
        // move stream pointer to next record
4091 2
        $this->pos += 4 + $length;
4092
4093
        // offset: 0; size: 2; row index
4094 2
        $row = self::getInt2d($recordData, 0);
4095
4096
        // offset: 2; size: 2; col index
4097 2
        $col = self::getInt2d($recordData, 2);
4098 2
        $columnString = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($col);
4099
4100
        // Read cell?
4101 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4102
            // offset: 4; size: 2; XF index
4103 2
            $xfIndex = self::getInt2d($recordData, 4);
4104
4105
            // add style information
4106 2
            if (!$this->readDataOnly && $this->readEmptyCells) {
4107 2
                $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4108
            }
4109
        }
4110 2
    }
4111
4112
    /**
4113
     * Read MSODRAWING record
4114
     */
4115 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...
4116
    {
4117 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...
4118
4119
        // get spliced record data
4120 2
        $splicedRecordData = $this->getSplicedRecordData();
4121 2
        $recordData = $splicedRecordData['recordData'];
4122
4123 2
        $this->drawingData .= $recordData;
4124 2
    }
4125
4126
    /**
4127
     * Read OBJ record
4128
     */
4129 2
    private function readObj()
4130
    {
4131 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4132 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4133
4134
        // move stream pointer to next record
4135 2
        $this->pos += 4 + $length;
4136
4137 2
        if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4138
            return;
4139
        }
4140
4141
        // recordData consists of an array of subrecords looking like this:
4142
        //    ft: 2 bytes; ftCmo type (0x15)
4143
        //    cb: 2 bytes; size in bytes of ftCmo data
4144
        //    ot: 2 bytes; Object Type
4145
        //    id: 2 bytes; Object id number
4146
        //    grbit: 2 bytes; Option Flags
4147
        //    data: var; subrecord data
4148
4149
        // for now, we are just interested in the second subrecord containing the object type
4150 2
        $ftCmoType = self::getInt2d($recordData, 0);
4151 2
        $cbCmoSize = self::getInt2d($recordData, 2);
4152 2
        $otObjType = self::getInt2d($recordData, 4);
4153 2
        $idObjID = self::getInt2d($recordData, 6);
4154 2
        $grbitOpts = self::getInt2d($recordData, 6);
4155
4156 2
        $this->objs[] = [
4157 2
            'ftCmoType' => $ftCmoType,
4158 2
            'cbCmoSize' => $cbCmoSize,
4159 2
            'otObjType' => $otObjType,
4160 2
            'idObjID' => $idObjID,
4161 2
            'grbitOpts' => $grbitOpts,
4162
        ];
4163 2
        $this->textObjRef = $idObjID;
4164 2
    }
4165
4166
    /**
4167
     * Read WINDOW2 record
4168
     */
4169 4
    private function readWindow2()
4170
    {
4171 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4172 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4173
4174
        // move stream pointer to next record
4175 4
        $this->pos += 4 + $length;
4176
4177
        // offset: 0; size: 2; option flags
4178 4
        $options = self::getInt2d($recordData, 0);
4179
4180
        // offset: 2; size: 2; index to first visible row
4181 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...
4182
4183
        // offset: 4; size: 2; index to first visible colum
4184 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...
4185 4
        if ($this->version === self::XLS_BIFF8) {
4186
            // offset:  8; size: 2; not used
4187
            // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4188
            // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4189
            // offset: 14; size: 4; not used
4190 4
            $zoomscaleInPageBreakPreview = self::getInt2d($recordData, 10);
4191 4
            if ($zoomscaleInPageBreakPreview === 0) {
4192 4
                $zoomscaleInPageBreakPreview = 60;
4193
            }
4194 4
            $zoomscaleInNormalView = self::getInt2d($recordData, 12);
4195 4
            if ($zoomscaleInNormalView === 0) {
4196 3
                $zoomscaleInNormalView = 100;
4197
            }
4198
        }
4199
4200
        // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4201 4
        $showGridlines = (bool) ((0x0002 & $options) >> 1);
4202 4
        $this->phpSheet->setShowGridlines($showGridlines);
4203
4204
        // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4205 4
        $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4206 4
        $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4207
4208
        // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4209 4
        $this->frozen = (bool) ((0x0008 & $options) >> 3);
4210
4211
        // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4212 4
        $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
4213
4214
        // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4215 4
        $isActive = (bool) ((0x0400 & $options) >> 10);
4216 4
        if ($isActive) {
4217 4
            $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
4218
        }
4219
4220
        // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4221 4
        $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4222
4223
        //FIXME: set $firstVisibleRow and $firstVisibleColumn
4224
4225 4
        if ($this->phpSheet->getSheetView()->getView() !== \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_PAGE_LAYOUT) {
4226
            //NOTE: this setting is inferior to page layout view(Excel2007-)
4227 4
            $view = $isPageBreakPreview ? \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_NORMAL;
4228 4
            $this->phpSheet->getSheetView()->setView($view);
4229 4
            if ($this->version === self::XLS_BIFF8) {
4230 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...
4231 4
                $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4232 4
                $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4233
            }
4234
        }
4235 4
    }
4236
4237
    /**
4238
     * Read PLV Record(Created by Excel2007 or upper)
4239
     */
4240 4
    private function readPageLayoutView()
4241
    {
4242 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4243 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4244
4245
        // move stream pointer to next record
4246 4
        $this->pos += 4 + $length;
4247
4248
        // offset: 0; size: 2; rt
4249
        //->ignore
4250 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...
4251
        // offset: 2; size: 2; grbitfr
4252
        //->ignore
4253 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...
4254
        // offset: 4; size: 8; reserved
4255
        //->ignore
4256
4257
        // offset: 12; size 2; zoom scale
4258 4
        $wScalePLV = self::getInt2d($recordData, 12);
4259
        // offset: 14; size 2; grbit
4260 4
        $grbit = self::getInt2d($recordData, 14);
4261
4262
        // decomprise grbit
4263 4
        $fPageLayoutView = $grbit & 0x01;
4264 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...
4265 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...
4266
4267 4
        if ($fPageLayoutView === 1) {
4268
            $this->phpSheet->getSheetView()->setView(\PhpOffice\PhpSpreadsheet\Worksheet\SheetView::SHEETVIEW_PAGE_LAYOUT);
4269
            $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4270
        }
4271
        //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4272 4
    }
4273
4274
    /**
4275
     * Read SCL record
4276
     */
4277
    private function readScl()
4278
    {
4279
        $length = self::getInt2d($this->data, $this->pos + 2);
4280
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4281
4282
        // move stream pointer to next record
4283
        $this->pos += 4 + $length;
4284
4285
        // offset: 0; size: 2; numerator of the view magnification
4286
        $numerator = self::getInt2d($recordData, 0);
4287
4288
        // offset: 2; size: 2; numerator of the view magnification
4289
        $denumerator = self::getInt2d($recordData, 2);
4290
4291
        // set the zoom scale (in percent)
4292
        $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4293
    }
4294
4295
    /**
4296
     * Read PANE record
4297
     */
4298
    private function readPane()
4299
    {
4300
        $length = self::getInt2d($this->data, $this->pos + 2);
4301
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4302
4303
        // move stream pointer to next record
4304
        $this->pos += 4 + $length;
4305
4306
        if (!$this->readDataOnly) {
4307
            // offset: 0; size: 2; position of vertical split
4308
            $px = self::getInt2d($recordData, 0);
4309
4310
            // offset: 2; size: 2; position of horizontal split
4311
            $py = self::getInt2d($recordData, 2);
4312
4313
            if ($this->frozen) {
4314
                // frozen panes
4315
                $this->phpSheet->freezePane(\PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($px) . ($py + 1));
4316
            } 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...
4317
                // unfrozen panes; split windows; not supported by PhpSpreadsheet core
4318
            }
4319
        }
4320
    }
4321
4322
    /**
4323
     * Read SELECTION record. There is one such record for each pane in the sheet.
4324
     */
4325 4
    private function readSelection()
4326
    {
4327 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4328 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4329
4330
        // move stream pointer to next record
4331 4
        $this->pos += 4 + $length;
4332
4333 4
        if (!$this->readDataOnly) {
4334
            // offset: 0; size: 1; pane identifier
4335 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...
4336
4337
            // offset: 1; size: 2; index to row of the active cell
4338 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...
4339
4340
            // offset: 3; size: 2; index to column of the active cell
4341 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...
4342
4343
            // offset: 5; size: 2; index into the following cell range list to the
4344
            //  entry that contains the active cell
4345 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...
4346
4347
            // offset: 7; size: var; cell range address list containing all selected cell ranges
4348 4
            $data = substr($recordData, 7);
4349 4
            $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4350
4351 4
            $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4352
4353
            // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4354 4
            if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4355
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4356
            }
4357
4358
            // first row '1' + last row '65536' indicates that full column is selected
4359 4
            if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4360
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4361
            }
4362
4363
            // first column 'A' + last column 'IV' indicates that full row is selected
4364 4
            if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4365
                $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '${1}XFD${2}', $selectedCells);
4366
            }
4367
4368 4
            $this->phpSheet->setSelectedCells($selectedCells);
4369
        }
4370 4
    }
4371
4372 2
    private function includeCellRangeFiltered($cellRangeAddress)
4373
    {
4374 2
        $includeCellRange = true;
4375 2
        if ($this->getReadFilter() !== null) {
4376 2
            $includeCellRange = false;
4377 2
            $rangeBoundaries = \PhpOffice\PhpSpreadsheet\Cell::getRangeBoundaries($cellRangeAddress);
4378 2
            ++$rangeBoundaries[1][0];
4379 2
            for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
4380 2
                for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
4381 2
                    if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4382 2
                        $includeCellRange = true;
4383 2
                        break 2;
4384
                    }
4385
                }
4386
            }
4387
        }
4388
4389 2
        return $includeCellRange;
4390
    }
4391
4392
    /**
4393
     * MERGEDCELLS
4394
     *
4395
     * This record contains the addresses of merged cell ranges
4396
     * in the current sheet.
4397
     *
4398
     * --    "OpenOffice.org's Documentation of the Microsoft
4399
     *         Excel File Format"
4400
     */
4401 2
    private function readMergedCells()
4402
    {
4403 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4404 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4405
4406
        // move stream pointer to next record
4407 2
        $this->pos += 4 + $length;
4408
4409 2
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4410 2
            $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4411 2
            foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4412 2
                if ((strpos($cellRangeAddress, ':') !== false) &&
4413 2
                    ($this->includeCellRangeFiltered($cellRangeAddress))) {
4414 2
                    $this->phpSheet->mergeCells($cellRangeAddress);
4415
                }
4416
            }
4417
        }
4418 2
    }
4419
4420
    /**
4421
     * Read HYPERLINK record
4422
     */
4423 2
    private function readHyperLink()
4424
    {
4425 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4426 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4427
4428
        // move stream pointer forward to next record
4429 2
        $this->pos += 4 + $length;
4430
4431 2
        if (!$this->readDataOnly) {
4432
            // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4433
            try {
4434 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...
4435
            } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4436
                return;
4437
            }
4438
4439
            // offset: 8, size: 16; GUID of StdLink
4440
4441
            // offset: 24, size: 4; unknown value
4442
4443
            // offset: 28, size: 4; option flags
4444
            // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4445 2
            $isFileLinkOrUrl = (0x00000001 & self::getInt2d($recordData, 28)) >> 0;
4446
4447
            // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4448 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...
4449
4450
            // 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...
4451 2
            $hasDesc = (0x00000014 & self::getInt2d($recordData, 28)) >> 2;
4452
4453
            // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4454 2
            $hasText = (0x00000008 & self::getInt2d($recordData, 28)) >> 3;
4455
4456
            // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4457 2
            $hasFrame = (0x00000080 & self::getInt2d($recordData, 28)) >> 7;
4458
4459
            // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4460 2
            $isUNC = (0x00000100 & self::getInt2d($recordData, 28)) >> 8;
4461
4462
            // offset within record data
4463 2
            $offset = 32;
4464
4465 2
            if ($hasDesc) {
4466
                // offset: 32; size: var; character count of description text
4467 1
                $dl = self::getInt4d($recordData, 32);
4468
                // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4469 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...
4470 1
                $offset += 4 + 2 * $dl;
4471
            }
4472 2
            if ($hasFrame) {
4473
                $fl = self::getInt4d($recordData, $offset);
4474
                $offset += 4 + 2 * $fl;
4475
            }
4476
4477
            // detect type of hyperlink (there are 4 types)
4478 2
            $hyperlinkType = null;
4479
4480 2
            if ($isUNC) {
4481
                $hyperlinkType = 'UNC';
4482 2
            } elseif (!$isFileLinkOrUrl) {
4483 2
                $hyperlinkType = 'workbook';
4484 2
            } elseif (ord($recordData{$offset}) == 0x03) {
4485
                $hyperlinkType = 'local';
4486 2
            } elseif (ord($recordData{$offset}) == 0xE0) {
4487 2
                $hyperlinkType = 'URL';
4488
            }
4489
4490
            switch ($hyperlinkType) {
4491 2
                case 'URL':
4492
                    // section 5.58.2: Hyperlink containing a URL
4493
                    // e.g. http://example.org/index.php
4494
4495
                    // offset: var; size: 16; GUID of URL Moniker
4496 2
                    $offset += 16;
4497
                    // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4498 2
                    $us = self::getInt4d($recordData, $offset);
4499 2
                    $offset += 4;
4500
                    // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4501 2
                    $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4502 2
                    $nullOffset = strpos($url, 0x00);
4503 2
                    if ($nullOffset) {
4504 1
                        $url = substr($url, 0, $nullOffset);
4505
                    }
4506 2
                    $url .= $hasText ? '#' : '';
4507 2
                    $offset += $us;
4508 2
                    break;
4509 2
                case 'local':
4510
                    // section 5.58.3: Hyperlink to local file
4511
                    // examples:
4512
                    //   mydoc.txt
4513
                    //   ../../somedoc.xls#Sheet!A1
4514
4515
                    // offset: var; size: 16; GUI of File Moniker
4516
                    $offset += 16;
4517
4518
                    // offset: var; size: 2; directory up-level count.
4519
                    $upLevelCount = self::getInt2d($recordData, $offset);
4520
                    $offset += 2;
4521
4522
                    // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4523
                    $sl = self::getInt4d($recordData, $offset);
4524
                    $offset += 4;
4525
4526
                    // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4527
                    $shortenedFilePath = substr($recordData, $offset, $sl);
4528
                    $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4529
                    $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4530
4531
                    $offset += $sl;
4532
4533
                    // offset: var; size: 24; unknown sequence
4534
                    $offset += 24;
4535
4536
                    // extended file path
4537
                    // offset: var; size: 4; size of the following file link field including string lenth mark
4538
                    $sz = self::getInt4d($recordData, $offset);
4539
                    $offset += 4;
4540
4541
                    // only present if $sz > 0
4542
                    if ($sz > 0) {
4543
                        // offset: var; size: 4; size of the character array of the extended file path and name
4544
                        $xl = self::getInt4d($recordData, $offset);
4545
                        $offset += 4;
4546
4547
                        // offset: var; size 2; unknown
4548
                        $offset += 2;
4549
4550
                        // offset: var; size $xl; character array of the extended file path and name.
4551
                        $extendedFilePath = substr($recordData, $offset, $xl);
4552
                        $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4553
                        $offset += $xl;
4554
                    }
4555
4556
                    // construct the path
4557
                    $url = str_repeat('..\\', $upLevelCount);
4558
                    $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...
4559
                    $url .= $hasText ? '#' : '';
4560
4561
                    break;
4562 2
                case 'UNC':
4563
                    // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4564
                    // todo: implement
4565
                    return;
4566 2
                case 'workbook':
4567
                    // section 5.58.5: Hyperlink to the Current Workbook
4568
                    // e.g. Sheet2!B1:C2, stored in text mark field
4569 2
                    $url = 'sheet://';
4570 2
                    break;
4571
                default:
4572
                    return;
4573
            }
4574
4575 2
            if ($hasText) {
4576
                // offset: var; size: 4; character count of text mark including trailing zero word
4577 2
                $tl = self::getInt4d($recordData, $offset);
4578 2
                $offset += 4;
4579
                // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4580 2
                $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4581 2
                $url .= $text;
4582
            }
4583
4584
            // apply the hyperlink to all the relevant cells
4585 2
            foreach (\PhpOffice\PhpSpreadsheet\Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4586 2
                $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4587
            }
4588
        }
4589 2
    }
4590
4591
    /**
4592
     * Read DATAVALIDATIONS record
4593
     */
4594
    private function readDataValidations()
4595
    {
4596
        $length = self::getInt2d($this->data, $this->pos + 2);
4597
        $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...
4598
4599
        // move stream pointer forward to next record
4600
        $this->pos += 4 + $length;
4601
    }
4602
4603
    /**
4604
     * Read DATAVALIDATION record
4605
     */
4606
    private function readDataValidation()
4607
    {
4608
        $length = self::getInt2d($this->data, $this->pos + 2);
4609
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4610
4611
        // move stream pointer forward to next record
4612
        $this->pos += 4 + $length;
4613
4614
        if ($this->readDataOnly) {
4615
            return;
4616
        }
4617
4618
        // offset: 0; size: 4; Options
4619
        $options = self::getInt4d($recordData, 0);
4620
4621
        // 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...
4622
        $type = (0x0000000F & $options) >> 0;
4623 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...
4624
            case 0x00:
4625
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_NONE;
4626
                break;
4627
            case 0x01:
4628
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_WHOLE;
4629
                break;
4630
            case 0x02:
4631
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_DECIMAL;
4632
                break;
4633
            case 0x03:
4634
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST;
4635
                break;
4636
            case 0x04:
4637
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_DATE;
4638
                break;
4639
            case 0x05:
4640
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_TIME;
4641
                break;
4642
            case 0x06:
4643
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_TEXTLENGTH;
4644
                break;
4645
            case 0x07:
4646
                $type = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_CUSTOM;
4647
                break;
4648
        }
4649
4650
        // bit: 4-6; mask: 0x00000070; error type
4651
        $errorStyle = (0x00000070 & $options) >> 4;
4652 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...
4653
            case 0x00:
4654
                $errorStyle = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_STOP;
4655
                break;
4656
            case 0x01:
4657
                $errorStyle = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_WARNING;
4658
                break;
4659
            case 0x02:
4660
                $errorStyle = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_INFORMATION;
4661
                break;
4662
        }
4663
4664
        // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4665
        // I have only seen cases where this is 1
4666
        $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...
4667
4668
        // bit: 8; mask: 0x00000100; 1= empty cells allowed
4669
        $allowBlank = (0x00000100 & $options) >> 8;
4670
4671
        // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4672
        $suppressDropDown = (0x00000200 & $options) >> 9;
4673
4674
        // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4675
        $showInputMessage = (0x00040000 & $options) >> 18;
4676
4677
        // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4678
        $showErrorMessage = (0x00080000 & $options) >> 19;
4679
4680
        // bit: 20-23; mask: 0x00F00000; condition operator
4681
        $operator = (0x00F00000 & $options) >> 20;
4682 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...
4683
            case 0x00:
4684
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_BETWEEN;
4685
                break;
4686
            case 0x01:
4687
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_NOTBETWEEN;
4688
                break;
4689
            case 0x02:
4690
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_EQUAL;
4691
                break;
4692
            case 0x03:
4693
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_NOTEQUAL;
4694
                break;
4695
            case 0x04:
4696
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_GREATERTHAN;
4697
                break;
4698
            case 0x05:
4699
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_LESSTHAN;
4700
                break;
4701
            case 0x06:
4702
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_GREATERTHANOREQUAL;
4703
                break;
4704
            case 0x07:
4705
                $operator = \PhpOffice\PhpSpreadsheet\Cell\DataValidation::OPERATOR_LESSTHANOREQUAL;
4706
                break;
4707
        }
4708
4709
        // offset: 4; size: var; title of the prompt box
4710
        $offset = 4;
4711
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4712
        $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4713
        $offset += $string['size'];
4714
4715
        // offset: var; size: var; title of the error box
4716
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4717
        $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4718
        $offset += $string['size'];
4719
4720
        // offset: var; size: var; text of the prompt box
4721
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4722
        $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4723
        $offset += $string['size'];
4724
4725
        // offset: var; size: var; text of the error box
4726
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4727
        $error = $string['value'] !== chr(0) ? $string['value'] : '';
4728
        $offset += $string['size'];
4729
4730
        // offset: var; size: 2; size of the formula data for the first condition
4731
        $sz1 = self::getInt2d($recordData, $offset);
4732
        $offset += 2;
4733
4734
        // offset: var; size: 2; not used
4735
        $offset += 2;
4736
4737
        // offset: var; size: $sz1; formula data for first condition (without size field)
4738
        $formula1 = substr($recordData, $offset, $sz1);
4739
        $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4740
        try {
4741
            $formula1 = $this->getFormulaFromStructure($formula1);
4742
4743
            // in list type validity, null characters are used as item separators
4744
            if ($type == \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST) {
4745
                $formula1 = str_replace(chr(0), ',', $formula1);
4746
            }
4747
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4748
            return;
4749
        }
4750
        $offset += $sz1;
4751
4752
        // offset: var; size: 2; size of the formula data for the first condition
4753
        $sz2 = self::getInt2d($recordData, $offset);
4754
        $offset += 2;
4755
4756
        // offset: var; size: 2; not used
4757
        $offset += 2;
4758
4759
        // offset: var; size: $sz2; formula data for second condition (without size field)
4760
        $formula2 = substr($recordData, $offset, $sz2);
4761
        $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4762
        try {
4763
            $formula2 = $this->getFormulaFromStructure($formula2);
4764
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4765
            return;
4766
        }
4767
        $offset += $sz2;
4768
4769
        // offset: var; size: var; cell range address list with
4770
        $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4771
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4772
4773
        foreach ($cellRangeAddresses as $cellRange) {
4774
            $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4775
            foreach (\PhpOffice\PhpSpreadsheet\Cell::extractAllCellReferencesInRange($stRange) as $coordinate) {
4776
                $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4777
                $objValidation->setType($type);
4778
                $objValidation->setErrorStyle($errorStyle);
4779
                $objValidation->setAllowBlank((bool) $allowBlank);
4780
                $objValidation->setShowInputMessage((bool) $showInputMessage);
4781
                $objValidation->setShowErrorMessage((bool) $showErrorMessage);
4782
                $objValidation->setShowDropDown(!$suppressDropDown);
4783
                $objValidation->setOperator($operator);
4784
                $objValidation->setErrorTitle($errorTitle);
4785
                $objValidation->setError($error);
4786
                $objValidation->setPromptTitle($promptTitle);
4787
                $objValidation->setPrompt($prompt);
4788
                $objValidation->setFormula1($formula1);
4789
                $objValidation->setFormula2($formula2);
4790
            }
4791
        }
4792
    }
4793
4794
    /**
4795
     * Read SHEETLAYOUT record. Stores sheet tab color information.
4796
     */
4797 2
    private function readSheetLayout()
4798
    {
4799 2
        $length = self::getInt2d($this->data, $this->pos + 2);
4800 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4801
4802
        // move stream pointer to next record
4803 2
        $this->pos += 4 + $length;
4804
4805
        // local pointer in record data
4806 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...
4807
4808 2
        if (!$this->readDataOnly) {
4809
            // offset: 0; size: 2; repeated record identifier 0x0862
4810
4811
            // offset: 2; size: 10; not used
4812
4813
            // offset: 12; size: 4; size of record data
4814
            // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
4815 2
            $sz = self::getInt4d($recordData, 12);
4816
4817
            switch ($sz) {
4818 2
                case 0x14:
4819
                    // offset: 16; size: 2; color index for sheet tab
4820 1
                    $colorIndex = self::getInt2d($recordData, 16);
4821 1
                    $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
4822 1
                    $this->phpSheet->getTabColor()->setRGB($color['rgb']);
4823 1
                    break;
4824 1
                case 0x28:
4825
                    // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
4826 1
                    return;
4827
                    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...
4828
            }
4829
        }
4830 1
    }
4831
4832
    /**
4833
     * Read SHEETPROTECTION record (FEATHEADR)
4834
     */
4835 4
    private function readSheetProtection()
4836
    {
4837 4
        $length = self::getInt2d($this->data, $this->pos + 2);
4838 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4839
4840
        // move stream pointer to next record
4841 4
        $this->pos += 4 + $length;
4842
4843 4
        if ($this->readDataOnly) {
4844
            return;
4845
        }
4846
4847
        // offset: 0; size: 2; repeated record header
4848
4849
        // offset: 2; size: 2; FRT cell reference flag (=0 currently)
4850
4851
        // offset: 4; size: 8; Currently not used and set to 0
4852
4853
        // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
4854 4
        $isf = self::getInt2d($recordData, 12);
4855 4
        if ($isf != 2) {
4856
            return;
4857
        }
4858
4859
        // offset: 14; size: 1; =1 since this is a feat header
4860
4861
        // offset: 15; size: 4; size of rgbHdrSData
4862
4863
        // rgbHdrSData, assume "Enhanced Protection"
4864
        // offset: 19; size: 2; option flags
4865 4
        $options = self::getInt2d($recordData, 19);
4866
4867
        // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
4868 4
        $bool = (0x0001 & $options) >> 0;
4869 4
        $this->phpSheet->getProtection()->setObjects(!$bool);
4870
4871
        // bit: 1; mask 0x0002; edit scenarios
4872 4
        $bool = (0x0002 & $options) >> 1;
4873 4
        $this->phpSheet->getProtection()->setScenarios(!$bool);
4874
4875
        // bit: 2; mask 0x0004; format cells
4876 4
        $bool = (0x0004 & $options) >> 2;
4877 4
        $this->phpSheet->getProtection()->setFormatCells(!$bool);
4878
4879
        // bit: 3; mask 0x0008; format columns
4880 4
        $bool = (0x0008 & $options) >> 3;
4881 4
        $this->phpSheet->getProtection()->setFormatColumns(!$bool);
4882
4883
        // bit: 4; mask 0x0010; format rows
4884 4
        $bool = (0x0010 & $options) >> 4;
4885 4
        $this->phpSheet->getProtection()->setFormatRows(!$bool);
4886
4887
        // bit: 5; mask 0x0020; insert columns
4888 4
        $bool = (0x0020 & $options) >> 5;
4889 4
        $this->phpSheet->getProtection()->setInsertColumns(!$bool);
4890
4891
        // bit: 6; mask 0x0040; insert rows
4892 4
        $bool = (0x0040 & $options) >> 6;
4893 4
        $this->phpSheet->getProtection()->setInsertRows(!$bool);
4894
4895
        // bit: 7; mask 0x0080; insert hyperlinks
4896 4
        $bool = (0x0080 & $options) >> 7;
4897 4
        $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
4898
4899
        // bit: 8; mask 0x0100; delete columns
4900 4
        $bool = (0x0100 & $options) >> 8;
4901 4
        $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
4902
4903
        // bit: 9; mask 0x0200; delete rows
4904 4
        $bool = (0x0200 & $options) >> 9;
4905 4
        $this->phpSheet->getProtection()->setDeleteRows(!$bool);
4906
4907
        // bit: 10; mask 0x0400; select locked cells
4908 4
        $bool = (0x0400 & $options) >> 10;
4909 4
        $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
4910
4911
        // bit: 11; mask 0x0800; sort cell range
4912 4
        $bool = (0x0800 & $options) >> 11;
4913 4
        $this->phpSheet->getProtection()->setSort(!$bool);
4914
4915
        // bit: 12; mask 0x1000; auto filter
4916 4
        $bool = (0x1000 & $options) >> 12;
4917 4
        $this->phpSheet->getProtection()->setAutoFilter(!$bool);
4918
4919
        // bit: 13; mask 0x2000; pivot tables
4920 4
        $bool = (0x2000 & $options) >> 13;
4921 4
        $this->phpSheet->getProtection()->setPivotTables(!$bool);
4922
4923
        // bit: 14; mask 0x4000; select unlocked cells
4924 4
        $bool = (0x4000 & $options) >> 14;
4925 4
        $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
4926
4927
        // offset: 21; size: 2; not used
4928 4
    }
4929
4930
    /**
4931
     * Read RANGEPROTECTION record
4932
     * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
4933
     * where it is referred to as FEAT record
4934
     */
4935 1
    private function readRangeProtection()
4936
    {
4937 1
        $length = self::getInt2d($this->data, $this->pos + 2);
4938 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4939
4940
        // move stream pointer to next record
4941 1
        $this->pos += 4 + $length;
4942
4943
        // local pointer in record data
4944 1
        $offset = 0;
4945
4946 1
        if (!$this->readDataOnly) {
4947 1
            $offset += 12;
4948
4949
            // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
4950 1
            $isf = self::getInt2d($recordData, 12);
4951 1
            if ($isf != 2) {
4952
                // we only read FEAT records of type 2
4953
                return;
4954
            }
4955 1
            $offset += 2;
4956
4957 1
            $offset += 5;
4958
4959
            // offset: 19; size: 2; count of ref ranges this feature is on
4960 1
            $cref = self::getInt2d($recordData, 19);
4961 1
            $offset += 2;
4962
4963 1
            $offset += 6;
4964
4965
            // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
4966 1
            $cellRanges = [];
4967 1
            for ($i = 0; $i < $cref; ++$i) {
4968
                try {
4969 1
                    $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
4970
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
4971
                    return;
4972
                }
4973 1
                $cellRanges[] = $cellRange;
4974 1
                $offset += 8;
4975
            }
4976
4977
            // offset: var; size: var; variable length of feature specific data
4978 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...
4979 1
            $offset += 4;
4980
4981
            // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
4982 1
            $wPassword = self::getInt4d($recordData, $offset);
4983 1
            $offset += 4;
4984
4985
            // Apply range protection to sheet
4986 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...
4987 1
                $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
4988
            }
4989
        }
4990 1
    }
4991
4992
    /**
4993
     * Read IMDATA record
4994
     */
4995
    private function readImData()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
4996
    {
4997
        $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...
4998
4999
        // get spliced record data
5000
        $splicedRecordData = $this->getSplicedRecordData();
5001
        $recordData = $splicedRecordData['recordData'];
5002
5003
        // UNDER CONSTRUCTION
5004
5005
        // offset: 0; size: 2; image format
5006
        $cf = self::getInt2d($recordData, 0);
5007
5008
        // offset: 2; size: 2; environment from which the file was written
5009
        $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...
5010
5011
        // offset: 4; size: 4; length of the image data
5012
        $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...
5013
5014
        // offset: 8; size: var; image data
5015
        $iData = substr($recordData, 8);
5016
5017
        switch ($cf) {
5018
            case 0x09: // Windows bitmap format
5019
                // BITMAPCOREINFO
5020
                // 1. BITMAPCOREHEADER
5021
                // offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure
5022
                $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...
5023
5024
                // offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels
5025
                $bcWidth = self::getInt2d($iData, 4);
5026
5027
                // offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels.
5028
                $bcHeight = self::getInt2d($iData, 6);
5029
                $ih = imagecreatetruecolor($bcWidth, $bcHeight);
5030
5031
                // offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1
5032
5033
                // offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24
5034
                $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...
5035
5036
                $rgbString = substr($iData, 12);
5037
                $rgbTriples = [];
5038
                while (strlen($rgbString) > 0) {
5039
                    $rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString);
5040
                    $rgbString = substr($rgbString, 3);
5041
                }
5042
                $x = 0;
5043
                $y = 0;
5044
                foreach ($rgbTriples as $i => $rgbTriple) {
5045
                    $color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']);
5046
                    imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color);
5047
                    $x = ($x + 1) % $bcWidth;
5048
                    $y = $y + floor(($x + 1) / $bcWidth);
5049
                }
5050
                //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...
5051
5052
                $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
5053
                $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...
5054
                $drawing->setWorksheet($this->phpSheet);
5055
                break;
5056
            case 0x02: // Windows metafile or Macintosh PICT format
5057
            case 0x0e: // native format
5058
            default:
5059
                break;
5060
        }
5061
5062
        // getSplicedRecordData() takes care of moving current position in data stream
5063
    }
5064
5065
    /**
5066
     * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5067
     * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5068
     * In this case, we must treat the CONTINUE record as a MSODRAWING record
5069
     */
5070
    private function readContinue()
5071
    {
5072
        $length = self::getInt2d($this->data, $this->pos + 2);
5073
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5074
5075
        // check if we are reading drawing data
5076
        // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5077
        if ($this->drawingData == '') {
5078
            // move stream pointer to next record
5079
            $this->pos += 4 + $length;
5080
5081
            return;
5082
        }
5083
5084
        // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5085
        if ($length < 4) {
5086
            // move stream pointer to next record
5087
            $this->pos += 4 + $length;
5088
5089
            return;
5090
        }
5091
5092
        // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5093
        // look inside CONTINUE record to see if it looks like a part of an Escher stream
5094
        // we know that Escher stream may be split at least at
5095
        //        0xF003 MsofbtSpgrContainer
5096
        //        0xF004 MsofbtSpContainer
5097
        //        0xF00D MsofbtClientTextbox
5098
        $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
5099
5100
        $splitPoint = self::getInt2d($recordData, 2);
5101
        if (in_array($splitPoint, $validSplitPoints)) {
5102
            // get spliced record data (and move pointer to next record)
5103
            $splicedRecordData = $this->getSplicedRecordData();
5104
            $this->drawingData .= $splicedRecordData['recordData'];
5105
5106
            return;
5107
        }
5108
5109
        // move stream pointer to next record
5110
        $this->pos += 4 + $length;
5111
    }
5112
5113
    /**
5114
     * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5115
     * records are found. Splices the record data pieces and returns the combined string as if record data
5116
     * is in one piece.
5117
     * Moves to next current position in data stream to start of next record different from a CONtINUE record
5118
     *
5119
     * @return array
5120
     */
5121 4
    private function getSplicedRecordData()
5122
    {
5123 4
        $data = '';
5124 4
        $spliceOffsets = [];
5125
5126 4
        $i = 0;
5127 4
        $spliceOffsets[0] = 0;
5128
5129
        do {
5130 4
            ++$i;
5131
5132
            // offset: 0; size: 2; identifier
5133 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...
5134
            // offset: 2; size: 2; length
5135 4
            $length = self::getInt2d($this->data, $this->pos + 2);
5136 4
            $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5137
5138 4
            $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5139
5140 4
            $this->pos += 4 + $length;
5141 4
            $nextIdentifier = self::getInt2d($this->data, $this->pos);
5142 4
        } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5143
5144
        $splicedData = [
5145 4
            'recordData' => $data,
5146 4
            'spliceOffsets' => $spliceOffsets,
5147
        ];
5148
5149 4
        return $splicedData;
5150
    }
5151
5152
    /**
5153
     * Convert formula structure into human readable Excel formula like 'A3+A5*5'
5154
     *
5155
     * @param string $formulaStructure The complete binary data for the formula
5156
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5157
     * @return string Human readable formula
5158
     */
5159 2
    private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5160
    {
5161
        // offset: 0; size: 2; size of the following formula data
5162 2
        $sz = self::getInt2d($formulaStructure, 0);
5163
5164
        // offset: 2; size: sz
5165 2
        $formulaData = substr($formulaStructure, 2, $sz);
5166
5167
        // offset: 2 + sz; size: variable (optional)
5168 2
        if (strlen($formulaStructure) > 2 + $sz) {
5169
            $additionalData = substr($formulaStructure, 2 + $sz);
5170
        } else {
5171 2
            $additionalData = '';
5172
        }
5173
5174 2
        return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5175
    }
5176
5177
    /**
5178
     * Take formula data and additional data for formula and return human readable formula
5179
     *
5180
     * @param string $formulaData The binary data for the formula itself
5181
     * @param string $additionalData Additional binary data going with the formula
5182
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5183
     * @return string Human readable formula
5184
     */
5185 2
    private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5186
    {
5187
        // start parsing the formula data
5188 2
        $tokens = [];
5189
5190 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...
5191 2
            $tokens[] = $token;
5192 2
            $formulaData = substr($formulaData, $token['size']);
5193
        }
5194
5195 2
        $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5196
5197 2
        return $formulaString;
5198
    }
5199
5200
    /**
5201
     * Take array of tokens together with additional data for formula and return human readable formula
5202
     *
5203
     * @param array $tokens
5204
     * @param string $additionalData Additional binary data going with the formula
5205
     * @return string Human readable formula
5206
     */
5207 2
    private function createFormulaFromTokens($tokens, $additionalData)
5208
    {
5209
        // empty formula?
5210 2
        if (empty($tokens)) {
5211
            return '';
5212
        }
5213
5214 2
        $formulaStrings = [];
5215 2
        foreach ($tokens as $token) {
5216
            // initialize spaces
5217 2
            $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5218 2
            $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5219 2
            $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5220 2
            $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5221 2
            $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5222 2
            $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5223
5224 2
            switch ($token['name']) {
5225 2
                case 'tAdd': // addition
5226 2
                case 'tConcat': // addition
5227 2
                case 'tDiv': // division
5228 2
                case 'tEQ': // equality
5229 2
                case 'tGE': // greater than or equal
5230 2
                case 'tGT': // greater than
5231 2
                case 'tIsect': // intersection
5232 2
                case 'tLE': // less than or equal
5233 2
                case 'tList': // less than or equal
5234 2
                case 'tLT': // less than
5235 2
                case 'tMul': // multiplication
5236 2
                case 'tNE': // multiplication
5237 2
                case 'tPower': // power
5238 2
                case 'tRange': // range
5239 2
                case 'tSub': // subtraction
5240 2
                    $op2 = array_pop($formulaStrings);
5241 2
                    $op1 = array_pop($formulaStrings);
5242 2
                    $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5243 2
                    unset($space0, $space1);
5244 2
                    break;
5245 2
                case 'tUplus': // unary plus
5246 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...
5247
                    $op = array_pop($formulaStrings);
5248
                    $formulaStrings[] = "$space1$space0{$token['data']}$op";
5249
                    unset($space0, $space1);
5250
                    break;
5251 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...
5252
                    $op = array_pop($formulaStrings);
5253
                    $formulaStrings[] = "$op$space1$space0{$token['data']}";
5254
                    unset($space0, $space1);
5255
                    break;
5256 2
                case 'tAttrVolatile': // indicates volatile function
5257 2
                case 'tAttrIf':
5258 2
                case 'tAttrSkip':
5259 2
                case 'tAttrChoose':
5260
                    // token is only important for Excel formula evaluator
5261
                    // do nothing
5262
                    break;
5263 2
                case 'tAttrSpace': // space / carriage return
5264
                    // space will be used when next token arrives, do not alter formulaString stack
5265
                    switch ($token['data']['spacetype']) {
5266
                        case 'type0':
5267
                            $space0 = str_repeat(' ', $token['data']['spacecount']);
5268
                            break;
5269
                        case 'type1':
5270
                            $space1 = str_repeat("\n", $token['data']['spacecount']);
5271
                            break;
5272
                        case 'type2':
5273
                            $space2 = str_repeat(' ', $token['data']['spacecount']);
5274
                            break;
5275
                        case 'type3':
5276
                            $space3 = str_repeat("\n", $token['data']['spacecount']);
5277
                            break;
5278
                        case 'type4':
5279
                            $space4 = str_repeat(' ', $token['data']['spacecount']);
5280
                            break;
5281
                        case 'type5':
5282
                            $space5 = str_repeat("\n", $token['data']['spacecount']);
5283
                            break;
5284
                    }
5285
                    break;
5286 2
                case 'tAttrSum': // SUM function with one parameter
5287 1
                    $op = array_pop($formulaStrings);
5288 1
                    $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5289 1
                    unset($space0, $space1);
5290 1
                    break;
5291 2
                case 'tFunc': // function with fixed number of arguments
5292 2
                case 'tFuncV': // function with variable number of arguments
5293 1
                    if ($token['data']['function'] != '') {
5294
                        // normal function
5295 1
                        $ops = []; // array of operators
5296 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...
5297 1
                            $ops[] = array_pop($formulaStrings);
5298
                        }
5299 1
                        $ops = array_reverse($ops);
5300 1
                        $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
5301 1
                        unset($space0, $space1);
5302
                    } else {
5303
                        // add-in function
5304
                        $ops = []; // array of operators
5305 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...
5306
                            $ops[] = array_pop($formulaStrings);
5307
                        }
5308
                        $ops = array_reverse($ops);
5309
                        $function = array_pop($formulaStrings);
5310
                        $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
5311
                        unset($space0, $space1);
5312
                    }
5313 1
                    break;
5314 2
                case 'tParen': // parenthesis
5315
                    $expression = array_pop($formulaStrings);
5316
                    $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5317
                    unset($space2, $space3, $space4, $space5);
5318
                    break;
5319 2
                case 'tArray': // array constant
5320
                    $constantArray = self::readBIFF8ConstantArray($additionalData);
5321
                    $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5322
                    $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5323
                    unset($space0, $space1);
5324
                    break;
5325 2
                case 'tMemArea':
5326
                    // bite off chunk of additional data
5327
                    $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5328
                    $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5329
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5330
                    unset($space0, $space1);
5331
                    break;
5332 2
                case 'tArea': // cell range address
5333 2
                case 'tBool': // boolean
5334 2
                case 'tErr': // error code
5335 2
                case 'tInt': // integer
5336 2
                case 'tMemErr':
5337 2
                case 'tMemFunc':
5338 2
                case 'tMissArg':
5339 2
                case 'tName':
5340 2
                case 'tNameX':
5341 2
                case 'tNum': // number
5342 2
                case 'tRef': // single cell reference
5343 2
                case 'tRef3d': // 3d cell reference
5344 2
                case 'tArea3d': // 3d cell range reference
5345 1
                case 'tRefN':
5346 1
                case 'tAreaN':
5347 1
                case 'tStr': // string
5348 2
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5349 2
                    unset($space0, $space1);
5350 2
                    break;
5351
            }
5352
        }
5353 2
        $formulaString = $formulaStrings[0];
5354
5355 2
        return $formulaString;
5356
    }
5357
5358
    /**
5359
     * Fetch next token from binary formula data
5360
     *
5361
     * @param string $formulaData Formula data
5362
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5363
     * @throws Exception
5364
     * @return array
5365
     */
5366 2
    private function getNextToken($formulaData, $baseCell = 'A1')
5367
    {
5368
        // offset: 0; size: 1; token id
5369 2
        $id = ord($formulaData[0]); // token id
5370 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...
5371
5372
        switch ($id) {
5373 2
            case 0x03:
5374 1
                $name = 'tAdd';
5375 1
                $size = 1;
5376 1
                $data = '+';
5377 1
                break;
5378 2
            case 0x04:
5379
                $name = 'tSub';
5380
                $size = 1;
5381
                $data = '-';
5382
                break;
5383 2
            case 0x05:
5384 2
                $name = 'tMul';
5385 2
                $size = 1;
5386 2
                $data = '*';
5387 2
                break;
5388 2
            case 0x06:
5389
                $name = 'tDiv';
5390
                $size = 1;
5391
                $data = '/';
5392
                break;
5393 2
            case 0x07:
5394
                $name = 'tPower';
5395
                $size = 1;
5396
                $data = '^';
5397
                break;
5398 2
            case 0x08:
5399
                $name = 'tConcat';
5400
                $size = 1;
5401
                $data = '&';
5402
                break;
5403 2
            case 0x09:
5404
                $name = 'tLT';
5405
                $size = 1;
5406
                $data = '<';
5407
                break;
5408 2
            case 0x0A:
5409
                $name = 'tLE';
5410
                $size = 1;
5411
                $data = '<=';
5412
                break;
5413 2
            case 0x0B:
5414
                $name = 'tEQ';
5415
                $size = 1;
5416
                $data = '=';
5417
                break;
5418 2
            case 0x0C:
5419
                $name = 'tGE';
5420
                $size = 1;
5421
                $data = '>=';
5422
                break;
5423 2
            case 0x0D:
5424
                $name = 'tGT';
5425
                $size = 1;
5426
                $data = '>';
5427
                break;
5428 2
            case 0x0E:
5429 1
                $name = 'tNE';
5430 1
                $size = 1;
5431 1
                $data = '<>';
5432 1
                break;
5433 2
            case 0x0F:
5434
                $name = 'tIsect';
5435
                $size = 1;
5436
                $data = ' ';
5437
                break;
5438 2
            case 0x10:
5439
                $name = 'tList';
5440
                $size = 1;
5441
                $data = ',';
5442
                break;
5443 2
            case 0x11:
5444
                $name = 'tRange';
5445
                $size = 1;
5446
                $data = ':';
5447
                break;
5448 2
            case 0x12:
5449
                $name = 'tUplus';
5450
                $size = 1;
5451
                $data = '+';
5452
                break;
5453 2
            case 0x13:
5454
                $name = 'tUminus';
5455
                $size = 1;
5456
                $data = '-';
5457
                break;
5458 2
            case 0x14:
5459
                $name = 'tPercent';
5460
                $size = 1;
5461
                $data = '%';
5462
                break;
5463 2
            case 0x15:    //    parenthesis
5464
                $name = 'tParen';
5465
                $size = 1;
5466
                $data = null;
5467
                break;
5468 2
            case 0x16:    //    missing argument
5469
                $name = 'tMissArg';
5470
                $size = 1;
5471
                $data = '';
5472
                break;
5473 2
            case 0x17:    //    string
5474 1
                $name = 'tStr';
5475
                // offset: 1; size: var; Unicode string, 8-bit string length
5476 1
                $string = self::readUnicodeStringShort(substr($formulaData, 1));
5477 1
                $size = 1 + $string['size'];
5478 1
                $data = self::UTF8toExcelDoubleQuoted($string['value']);
5479 1
                break;
5480 2
            case 0x19:    //    Special attribute
5481
                // offset: 1; size: 1; attribute type flags:
5482 1
                switch (ord($formulaData[1])) {
5483 1
                    case 0x01:
5484
                        $name = 'tAttrVolatile';
5485
                        $size = 4;
5486
                        $data = null;
5487
                        break;
5488 1
                    case 0x02:
5489
                        $name = 'tAttrIf';
5490
                        $size = 4;
5491
                        $data = null;
5492
                        break;
5493 1
                    case 0x04:
5494
                        $name = 'tAttrChoose';
5495
                        // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5496
                        $nc = self::getInt2d($formulaData, 2);
5497
                        // offset: 4; size: 2 * $nc
5498
                        // offset: 4 + 2 * $nc; size: 2
5499
                        $size = 2 * $nc + 6;
5500
                        $data = null;
5501
                        break;
5502 1
                    case 0x08:
5503
                        $name = 'tAttrSkip';
5504
                        $size = 4;
5505
                        $data = null;
5506
                        break;
5507 1
                    case 0x10:
5508 1
                        $name = 'tAttrSum';
5509 1
                        $size = 4;
5510 1
                        $data = null;
5511 1
                        break;
5512
                    case 0x40:
5513
                    case 0x41:
5514
                        $name = 'tAttrSpace';
5515
                        $size = 4;
5516
                        // offset: 2; size: 2; space type and position
5517
                        switch (ord($formulaData[2])) {
5518
                            case 0x00:
5519
                                $spacetype = 'type0';
5520
                                break;
5521
                            case 0x01:
5522
                                $spacetype = 'type1';
5523
                                break;
5524
                            case 0x02:
5525
                                $spacetype = 'type2';
5526
                                break;
5527
                            case 0x03:
5528
                                $spacetype = 'type3';
5529
                                break;
5530
                            case 0x04:
5531
                                $spacetype = 'type4';
5532
                                break;
5533
                            case 0x05:
5534
                                $spacetype = 'type5';
5535
                                break;
5536
                            default:
5537
                                throw new Exception('Unrecognized space type in tAttrSpace token');
5538
                                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...
5539
                        }
5540
                        // offset: 3; size: 1; number of inserted spaces/carriage returns
5541
                        $spacecount = ord($formulaData[3]);
5542
5543
                        $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
5544
                        break;
5545
                    default:
5546
                        throw new Exception('Unrecognized attribute flag in tAttr token');
5547
                        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...
5548
                }
5549 1
                break;
5550 2
            case 0x1C:    //    error code
5551
                // offset: 1; size: 1; error code
5552
                $name = 'tErr';
5553
                $size = 2;
5554
                $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
5555
                break;
5556 2
            case 0x1D:    //    boolean
5557
                // 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...
5558
                $name = 'tBool';
5559
                $size = 2;
5560
                $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5561
                break;
5562 2
            case 0x1E:    //    integer
5563
                // offset: 1; size: 2; unsigned 16-bit integer
5564
                $name = 'tInt';
5565
                $size = 3;
5566
                $data = self::getInt2d($formulaData, 1);
5567
                break;
5568 2
            case 0x1F:    //    number
5569
                // 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...
5570 2
                $name = 'tNum';
5571 2
                $size = 9;
5572 2
                $data = self::extractNumber(substr($formulaData, 1));
5573 2
                $data = str_replace(',', '.', (string) $data); // in case non-English locale
5574 2
                break;
5575 2
            case 0x20:    //    array constant
5576 2
            case 0x40:
5577 2
            case 0x60:
5578
                // offset: 1; size: 7; not used
5579
                $name = 'tArray';
5580
                $size = 8;
5581
                $data = null;
5582
                break;
5583 2
            case 0x21:    //    function with fixed number of arguments
5584 2
            case 0x41:
5585 2
            case 0x61:
5586
                $name = 'tFunc';
5587
                $size = 3;
5588
                // offset: 1; size: 2; index to built-in sheet function
5589
                switch (self::getInt2d($formulaData, 1)) {
5590
                    case 2:
5591
                        $function = 'ISNA';
5592
                        $args = 1;
5593
                        break;
5594
                    case 3:
5595
                        $function = 'ISERROR';
5596
                        $args = 1;
5597
                        break;
5598
                    case 10:
5599
                        $function = 'NA';
5600
                        $args = 0;
5601
                        break;
5602
                    case 15:
5603
                        $function = 'SIN';
5604
                        $args = 1;
5605
                        break;
5606
                    case 16:
5607
                        $function = 'COS';
5608
                        $args = 1;
5609
                        break;
5610
                    case 17:
5611
                        $function = 'TAN';
5612
                        $args = 1;
5613
                        break;
5614
                    case 18:
5615
                        $function = 'ATAN';
5616
                        $args = 1;
5617
                        break;
5618
                    case 19:
5619
                        $function = 'PI';
5620
                        $args = 0;
5621
                        break;
5622
                    case 20:
5623
                        $function = 'SQRT';
5624
                        $args = 1;
5625
                        break;
5626
                    case 21:
5627
                        $function = 'EXP';
5628
                        $args = 1;
5629
                        break;
5630
                    case 22:
5631
                        $function = 'LN';
5632
                        $args = 1;
5633
                        break;
5634
                    case 23:
5635
                        $function = 'LOG10';
5636
                        $args = 1;
5637
                        break;
5638
                    case 24:
5639
                        $function = 'ABS';
5640
                        $args = 1;
5641
                        break;
5642
                    case 25:
5643
                        $function = 'INT';
5644
                        $args = 1;
5645
                        break;
5646
                    case 26:
5647
                        $function = 'SIGN';
5648
                        $args = 1;
5649
                        break;
5650
                    case 27:
5651
                        $function = 'ROUND';
5652
                        $args = 2;
5653
                        break;
5654
                    case 30:
5655
                        $function = 'REPT';
5656
                        $args = 2;
5657
                        break;
5658
                    case 31:
5659
                        $function = 'MID';
5660
                        $args = 3;
5661
                        break;
5662
                    case 32:
5663
                        $function = 'LEN';
5664
                        $args = 1;
5665
                        break;
5666
                    case 33:
5667
                        $function = 'VALUE';
5668
                        $args = 1;
5669
                        break;
5670
                    case 34:
5671
                        $function = 'TRUE';
5672
                        $args = 0;
5673
                        break;
5674
                    case 35:
5675
                        $function = 'FALSE';
5676
                        $args = 0;
5677
                        break;
5678
                    case 38:
5679
                        $function = 'NOT';
5680
                        $args = 1;
5681
                        break;
5682
                    case 39:
5683
                        $function = 'MOD';
5684
                        $args = 2;
5685
                        break;
5686
                    case 40:
5687
                        $function = 'DCOUNT';
5688
                        $args = 3;
5689
                        break;
5690
                    case 41:
5691
                        $function = 'DSUM';
5692
                        $args = 3;
5693
                        break;
5694
                    case 42:
5695
                        $function = 'DAVERAGE';
5696
                        $args = 3;
5697
                        break;
5698
                    case 43:
5699
                        $function = 'DMIN';
5700
                        $args = 3;
5701
                        break;
5702
                    case 44:
5703
                        $function = 'DMAX';
5704
                        $args = 3;
5705
                        break;
5706
                    case 45:
5707
                        $function = 'DSTDEV';
5708
                        $args = 3;
5709
                        break;
5710
                    case 48:
5711
                        $function = 'TEXT';
5712
                        $args = 2;
5713
                        break;
5714
                    case 61:
5715
                        $function = 'MIRR';
5716
                        $args = 3;
5717
                        break;
5718
                    case 63:
5719
                        $function = 'RAND';
5720
                        $args = 0;
5721
                        break;
5722
                    case 65:
5723
                        $function = 'DATE';
5724
                        $args = 3;
5725
                        break;
5726
                    case 66:
5727
                        $function = 'TIME';
5728
                        $args = 3;
5729
                        break;
5730
                    case 67:
5731
                        $function = 'DAY';
5732
                        $args = 1;
5733
                        break;
5734
                    case 68:
5735
                        $function = 'MONTH';
5736
                        $args = 1;
5737
                        break;
5738
                    case 69:
5739
                        $function = 'YEAR';
5740
                        $args = 1;
5741
                        break;
5742
                    case 71:
5743
                        $function = 'HOUR';
5744
                        $args = 1;
5745
                        break;
5746
                    case 72:
5747
                        $function = 'MINUTE';
5748
                        $args = 1;
5749
                        break;
5750
                    case 73:
5751
                        $function = 'SECOND';
5752
                        $args = 1;
5753
                        break;
5754
                    case 74:
5755
                        $function = 'NOW';
5756
                        $args = 0;
5757
                        break;
5758
                    case 75:
5759
                        $function = 'AREAS';
5760
                        $args = 1;
5761
                        break;
5762
                    case 76:
5763
                        $function = 'ROWS';
5764
                        $args = 1;
5765
                        break;
5766
                    case 77:
5767
                        $function = 'COLUMNS';
5768
                        $args = 1;
5769
                        break;
5770
                    case 83:
5771
                        $function = 'TRANSPOSE';
5772
                        $args = 1;
5773
                        break;
5774
                    case 86:
5775
                        $function = 'TYPE';
5776
                        $args = 1;
5777
                        break;
5778
                    case 97:
5779
                        $function = 'ATAN2';
5780
                        $args = 2;
5781
                        break;
5782
                    case 98:
5783
                        $function = 'ASIN';
5784
                        $args = 1;
5785
                        break;
5786
                    case 99:
5787
                        $function = 'ACOS';
5788
                        $args = 1;
5789
                        break;
5790
                    case 105:
5791
                        $function = 'ISREF';
5792
                        $args = 1;
5793
                        break;
5794
                    case 111:
5795
                        $function = 'CHAR';
5796
                        $args = 1;
5797
                        break;
5798
                    case 112:
5799
                        $function = 'LOWER';
5800
                        $args = 1;
5801
                        break;
5802
                    case 113:
5803
                        $function = 'UPPER';
5804
                        $args = 1;
5805
                        break;
5806
                    case 114:
5807
                        $function = 'PROPER';
5808
                        $args = 1;
5809
                        break;
5810
                    case 117:
5811
                        $function = 'EXACT';
5812
                        $args = 2;
5813
                        break;
5814
                    case 118:
5815
                        $function = 'TRIM';
5816
                        $args = 1;
5817
                        break;
5818
                    case 119:
5819
                        $function = 'REPLACE';
5820
                        $args = 4;
5821
                        break;
5822
                    case 121:
5823
                        $function = 'CODE';
5824
                        $args = 1;
5825
                        break;
5826
                    case 126:
5827
                        $function = 'ISERR';
5828
                        $args = 1;
5829
                        break;
5830
                    case 127:
5831
                        $function = 'ISTEXT';
5832
                        $args = 1;
5833
                        break;
5834
                    case 128:
5835
                        $function = 'ISNUMBER';
5836
                        $args = 1;
5837
                        break;
5838
                    case 129:
5839
                        $function = 'ISBLANK';
5840
                        $args = 1;
5841
                        break;
5842
                    case 130:
5843
                        $function = 'T';
5844
                        $args = 1;
5845
                        break;
5846
                    case 131:
5847
                        $function = 'N';
5848
                        $args = 1;
5849
                        break;
5850
                    case 140:
5851
                        $function = 'DATEVALUE';
5852
                        $args = 1;
5853
                        break;
5854
                    case 141:
5855
                        $function = 'TIMEVALUE';
5856
                        $args = 1;
5857
                        break;
5858
                    case 142:
5859
                        $function = 'SLN';
5860
                        $args = 3;
5861
                        break;
5862
                    case 143:
5863
                        $function = 'SYD';
5864
                        $args = 4;
5865
                        break;
5866
                    case 162:
5867
                        $function = 'CLEAN';
5868
                        $args = 1;
5869
                        break;
5870
                    case 163:
5871
                        $function = 'MDETERM';
5872
                        $args = 1;
5873
                        break;
5874
                    case 164:
5875
                        $function = 'MINVERSE';
5876
                        $args = 1;
5877
                        break;
5878
                    case 165:
5879
                        $function = 'MMULT';
5880
                        $args = 2;
5881
                        break;
5882
                    case 184:
5883
                        $function = 'FACT';
5884
                        $args = 1;
5885
                        break;
5886
                    case 189:
5887
                        $function = 'DPRODUCT';
5888
                        $args = 3;
5889
                        break;
5890
                    case 190:
5891
                        $function = 'ISNONTEXT';
5892
                        $args = 1;
5893
                        break;
5894
                    case 195:
5895
                        $function = 'DSTDEVP';
5896
                        $args = 3;
5897
                        break;
5898
                    case 196:
5899
                        $function = 'DVARP';
5900
                        $args = 3;
5901
                        break;
5902
                    case 198:
5903
                        $function = 'ISLOGICAL';
5904
                        $args = 1;
5905
                        break;
5906
                    case 199:
5907
                        $function = 'DCOUNTA';
5908
                        $args = 3;
5909
                        break;
5910
                    case 207:
5911
                        $function = 'REPLACEB';
5912
                        $args = 4;
5913
                        break;
5914
                    case 210:
5915
                        $function = 'MIDB';
5916
                        $args = 3;
5917
                        break;
5918
                    case 211:
5919
                        $function = 'LENB';
5920
                        $args = 1;
5921
                        break;
5922
                    case 212:
5923
                        $function = 'ROUNDUP';
5924
                        $args = 2;
5925
                        break;
5926
                    case 213:
5927
                        $function = 'ROUNDDOWN';
5928
                        $args = 2;
5929
                        break;
5930
                    case 214:
5931
                        $function = 'ASC';
5932
                        $args = 1;
5933
                        break;
5934
                    case 215:
5935
                        $function = 'DBCS';
5936
                        $args = 1;
5937
                        break;
5938
                    case 221:
5939
                        $function = 'TODAY';
5940
                        $args = 0;
5941
                        break;
5942
                    case 229:
5943
                        $function = 'SINH';
5944
                        $args = 1;
5945
                        break;
5946
                    case 230:
5947
                        $function = 'COSH';
5948
                        $args = 1;
5949
                        break;
5950
                    case 231:
5951
                        $function = 'TANH';
5952
                        $args = 1;
5953
                        break;
5954
                    case 232:
5955
                        $function = 'ASINH';
5956
                        $args = 1;
5957
                        break;
5958
                    case 233:
5959
                        $function = 'ACOSH';
5960
                        $args = 1;
5961
                        break;
5962
                    case 234:
5963
                        $function = 'ATANH';
5964
                        $args = 1;
5965
                        break;
5966
                    case 235:
5967
                        $function = 'DGET';
5968
                        $args = 3;
5969
                        break;
5970
                    case 244:
5971
                        $function = 'INFO';
5972
                        $args = 1;
5973
                        break;
5974
                    case 252:
5975
                        $function = 'FREQUENCY';
5976
                        $args = 2;
5977
                        break;
5978
                    case 261:
5979
                        $function = 'ERROR.TYPE';
5980
                        $args = 1;
5981
                        break;
5982
                    case 271:
5983
                        $function = 'GAMMALN';
5984
                        $args = 1;
5985
                        break;
5986
                    case 273:
5987
                        $function = 'BINOMDIST';
5988
                        $args = 4;
5989
                        break;
5990
                    case 274:
5991
                        $function = 'CHIDIST';
5992
                        $args = 2;
5993
                        break;
5994
                    case 275:
5995
                        $function = 'CHIINV';
5996
                        $args = 2;
5997
                        break;
5998
                    case 276:
5999
                        $function = 'COMBIN';
6000
                        $args = 2;
6001
                        break;
6002
                    case 277:
6003
                        $function = 'CONFIDENCE';
6004
                        $args = 3;
6005
                        break;
6006
                    case 278:
6007
                        $function = 'CRITBINOM';
6008
                        $args = 3;
6009
                        break;
6010
                    case 279:
6011
                        $function = 'EVEN';
6012
                        $args = 1;
6013
                        break;
6014
                    case 280:
6015
                        $function = 'EXPONDIST';
6016
                        $args = 3;
6017
                        break;
6018
                    case 281:
6019
                        $function = 'FDIST';
6020
                        $args = 3;
6021
                        break;
6022
                    case 282:
6023
                        $function = 'FINV';
6024
                        $args = 3;
6025
                        break;
6026
                    case 283:
6027
                        $function = 'FISHER';
6028
                        $args = 1;
6029
                        break;
6030
                    case 284:
6031
                        $function = 'FISHERINV';
6032
                        $args = 1;
6033
                        break;
6034
                    case 285:
6035
                        $function = 'FLOOR';
6036
                        $args = 2;
6037
                        break;
6038
                    case 286:
6039
                        $function = 'GAMMADIST';
6040
                        $args = 4;
6041
                        break;
6042
                    case 287:
6043
                        $function = 'GAMMAINV';
6044
                        $args = 3;
6045
                        break;
6046
                    case 288:
6047
                        $function = 'CEILING';
6048
                        $args = 2;
6049
                        break;
6050
                    case 289:
6051
                        $function = 'HYPGEOMDIST';
6052
                        $args = 4;
6053
                        break;
6054
                    case 290:
6055
                        $function = 'LOGNORMDIST';
6056
                        $args = 3;
6057
                        break;
6058
                    case 291:
6059
                        $function = 'LOGINV';
6060
                        $args = 3;
6061
                        break;
6062
                    case 292:
6063
                        $function = 'NEGBINOMDIST';
6064
                        $args = 3;
6065
                        break;
6066
                    case 293:
6067
                        $function = 'NORMDIST';
6068
                        $args = 4;
6069
                        break;
6070
                    case 294:
6071
                        $function = 'NORMSDIST';
6072
                        $args = 1;
6073
                        break;
6074
                    case 295:
6075
                        $function = 'NORMINV';
6076
                        $args = 3;
6077
                        break;
6078
                    case 296:
6079
                        $function = 'NORMSINV';
6080
                        $args = 1;
6081
                        break;
6082
                    case 297:
6083
                        $function = 'STANDARDIZE';
6084
                        $args = 3;
6085
                        break;
6086
                    case 298:
6087
                        $function = 'ODD';
6088
                        $args = 1;
6089
                        break;
6090
                    case 299:
6091
                        $function = 'PERMUT';
6092
                        $args = 2;
6093
                        break;
6094
                    case 300:
6095
                        $function = 'POISSON';
6096
                        $args = 3;
6097
                        break;
6098
                    case 301:
6099
                        $function = 'TDIST';
6100
                        $args = 3;
6101
                        break;
6102
                    case 302:
6103
                        $function = 'WEIBULL';
6104
                        $args = 4;
6105
                        break;
6106
                    case 303:
6107
                        $function = 'SUMXMY2';
6108
                        $args = 2;
6109
                        break;
6110
                    case 304:
6111
                        $function = 'SUMX2MY2';
6112
                        $args = 2;
6113
                        break;
6114
                    case 305:
6115
                        $function = 'SUMX2PY2';
6116
                        $args = 2;
6117
                        break;
6118
                    case 306:
6119
                        $function = 'CHITEST';
6120
                        $args = 2;
6121
                        break;
6122
                    case 307:
6123
                        $function = 'CORREL';
6124
                        $args = 2;
6125
                        break;
6126
                    case 308:
6127
                        $function = 'COVAR';
6128
                        $args = 2;
6129
                        break;
6130
                    case 309:
6131
                        $function = 'FORECAST';
6132
                        $args = 3;
6133
                        break;
6134
                    case 310:
6135
                        $function = 'FTEST';
6136
                        $args = 2;
6137
                        break;
6138
                    case 311:
6139
                        $function = 'INTERCEPT';
6140
                        $args = 2;
6141
                        break;
6142
                    case 312:
6143
                        $function = 'PEARSON';
6144
                        $args = 2;
6145
                        break;
6146
                    case 313:
6147
                        $function = 'RSQ';
6148
                        $args = 2;
6149
                        break;
6150
                    case 314:
6151
                        $function = 'STEYX';
6152
                        $args = 2;
6153
                        break;
6154
                    case 315:
6155
                        $function = 'SLOPE';
6156
                        $args = 2;
6157
                        break;
6158
                    case 316:
6159
                        $function = 'TTEST';
6160
                        $args = 4;
6161
                        break;
6162
                    case 325:
6163
                        $function = 'LARGE';
6164
                        $args = 2;
6165
                        break;
6166
                    case 326:
6167
                        $function = 'SMALL';
6168
                        $args = 2;
6169
                        break;
6170
                    case 327:
6171
                        $function = 'QUARTILE';
6172
                        $args = 2;
6173
                        break;
6174
                    case 328:
6175
                        $function = 'PERCENTILE';
6176
                        $args = 2;
6177
                        break;
6178
                    case 331:
6179
                        $function = 'TRIMMEAN';
6180
                        $args = 2;
6181
                        break;
6182
                    case 332:
6183
                        $function = 'TINV';
6184
                        $args = 2;
6185
                        break;
6186
                    case 337:
6187
                        $function = 'POWER';
6188
                        $args = 2;
6189
                        break;
6190
                    case 342:
6191
                        $function = 'RADIANS';
6192
                        $args = 1;
6193
                        break;
6194
                    case 343:
6195
                        $function = 'DEGREES';
6196
                        $args = 1;
6197
                        break;
6198
                    case 346:
6199
                        $function = 'COUNTIF';
6200
                        $args = 2;
6201
                        break;
6202
                    case 347:
6203
                        $function = 'COUNTBLANK';
6204
                        $args = 1;
6205
                        break;
6206
                    case 350:
6207
                        $function = 'ISPMT';
6208
                        $args = 4;
6209
                        break;
6210
                    case 351:
6211
                        $function = 'DATEDIF';
6212
                        $args = 3;
6213
                        break;
6214
                    case 352:
6215
                        $function = 'DATESTRING';
6216
                        $args = 1;
6217
                        break;
6218
                    case 353:
6219
                        $function = 'NUMBERSTRING';
6220
                        $args = 2;
6221
                        break;
6222
                    case 360:
6223
                        $function = 'PHONETIC';
6224
                        $args = 1;
6225
                        break;
6226
                    case 368:
6227
                        $function = 'BAHTTEXT';
6228
                        $args = 1;
6229
                        break;
6230
                    default:
6231
                        throw new Exception('Unrecognized function in formula');
6232
                        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...
6233
                }
6234
                $data = ['function' => $function, 'args' => $args];
6235
                break;
6236 2
            case 0x22:    //    function with variable number of arguments
6237 2
            case 0x42:
6238 2
            case 0x62:
6239 1
                $name = 'tFuncV';
6240 1
                $size = 4;
6241
                // offset: 1; size: 1; number of arguments
6242 1
                $args = ord($formulaData[1]);
6243
                // offset: 2: size: 2; index to built-in sheet function
6244 1
                $index = self::getInt2d($formulaData, 2);
6245
                switch ($index) {
6246 1
                    case 0:
6247
                        $function = 'COUNT';
6248
                        break;
6249 1
                    case 1:
6250 1
                        $function = 'IF';
6251 1
                        break;
6252 1
                    case 4:
6253 1
                        $function = 'SUM';
6254 1
                        break;
6255
                    case 5:
6256
                        $function = 'AVERAGE';
6257
                        break;
6258
                    case 6:
6259
                        $function = 'MIN';
6260
                        break;
6261
                    case 7:
6262
                        $function = 'MAX';
6263
                        break;
6264
                    case 8:
6265
                        $function = 'ROW';
6266
                        break;
6267
                    case 9:
6268
                        $function = 'COLUMN';
6269
                        break;
6270
                    case 11:
6271
                        $function = 'NPV';
6272
                        break;
6273
                    case 12:
6274
                        $function = 'STDEV';
6275
                        break;
6276
                    case 13:
6277
                        $function = 'DOLLAR';
6278
                        break;
6279
                    case 14:
6280
                        $function = 'FIXED';
6281
                        break;
6282
                    case 28:
6283
                        $function = 'LOOKUP';
6284
                        break;
6285
                    case 29:
6286
                        $function = 'INDEX';
6287
                        break;
6288
                    case 36:
6289
                        $function = 'AND';
6290
                        break;
6291
                    case 37:
6292
                        $function = 'OR';
6293
                        break;
6294
                    case 46:
6295
                        $function = 'VAR';
6296
                        break;
6297
                    case 49:
6298
                        $function = 'LINEST';
6299
                        break;
6300
                    case 50:
6301
                        $function = 'TREND';
6302
                        break;
6303
                    case 51:
6304
                        $function = 'LOGEST';
6305
                        break;
6306
                    case 52:
6307
                        $function = 'GROWTH';
6308
                        break;
6309
                    case 56:
6310
                        $function = 'PV';
6311
                        break;
6312
                    case 57:
6313
                        $function = 'FV';
6314
                        break;
6315
                    case 58:
6316
                        $function = 'NPER';
6317
                        break;
6318
                    case 59:
6319
                        $function = 'PMT';
6320
                        break;
6321
                    case 60:
6322
                        $function = 'RATE';
6323
                        break;
6324
                    case 62:
6325
                        $function = 'IRR';
6326
                        break;
6327
                    case 64:
6328
                        $function = 'MATCH';
6329
                        break;
6330
                    case 70:
6331
                        $function = 'WEEKDAY';
6332
                        break;
6333
                    case 78:
6334
                        $function = 'OFFSET';
6335
                        break;
6336
                    case 82:
6337
                        $function = 'SEARCH';
6338
                        break;
6339
                    case 100:
6340
                        $function = 'CHOOSE';
6341
                        break;
6342
                    case 101:
6343
                        $function = 'HLOOKUP';
6344
                        break;
6345
                    case 102:
6346
                        $function = 'VLOOKUP';
6347
                        break;
6348
                    case 109:
6349
                        $function = 'LOG';
6350
                        break;
6351
                    case 115:
6352
                        $function = 'LEFT';
6353
                        break;
6354
                    case 116:
6355
                        $function = 'RIGHT';
6356
                        break;
6357
                    case 120:
6358
                        $function = 'SUBSTITUTE';
6359
                        break;
6360
                    case 124:
6361
                        $function = 'FIND';
6362
                        break;
6363
                    case 125:
6364
                        $function = 'CELL';
6365
                        break;
6366
                    case 144:
6367
                        $function = 'DDB';
6368
                        break;
6369
                    case 148:
6370
                        $function = 'INDIRECT';
6371
                        break;
6372
                    case 167:
6373
                        $function = 'IPMT';
6374
                        break;
6375
                    case 168:
6376
                        $function = 'PPMT';
6377
                        break;
6378
                    case 169:
6379
                        $function = 'COUNTA';
6380
                        break;
6381
                    case 183:
6382
                        $function = 'PRODUCT';
6383
                        break;
6384
                    case 193:
6385
                        $function = 'STDEVP';
6386
                        break;
6387
                    case 194:
6388
                        $function = 'VARP';
6389
                        break;
6390
                    case 197:
6391
                        $function = 'TRUNC';
6392
                        break;
6393
                    case 204:
6394
                        $function = 'USDOLLAR';
6395
                        break;
6396
                    case 205:
6397
                        $function = 'FINDB';
6398
                        break;
6399
                    case 206:
6400
                        $function = 'SEARCHB';
6401
                        break;
6402
                    case 208:
6403
                        $function = 'LEFTB';
6404
                        break;
6405
                    case 209:
6406
                        $function = 'RIGHTB';
6407
                        break;
6408
                    case 216:
6409
                        $function = 'RANK';
6410
                        break;
6411
                    case 219:
6412
                        $function = 'ADDRESS';
6413
                        break;
6414
                    case 220:
6415
                        $function = 'DAYS360';
6416
                        break;
6417
                    case 222:
6418
                        $function = 'VDB';
6419
                        break;
6420
                    case 227:
6421
                        $function = 'MEDIAN';
6422
                        break;
6423
                    case 228:
6424
                        $function = 'SUMPRODUCT';
6425
                        break;
6426
                    case 247:
6427
                        $function = 'DB';
6428
                        break;
6429
                    case 255:
6430
                        $function = '';
6431
                        break;
6432
                    case 269:
6433
                        $function = 'AVEDEV';
6434
                        break;
6435
                    case 270:
6436
                        $function = 'BETADIST';
6437
                        break;
6438
                    case 272:
6439
                        $function = 'BETAINV';
6440
                        break;
6441
                    case 317:
6442
                        $function = 'PROB';
6443
                        break;
6444
                    case 318:
6445
                        $function = 'DEVSQ';
6446
                        break;
6447
                    case 319:
6448
                        $function = 'GEOMEAN';
6449
                        break;
6450
                    case 320:
6451
                        $function = 'HARMEAN';
6452
                        break;
6453
                    case 321:
6454
                        $function = 'SUMSQ';
6455
                        break;
6456
                    case 322:
6457
                        $function = 'KURT';
6458
                        break;
6459
                    case 323:
6460
                        $function = 'SKEW';
6461
                        break;
6462
                    case 324:
6463
                        $function = 'ZTEST';
6464
                        break;
6465
                    case 329:
6466
                        $function = 'PERCENTRANK';
6467
                        break;
6468
                    case 330:
6469
                        $function = 'MODE';
6470
                        break;
6471
                    case 336:
6472
                        $function = 'CONCATENATE';
6473
                        break;
6474
                    case 344:
6475
                        $function = 'SUBTOTAL';
6476
                        break;
6477
                    case 345:
6478
                        $function = 'SUMIF';
6479
                        break;
6480
                    case 354:
6481
                        $function = 'ROMAN';
6482
                        break;
6483
                    case 358:
6484
                        $function = 'GETPIVOTDATA';
6485
                        break;
6486
                    case 359:
6487
                        $function = 'HYPERLINK';
6488
                        break;
6489
                    case 361:
6490
                        $function = 'AVERAGEA';
6491
                        break;
6492
                    case 362:
6493
                        $function = 'MAXA';
6494
                        break;
6495
                    case 363:
6496
                        $function = 'MINA';
6497
                        break;
6498
                    case 364:
6499
                        $function = 'STDEVPA';
6500
                        break;
6501
                    case 365:
6502
                        $function = 'VARPA';
6503
                        break;
6504
                    case 366:
6505
                        $function = 'STDEVA';
6506
                        break;
6507
                    case 367:
6508
                        $function = 'VARA';
6509
                        break;
6510
                    default:
6511
                        throw new Exception('Unrecognized function in formula');
6512
                        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...
6513
                }
6514 1
                $data = ['function' => $function, 'args' => $args];
6515 1
                break;
6516 2
            case 0x23:    //    index to defined name
6517 2
            case 0x43:
6518 2
            case 0x63:
6519
                $name = 'tName';
6520
                $size = 5;
6521
                // offset: 1; size: 2; one-based index to definedname record
6522
                $definedNameIndex = self::getInt2d($formulaData, 1) - 1;
6523
                // offset: 2; size: 2; not used
6524
                $data = $this->definedname[$definedNameIndex]['name'];
6525
                break;
6526 2
            case 0x24:    //    single cell reference e.g. A5
6527 2
            case 0x44:
6528 2
            case 0x64:
6529 2
                $name = 'tRef';
6530 2
                $size = 5;
6531 2
                $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6532 2
                break;
6533 2
            case 0x25:    //    cell range reference to cells in the same sheet (2d)
6534 1
            case 0x45:
6535 1
            case 0x65:
6536 2
                $name = 'tArea';
6537 2
                $size = 9;
6538 2
                $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6539 2
                break;
6540 1
            case 0x26:    //    Constant reference sub-expression
6541 1
            case 0x46:
6542 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...
6543
                $name = 'tMemArea';
6544
                // offset: 1; size: 4; not used
6545
                // offset: 5; size: 2; size of the following subexpression
6546
                $subSize = self::getInt2d($formulaData, 5);
6547
                $size = 7 + $subSize;
6548
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6549
                break;
6550 1
            case 0x27:    //    Deleted constant reference sub-expression
6551 1
            case 0x47:
6552 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...
6553
                $name = 'tMemErr';
6554
                // offset: 1; size: 4; not used
6555
                // offset: 5; size: 2; size of the following subexpression
6556
                $subSize = self::getInt2d($formulaData, 5);
6557
                $size = 7 + $subSize;
6558
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6559
                break;
6560 1
            case 0x29:    //    Variable reference sub-expression
6561 1
            case 0x49:
6562 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...
6563
                $name = 'tMemFunc';
6564
                // offset: 1; size: 2; size of the following sub-expression
6565
                $subSize = self::getInt2d($formulaData, 1);
6566
                $size = 3 + $subSize;
6567
                $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
6568
                break;
6569 1
            case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
6570 1
            case 0x4C:
6571 1
            case 0x6C:
6572
                $name = 'tRefN';
6573
                $size = 5;
6574
                $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
6575
                break;
6576 1
            case 0x2D:    //    Relative 2d range reference
6577 1
            case 0x4D:
6578 1
            case 0x6D:
6579
                $name = 'tAreaN';
6580
                $size = 9;
6581
                $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
6582
                break;
6583 1
            case 0x39:    //    External name
6584 1
            case 0x59:
6585 1
            case 0x79:
6586
                $name = 'tNameX';
6587
                $size = 7;
6588
                // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
6589
                // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
6590
                $index = self::getInt2d($formulaData, 3);
6591
                // assume index is to EXTERNNAME record
6592
                $data = $this->externalNames[$index - 1]['name'];
6593
                // offset: 5; size: 2; not used
6594
                break;
6595 1
            case 0x3A:    //    3d reference to cell
6596 1
            case 0x5A:
6597 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...
6598
                $name = 'tRef3d';
6599
                $size = 7;
6600
6601
                try {
6602
                    // offset: 1; size: 2; index to REF entry
6603
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getInt2d($formulaData, 1));
6604
                    // offset: 3; size: 4; cell address
6605
                    $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
6606
6607
                    $data = "$sheetRange!$cellAddress";
6608
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
6609
                    // deleted sheet reference
6610
                    $data = '#REF!';
6611
                }
6612
                break;
6613 1
            case 0x3B:    //    3d reference to cell range
6614
            case 0x5B:
6615 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...
6616 1
                $name = 'tArea3d';
6617 1
                $size = 11;
6618
6619
                try {
6620
                    // offset: 1; size: 2; index to REF entry
6621 1
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getInt2d($formulaData, 1));
6622
                    // offset: 3; size: 8; cell address
6623 1
                    $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
6624
6625 1
                    $data = "$sheetRange!$cellRangeAddress";
6626
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
6627
                    // deleted sheet reference
6628
                    $data = '#REF!';
6629
                }
6630 1
                break;
6631
            // Unknown cases    // don't know how to deal with
6632
            default:
6633
                throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
6634
                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...
6635
        }
6636
6637
        return [
6638 2
            'id' => $id,
6639 2
            'name' => $name,
6640 2
            'size' => $size,
6641 2
            'data' => $data,
6642
        ];
6643
    }
6644
6645
    /**
6646
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
6647
     * section 3.3.4
6648
     *
6649
     * @param string $cellAddressStructure
6650
     * @return string
6651
     */
6652 2
    private function readBIFF8CellAddress($cellAddressStructure)
6653
    {
6654
        // 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...
6655 2
        $row = self::getInt2d($cellAddressStructure, 0) + 1;
6656
6657
        // offset: 2; size: 2; index to column or column offset + relative flags
6658
        // bit: 7-0; mask 0x00FF; column index
6659 2
        $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(0x00FF & self::getInt2d($cellAddressStructure, 2));
6660
6661
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6662 2
        if (!(0x4000 & self::getInt2d($cellAddressStructure, 2))) {
6663 1
            $column = '$' . $column;
6664
        }
6665
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6666 2
        if (!(0x8000 & self::getInt2d($cellAddressStructure, 2))) {
6667 1
            $row = '$' . $row;
6668
        }
6669
6670 2
        return $column . $row;
6671
    }
6672
6673
    /**
6674
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
6675
     * to indicate offsets from a base cell
6676
     * section 3.3.4
6677
     *
6678
     * @param string $cellAddressStructure
6679
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
6680
     * @return string
6681
     */
6682
    private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
6683
    {
6684
        list($baseCol, $baseRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($baseCell);
6685
        $baseCol = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($baseCol) - 1;
6686
6687
        // 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...
6688
        $rowIndex = self::getInt2d($cellAddressStructure, 0);
6689
        $row = self::getInt2d($cellAddressStructure, 0) + 1;
6690
6691
        // offset: 2; size: 2; index to column or column offset + relative flags
6692
        // bit: 7-0; mask 0x00FF; column index
6693
        $colIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
6694
6695
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6696 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...
6697
            $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($colIndex);
6698
            $column = '$' . $column;
6699
        } else {
6700
            $colIndex = ($colIndex <= 127) ? $colIndex : $colIndex - 256;
6701
            $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $colIndex);
6702
        }
6703
6704
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6705
        if (!(0x8000 & self::getInt2d($cellAddressStructure, 2))) {
6706
            $row = '$' . $row;
6707
        } else {
6708
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
6709
            $row = $baseRow + $rowIndex;
6710
        }
6711
6712
        return $column . $row;
6713
    }
6714
6715
    /**
6716
     * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
6717
     * always fixed range
6718
     * section 2.5.14
6719
     *
6720
     * @param string $subData
6721
     * @throws Exception
6722
     * @return string
6723
     */
6724 4
    private function readBIFF5CellRangeAddressFixed($subData)
6725
    {
6726
        // offset: 0; size: 2; index to first row
6727 4
        $fr = self::getInt2d($subData, 0) + 1;
6728
6729
        // offset: 2; size: 2; index to last row
6730 4
        $lr = self::getInt2d($subData, 2) + 1;
6731
6732
        // offset: 4; size: 1; index to first column
6733 4
        $fc = ord($subData{4});
6734
6735
        // offset: 5; size: 1; index to last column
6736 4
        $lc = ord($subData{5});
6737
6738
        // check values
6739 4
        if ($fr > $lr || $fc > $lc) {
6740
            throw new Exception('Not a cell range address');
6741
        }
6742
6743
        // column index to letter
6744 4
        $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fc);
6745 4
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($lc);
6746
6747 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...
6748 4
            return "$fc$fr";
6749
        }
6750
6751 1
        return "$fc$fr:$lc$lr";
6752
    }
6753
6754
    /**
6755
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
6756
     * always fixed range
6757
     * section 2.5.14
6758
     *
6759
     * @param string $subData
6760
     * @throws Exception
6761
     * @return string
6762
     */
6763 2
    private function readBIFF8CellRangeAddressFixed($subData)
6764
    {
6765
        // offset: 0; size: 2; index to first row
6766 2
        $fr = self::getInt2d($subData, 0) + 1;
6767
6768
        // offset: 2; size: 2; index to last row
6769 2
        $lr = self::getInt2d($subData, 2) + 1;
6770
6771
        // offset: 4; size: 2; index to first column
6772 2
        $fc = self::getInt2d($subData, 4);
6773
6774
        // offset: 6; size: 2; index to last column
6775 2
        $lc = self::getInt2d($subData, 6);
6776
6777
        // check values
6778 2
        if ($fr > $lr || $fc > $lc) {
6779
            throw new Exception('Not a cell range address');
6780
        }
6781
6782
        // column index to letter
6783 2
        $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fc);
6784 2
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($lc);
6785
6786 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...
6787 2
            return "$fc$fr";
6788
        }
6789
6790 2
        return "$fc$fr:$lc$lr";
6791
    }
6792
6793
    /**
6794
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
6795
     * there are flags indicating whether column/row index is relative
6796
     * section 3.3.4
6797
     *
6798
     * @param string $subData
6799
     * @return string
6800
     */
6801 2
    private function readBIFF8CellRangeAddress($subData)
6802
    {
6803
        // todo: if cell range is just a single cell, should this funciton
6804
        // not just return e.g. 'A1' and not 'A1:A1' ?
6805
6806
        // 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...
6807 2
            $fr = self::getInt2d($subData, 0) + 1;
6808
6809
        // 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...
6810 2
            $lr = self::getInt2d($subData, 2) + 1;
6811
6812
        // offset: 4; size: 2; index to first column or column offset + relative flags
6813
6814
        // bit: 7-0; mask 0x00FF; column index
6815 2
        $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(0x00FF & self::getInt2d($subData, 4));
6816
6817
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6818 2
        if (!(0x4000 & self::getInt2d($subData, 4))) {
6819 1
            $fc = '$' . $fc;
6820
        }
6821
6822
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6823 2
        if (!(0x8000 & self::getInt2d($subData, 4))) {
6824 1
            $fr = '$' . $fr;
6825
        }
6826
6827
        // offset: 6; size: 2; index to last column or column offset + relative flags
6828
6829
        // bit: 7-0; mask 0x00FF; column index
6830 2
        $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(0x00FF & self::getInt2d($subData, 6));
6831
6832
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6833 2
        if (!(0x4000 & self::getInt2d($subData, 6))) {
6834 1
            $lc = '$' . $lc;
6835
        }
6836
6837
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6838 2
        if (!(0x8000 & self::getInt2d($subData, 6))) {
6839 1
            $lr = '$' . $lr;
6840
        }
6841
6842 2
        return "$fc$fr:$lc$lr";
6843
    }
6844
6845
    /**
6846
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
6847
     * to indicate offsets from a base cell
6848
     * section 3.3.4
6849
     *
6850
     * @param string $subData
6851
     * @param string $baseCell Base cell
6852
     * @return string Cell range address
6853
     */
6854
    private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
6855
    {
6856
        list($baseCol, $baseRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($baseCell);
6857
        $baseCol = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($baseCol) - 1;
6858
6859
        // TODO: if cell range is just a single cell, should this funciton
6860
        // not just return e.g. 'A1' and not 'A1:A1' ?
6861
6862
        // offset: 0; size: 2; first row
6863
        $frIndex = self::getInt2d($subData, 0); // adjust below
6864
6865
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
6866
        $lrIndex = self::getInt2d($subData, 2); // adjust below
6867
6868
        // offset: 4; size: 2; first column with relative/absolute flags
6869
6870
        // bit: 7-0; mask 0x00FF; column index
6871
        $fcIndex = 0x00FF & self::getInt2d($subData, 4);
6872
6873
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6874 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...
6875
            // absolute column index
6876
            $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($fcIndex);
6877
            $fc = '$' . $fc;
6878
        } else {
6879
            // column offset
6880
            $fcIndex = ($fcIndex <= 127) ? $fcIndex : $fcIndex - 256;
6881
            $fc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $fcIndex);
6882
        }
6883
6884
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6885 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...
6886
            // absolute row index
6887
            $fr = $frIndex + 1;
6888
            $fr = '$' . $fr;
6889
        } else {
6890
            // row offset
6891
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
6892
            $fr = $baseRow + $frIndex;
6893
        }
6894
6895
        // offset: 6; size: 2; last column with relative/absolute flags
6896
6897
        // bit: 7-0; mask 0x00FF; column index
6898
        $lcIndex = 0x00FF & self::getInt2d($subData, 6);
6899
        $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
6900
        $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...
6901
6902
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6903 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...
6904
            // absolute column index
6905
            $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($lcIndex);
6906
            $lc = '$' . $lc;
6907
        } else {
6908
            // column offset
6909
            $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
6910
            $lc = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($baseCol + $lcIndex);
6911
        }
6912
6913
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6914 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...
6915
            // absolute row index
6916
            $lr = $lrIndex + 1;
6917
            $lr = '$' . $lr;
6918
        } else {
6919
            // row offset
6920
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
6921
            $lr = $baseRow + $lrIndex;
6922
        }
6923
6924
        return "$fc$fr:$lc$lr";
6925
    }
6926
6927
    /**
6928
     * Read BIFF8 cell range address list
6929
     * section 2.5.15
6930
     *
6931
     * @param string $subData
6932
     * @return array
6933
     */
6934 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...
6935
    {
6936 2
        $cellRangeAddresses = [];
6937
6938
        // offset: 0; size: 2; number of the following cell range addresses
6939 2
        $nm = self::getInt2d($subData, 0);
6940
6941 2
        $offset = 2;
6942
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
6943 2
        for ($i = 0; $i < $nm; ++$i) {
6944 2
            $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
6945 2
            $offset += 8;
6946
        }
6947
6948
        return [
6949 2
            'size' => 2 + 8 * $nm,
6950 2
            'cellRangeAddresses' => $cellRangeAddresses,
6951
        ];
6952
    }
6953
6954
    /**
6955
     * Read BIFF5 cell range address list
6956
     * section 2.5.15
6957
     *
6958
     * @param string $subData
6959
     * @return array
6960
     */
6961 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...
6962
    {
6963 4
        $cellRangeAddresses = [];
6964
6965
        // offset: 0; size: 2; number of the following cell range addresses
6966 4
        $nm = self::getInt2d($subData, 0);
6967
6968 4
        $offset = 2;
6969
        // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
6970 4
        for ($i = 0; $i < $nm; ++$i) {
6971 4
            $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
6972 4
            $offset += 6;
6973
        }
6974
6975
        return [
6976 4
            'size' => 2 + 6 * $nm,
6977 4
            'cellRangeAddresses' => $cellRangeAddresses,
6978
        ];
6979
    }
6980
6981
    /**
6982
     * Get a sheet range like Sheet1:Sheet3 from REF index
6983
     * Note: If there is only one sheet in the range, one gets e.g Sheet1
6984
     * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
6985
     * in which case an Exception is thrown
6986
     *
6987
     * @param int $index
6988
     * @throws Exception
6989
     * @return string|false
6990
     */
6991 1
    private function readSheetRangeByRefIndex($index)
6992
    {
6993 1
        if (isset($this->ref[$index])) {
6994 1
            $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
6995
6996
            switch ($type) {
6997 1
                case 'internal':
6998
                    // check if we have a deleted 3d reference
6999 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...
7000
                        throw new Exception('Deleted sheet reference');
7001
                    }
7002
7003
                    // we have normal sheet range (collapsed or uncollapsed)
7004 1
                    $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7005 1
                    $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7006
7007 1
                    if ($firstSheetName == $lastSheetName) {
7008
                        // collapsed sheet range
7009 1
                        $sheetRange = $firstSheetName;
7010
                    } else {
7011
                        $sheetRange = "$firstSheetName:$lastSheetName";
7012
                    }
7013
7014
                    // escape the single-quotes
7015 1
                    $sheetRange = str_replace("'", "''", $sheetRange);
7016
7017
                    // if there are special characters, we need to enclose the range in single-quotes
7018
                    // todo: check if we have identified the whole set of special characters
7019
                    // it seems that the following characters are not accepted for sheet names
7020
                    // and we may assume that they are not present: []*/:\?
7021 1
                    if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) {
7022
                        $sheetRange = "'$sheetRange'";
7023
                    }
7024
7025 1
                    return $sheetRange;
7026
                    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...
7027
                default:
7028
                    // TODO: external sheet support
7029
                    throw new Exception('Xls reader only supports internal sheets in fomulas');
7030
                    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...
7031
            }
7032
        }
7033
7034
        return false;
7035
    }
7036
7037
    /**
7038
     * read BIFF8 constant value array from array data
7039
     * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7040
     * section 2.5.8
7041
     *
7042
     * @param string $arrayData
7043
     * @return array
7044
     */
7045
    private static function readBIFF8ConstantArray($arrayData)
7046
    {
7047
        // offset: 0; size: 1; number of columns decreased by 1
7048
        $nc = ord($arrayData[0]);
7049
7050
        // offset: 1; size: 2; number of rows decreased by 1
7051
        $nr = self::getInt2d($arrayData, 1);
7052
        $size = 3; // initialize
7053
        $arrayData = substr($arrayData, 3);
7054
7055
        // 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...
7056
        $matrixChunks = [];
7057
        for ($r = 1; $r <= $nr + 1; ++$r) {
7058
            $items = [];
7059
            for ($c = 1; $c <= $nc + 1; ++$c) {
7060
                $constant = self::readBIFF8Constant($arrayData);
7061
                $items[] = $constant['value'];
7062
                $arrayData = substr($arrayData, $constant['size']);
7063
                $size += $constant['size'];
7064
            }
7065
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7066
        }
7067
        $matrix = '{' . implode(';', $matrixChunks) . '}';
7068
7069
        return [
7070
            'value' => $matrix,
7071
            'size' => $size,
7072
        ];
7073
    }
7074
7075
    /**
7076
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7077
     * section 2.5.7
7078
     * returns e.g. array('value' => '5', 'size' => 9)
7079
     *
7080
     * @param string $valueData
7081
     * @return array
7082
     */
7083
    private static function readBIFF8Constant($valueData)
7084
    {
7085
        // offset: 0; size: 1; identifier for type of constant
7086
        $identifier = ord($valueData[0]);
7087
7088
        switch ($identifier) {
7089
            case 0x00: // empty constant (what is this?)
7090
                $value = '';
7091
                $size = 9;
7092
                break;
7093
            case 0x01: // number
7094
                // offset: 1; size: 8; IEEE 754 floating-point value
7095
                $value = self::extractNumber(substr($valueData, 1, 8));
7096
                $size = 9;
7097
                break;
7098
            case 0x02: // string value
7099
                // offset: 1; size: var; Unicode string, 16-bit string length
7100
                $string = self::readUnicodeStringLong(substr($valueData, 1));
7101
                $value = '"' . $string['value'] . '"';
7102
                $size = 1 + $string['size'];
7103
                break;
7104
            case 0x04: // boolean
7105
                // 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...
7106
                if (ord($valueData[1])) {
7107
                    $value = 'TRUE';
7108
                } else {
7109
                    $value = 'FALSE';
7110
                }
7111
                $size = 9;
7112
                break;
7113
            case 0x10: // error code
7114
                // offset: 1; size: 1; error code
7115
                $value = Xls\ErrorCode::lookup(ord($valueData[1]));
7116
                $size = 9;
7117
                break;
7118
        }
7119
7120
        return [
7121
            '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...
7122
            '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...
7123
        ];
7124
    }
7125
7126
    /**
7127
     * Extract RGB color
7128
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4
7129
     *
7130
     * @param string $rgb Encoded RGB value (4 bytes)
7131
     * @return array
7132
     */
7133 2
    private static function readRGB($rgb)
7134
    {
7135
        // offset: 0; size 1; Red component
7136 2
        $r = ord($rgb{0});
7137
7138
        // offset: 1; size: 1; Green component
7139 2
        $g = ord($rgb{1});
7140
7141
        // offset: 2; size: 1; Blue component
7142 2
        $b = ord($rgb{2});
7143
7144
        // HEX notation, e.g. 'FF00FC'
7145 2
        $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7146
7147 2
        return ['rgb' => $rgb];
7148
    }
7149
7150
    /**
7151
     * Read byte string (8-bit string length)
7152
     * OpenOffice documentation: 2.5.2
7153
     *
7154
     * @param string $subData
7155
     * @return array
7156
     */
7157 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...
7158
    {
7159
        // offset: 0; size: 1; length of the string (character count)
7160
        $ln = ord($subData[0]);
7161
7162
        // 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...
7163
        $value = $this->decodeCodepage(substr($subData, 1, $ln));
7164
7165
        return [
7166
            'value' => $value,
7167
            'size' => 1 + $ln, // size in bytes of data structure
7168
        ];
7169
    }
7170
7171
    /**
7172
     * Read byte string (16-bit string length)
7173
     * OpenOffice documentation: 2.5.2
7174
     *
7175
     * @param string $subData
7176
     * @return array
7177
     */
7178 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...
7179
    {
7180
        // offset: 0; size: 2; length of the string (character count)
7181
        $ln = self::getInt2d($subData, 0);
7182
7183
        // 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...
7184
        $value = $this->decodeCodepage(substr($subData, 2));
7185
7186
        //return $string;
7187
        return [
7188
            'value' => $value,
7189
            'size' => 2 + $ln, // size in bytes of data structure
7190
        ];
7191
    }
7192
7193
    /**
7194
     * Extracts an Excel Unicode short string (8-bit string length)
7195
     * OpenOffice documentation: 2.5.3
7196
     * function will automatically find out where the Unicode string ends.
7197
     *
7198
     * @param string $subData
7199
     * @return array
7200
     */
7201 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...
7202
    {
7203 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...
7204
7205
        // offset: 0: size: 1; length of the string (character count)
7206 4
        $characterCount = ord($subData[0]);
7207
7208 4
        $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7209
7210
        // add 1 for the string length
7211 4
        $string['size'] += 1;
7212
7213 4
        return $string;
7214
    }
7215
7216
    /**
7217
     * Extracts an Excel Unicode long string (16-bit string length)
7218
     * OpenOffice documentation: 2.5.3
7219
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7220
     *
7221
     * @param string $subData
7222
     * @return array
7223
     */
7224 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...
7225
    {
7226 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...
7227
7228
        // offset: 0: size: 2; length of the string (character count)
7229 4
        $characterCount = self::getInt2d($subData, 0);
7230
7231 4
        $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7232
7233
        // add 2 for the string length
7234 4
        $string['size'] += 2;
7235
7236 4
        return $string;
7237
    }
7238
7239
    /**
7240
     * Read Unicode string with no string length field, but with known character count
7241
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7242
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3
7243
     *
7244
     * @param string $subData
7245
     * @param int $characterCount
7246
     * @return array
7247
     */
7248 4
    private static function readUnicodeString($subData, $characterCount)
7249
    {
7250 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...
7251
7252
        // offset: 0: size: 1; option flags
7253
        // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7254 4
        $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7255
7256
        // bit: 2; mask: 0x04; Asian phonetic settings
7257 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...
7258
7259
        // bit: 3; mask: 0x08; Rich-Text settings
7260 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...
7261
7262
        // 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...
7263
        // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7264
        // needs to be fixed
7265 4
        $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7266
7267
        return [
7268 4
            'value' => $value,
7269 4
            'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7270
        ];
7271
    }
7272
7273
    /**
7274
     * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7275
     * Example:  hello"world  -->  "hello""world"
7276
     *
7277
     * @param string $value UTF-8 encoded string
7278
     * @return string
7279
     */
7280 1
    private static function UTF8toExcelDoubleQuoted($value)
7281
    {
7282 1
        return '"' . str_replace('"', '""', $value) . '"';
7283
    }
7284
7285
    /**
7286
     * Reads first 8 bytes of a string and return IEEE 754 float
7287
     *
7288
     * @param string $data Binary string that is at least 8 bytes long
7289
     * @return float
7290
     */
7291 4
    private static function extractNumber($data)
7292
    {
7293 4
        $rknumhigh = self::getInt4d($data, 4);
7294 4
        $rknumlow = self::getInt4d($data, 0);
7295 4
        $sign = ($rknumhigh & 0x80000000) >> 31;
7296 4
        $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7297 4
        $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7298 4
        $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7299 4
        $mantissalow2 = ($rknumlow & 0x7fffffff);
7300 4
        $value = $mantissa / pow(2, (20 - $exp));
7301
7302 4
        if ($mantissalow1 != 0) {
7303 2
            $value += 1 / pow(2, (21 - $exp));
7304
        }
7305
7306 4
        $value += $mantissalow2 / pow(2, (52 - $exp));
7307 4
        if ($sign) {
7308
            $value *= -1;
7309
        }
7310
7311 4
        return $value;
7312
    }
7313
7314
    /**
7315
     * @param int $rknum
7316
     */
7317 1
    private static function getIEEE754($rknum)
7318
    {
7319 1
        if (($rknum & 0x02) != 0) {
7320
            $value = $rknum >> 2;
7321
        } else {
7322
            // changes by mmp, info on IEEE754 encoding from
7323
            // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7324
            // The RK format calls for using only the most significant 30 bits
7325
            // of the 64 bit floating point value. The other 34 bits are assumed
7326
            // to be 0 so we use the upper 30 bits of $rknum as follows...
7327 1
            $sign = ($rknum & 0x80000000) >> 31;
7328 1
            $exp = ($rknum & 0x7ff00000) >> 20;
7329 1
            $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7330 1
            $value = $mantissa / pow(2, (20 - ($exp - 1023)));
7331 1
            if ($sign) {
7332
                $value = -1 * $value;
7333
            }
7334
            //end of changes by mmp
7335
        }
7336 1
        if (($rknum & 0x01) != 0) {
7337
            $value /= 100;
7338
        }
7339
7340 1
        return $value;
7341
    }
7342
7343
    /**
7344
     * Get UTF-8 string from (compressed or uncompressed) UTF-16 string
7345
     *
7346
     * @param string $string
7347
     * @param bool $compressed
7348
     * @return string
7349
     */
7350 4
    private static function encodeUTF16($string, $compressed = false)
7351
    {
7352 4
        if ($compressed) {
7353 3
            $string = self::uncompressByteString($string);
7354
        }
7355
7356 4
        return \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
7357
    }
7358
7359
    /**
7360
     * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7361
     *
7362
     * @param string $string
7363
     * @return string
7364
     */
7365 3
    private static function uncompressByteString($string)
7366
    {
7367 3
        $uncompressedString = '';
7368 3
        $strLen = strlen($string);
7369 3
        for ($i = 0; $i < $strLen; ++$i) {
7370 3
            $uncompressedString .= $string[$i] . "\0";
7371
        }
7372
7373 3
        return $uncompressedString;
7374
    }
7375
7376
    /**
7377
     * Convert string to UTF-8. Only used for BIFF5.
7378
     *
7379
     * @param string $string
7380
     * @return string
7381
     */
7382
    private function decodeCodepage($string)
7383
    {
7384
        return \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
7385
    }
7386
7387
    /**
7388
     * Read 16-bit unsigned integer
7389
     *
7390
     * @param string $data
7391
     * @param int $pos
7392
     * @return int
7393
     */
7394 4
    public static function getInt2d($data, $pos)
7395
    {
7396 4
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
7397
    }
7398
7399
    /**
7400
     * Read 32-bit signed integer
7401
     *
7402
     * @param string $data
7403
     * @param int $pos
7404
     * @return int
7405
     */
7406 4
    public static function getInt4d($data, $pos)
7407
    {
7408
        // FIX: represent numbers correctly on 64-bit system
7409
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7410
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7411 4
        $_or_24 = ord($data[$pos + 3]);
7412 4
        if ($_or_24 >= 128) {
7413
            // negative number
7414 2
            $_ord_24 = -abs((256 - $_or_24) << 24);
7415
        } else {
7416 4
            $_ord_24 = ($_or_24 & 127) << 24;
7417
        }
7418
7419 4
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
7420
    }
7421
7422 1
    private function parseRichText($is = '')
7423
    {
7424 1
        $value = new \PhpOffice\PhpSpreadsheet\RichText();
7425 1
        $value->createText($is);
7426
7427 1
        return $value;
7428
    }
7429
}
7430