Completed
Push — develop ( 102c19...d791a0 )
by Adrien
27:51 queued 16:51
created

Excel2003XML::identifyFixedStyleValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 11
Ratio 84.62 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 2
dl 11
loc 13
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 9.4285
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Spreadsheet;
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
 * @version    ##VERSION##, ##DATE##
28
 */
29
class Excel2003XML extends BaseReader implements IReader
30
{
31
    /**
32
     * Formats
33
     *
34
     * @var array
35
     */
36
    protected $styles = [];
37
38
    /**
39
     * Character set used in the file
40
     *
41
     * @var string
42
     */
43
    protected $charSet = 'UTF-8';
44
45
    /**
46
     * Create a new Excel2003XML Reader instance
47
     */
48 1
    public function __construct()
49
    {
50 1
        $this->readFilter = new DefaultReadFilter();
51 1
    }
52
53
    /**
54
     * Can the current IReader read the file?
55
     *
56
     * @param     string         $pFilename
57
     * @throws Exception
58
     * @return     bool
59
     */
60 1
    public function canRead($pFilename)
61
    {
62
63
        //    Office                    xmlns:o="urn:schemas-microsoft-com:office:office"
64
        //    Excel                    xmlns:x="urn:schemas-microsoft-com:office:excel"
65
        //    XML Spreadsheet            xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
66
        //    Spreadsheet component    xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
67
        //    XML schema                 xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
68
        //    XML data type            xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
69
        //    MS-persist recordset    xmlns:rs="urn:schemas-microsoft-com:rowset"
70
        //    Rowset                    xmlns:z="#RowsetSchema"
71
        //
72
73
        $signature = [
74 1
                '<?xml version="1.0"',
75
                '<?mso-application progid="Excel.Sheet"?>',
76
            ];
77
78
        // Open file
79 1
        $this->openFile($pFilename);
80 1
        $fileHandle = $this->fileHandle;
81
82
        // Read sample data (first 2 KB will do)
83 1
        $data = fread($fileHandle, 2048);
84 1
        fclose($fileHandle);
85 1
        $data = strtr($data, "'", '"'); // fix headers with single quote
86
87 1
        $valid = true;
88 1
        foreach ($signature as $match) {
89
            // every part of the signature must be present
90 1
            if (strpos($data, $match) === false) {
91
                $valid = false;
92 1
                break;
93
            }
94
        }
95
96
        //    Retrieve charset encoding
97 1
        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
98 1
            $this->charSet = strtoupper($matches[1]);
99
        }
100
101 1
        return $valid;
102
    }
103
104
    /**
105
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object
106
     *
107
     * @param     string         $pFilename
108
     * @throws     Exception
109
     */
110
    public function listWorksheetNames($pFilename)
111
    {
112
        // Check if file exists
113
        if (!file_exists($pFilename)) {
114
            throw new Exception('Could not open ' . $pFilename . ' for reading! File does not exist.');
115
        }
116
        if (!$this->canRead($pFilename)) {
117
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
118
        }
119
120
        $worksheetNames = [];
121
122
        $xml = simplexml_load_string(
123
            $this->securityScan(file_get_contents($pFilename)),
124
            'SimpleXMLElement',
125
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
126
        );
127
        $namespaces = $xml->getNamespaces(true);
128
129
        $xml_ss = $xml->children($namespaces['ss']);
130
        foreach ($xml_ss->Worksheet as $worksheet) {
131
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
132
            $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
133
        }
134
135
        return $worksheetNames;
136
    }
137
138
    /**
139
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
140
     *
141
     * @param   string     $pFilename
142
     * @throws   Exception
143
     */
144
    public function listWorksheetInfo($pFilename)
145
    {
146
        // Check if file exists
147
        if (!file_exists($pFilename)) {
148
            throw new Exception('Could not open ' . $pFilename . ' for reading! File does not exist.');
149
        }
150
151
        $worksheetInfo = [];
152
153
        $xml = simplexml_load_string(
154
            $this->securityScan(file_get_contents($pFilename)),
155
            'SimpleXMLElement',
156
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
157
        );
158
        $namespaces = $xml->getNamespaces(true);
159
160
        $worksheetID = 1;
161
        $xml_ss = $xml->children($namespaces['ss']);
162
        foreach ($xml_ss->Worksheet as $worksheet) {
163
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
164
165
            $tmpInfo = [];
166
            $tmpInfo['worksheetName'] = '';
167
            $tmpInfo['lastColumnLetter'] = 'A';
168
            $tmpInfo['lastColumnIndex'] = 0;
169
            $tmpInfo['totalRows'] = 0;
170
            $tmpInfo['totalColumns'] = 0;
171
172
            if (isset($worksheet_ss['Name'])) {
173
                $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
174
            } else {
175
                $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
176
            }
177
178
            if (isset($worksheet->Table->Row)) {
179
                $rowIndex = 0;
180
181
                foreach ($worksheet->Table->Row as $rowData) {
182
                    $columnIndex = 0;
183
                    $rowHasData = false;
184
185
                    foreach ($rowData->Cell as $cell) {
186
                        if (isset($cell->Data)) {
187
                            $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
188
                            $rowHasData = true;
189
                        }
190
191
                        ++$columnIndex;
192
                    }
193
194
                    ++$rowIndex;
195
196
                    if ($rowHasData) {
197
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
198
                    }
199
                }
200
            }
201
202
            $tmpInfo['lastColumnLetter'] = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
203
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
204
205
            $worksheetInfo[] = $tmpInfo;
206
            ++$worksheetID;
207
        }
208
209
        return $worksheetInfo;
210
    }
211
212
    /**
213
     * Loads Spreadsheet from file
214
     *
215
     * @param     string         $pFilename
216
     * @throws     Exception
217
     * @return     \PhpOffice\PhpSpreadsheet\Spreadsheet
218
     */
219 1
    public function load($pFilename)
220
    {
221
        // Create new Spreadsheet
222 1
        $spreadsheet = new Spreadsheet();
223 1
        $spreadsheet->removeSheetByIndex(0);
224
225
        // Load into this instance
226 1
        return $this->loadIntoExisting($pFilename, $spreadsheet);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->loadIntoEx...ilename, $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...
227
    }
228
229 1 View Code Duplication
    protected static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
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...
230
    {
231 1
        $styleAttributeValue = strtolower($styleAttributeValue);
232 1
        foreach ($styleList as $style) {
233 1
            if ($styleAttributeValue == strtolower($style)) {
234 1
                $styleAttributeValue = $style;
235
236 1
                return true;
237
            }
238
        }
239
240
        return false;
241
    }
242
243
    /**
244
     * pixel units to excel width units(units of 1/256th of a character width)
245
     * @param pxs
246
     * @return
247
     */
248
    protected static function pixel2WidthUnits($pxs)
249
    {
250
        $UNIT_OFFSET_MAP = [0, 36, 73, 109, 146, 182, 219];
251
252
        $widthUnits = 256 * ($pxs / 7);
253
        $widthUnits += $UNIT_OFFSET_MAP[($pxs % 7)];
254
255
        return $widthUnits;
256
    }
257
258
    /**
259
     * excel width units(units of 1/256th of a character width) to pixel units
260
     * @param widthUnits
261
     * @return
262
     */
263
    protected static function widthUnits2Pixel($widthUnits)
264
    {
265
        $pixels = ($widthUnits / 256) * 7;
266
        $offsetWidthUnits = $widthUnits % 256;
267
        $pixels += round($offsetWidthUnits / (256 / 7));
268
269
        return $pixels;
270
    }
271
272
    protected static function hex2str($hex)
273
    {
274
        return chr(hexdec($hex[1]));
275
    }
276
277
    /**
278
     * Loads from file into Spreadsheet instance
279
     *
280
     * @param     string         $pFilename
281
     * @param     \PhpOffice\PhpSpreadsheet\Spreadsheet    $spreadsheet
282
     * @throws    Exception
283
     * @return    \PhpOffice\PhpSpreadsheet\Spreadsheet
284
     */
285 1
    public function loadIntoExisting($pFilename, \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet)
286
    {
287 1
        $fromFormats = ['\-', '\ '];
288 1
        $toFormats = ['-', ' '];
289
290
        $underlineStyles = [
291 1
            \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE,
292 1
            \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE,
293 1
            \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING,
294 1
            \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE,
295 1
            \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING,
296
        ];
297
        $verticalAlignmentStyles = [
298 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_BOTTOM,
299 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP,
300 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
301 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_JUSTIFY,
302
        ];
303
        $horizontalAlignmentStyles = [
304 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_GENERAL,
305 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT,
306 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT,
307 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
308 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER_CONTINUOUS,
309 1
            \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_JUSTIFY,
310
        ];
311
312 1
        $timezoneObj = new \DateTimeZone('Europe/London');
0 ignored issues
show
Unused Code introduced by
$timezoneObj 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...
313 1
        $GMT = new \DateTimeZone('UTC');
0 ignored issues
show
Unused Code introduced by
$GMT 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...
314
315
        // Check if file exists
316 1
        if (!file_exists($pFilename)) {
317
            throw new Exception('Could not open ' . $pFilename . ' for reading! File does not exist.');
318
        }
319
320 1
        if (!$this->canRead($pFilename)) {
321
            throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
322
        }
323
324 1
        $xml = simplexml_load_string(
325 1
            $this->securityScan(file_get_contents($pFilename)),
326 1
            'SimpleXMLElement',
327 1
            \PhpOffice\PhpSpreadsheet\Settings::getLibXmlLoaderOptions()
328
        );
329 1
        $namespaces = $xml->getNamespaces(true);
330
331 1
        $docProps = $spreadsheet->getProperties();
332 1
        if (isset($xml->DocumentProperties[0])) {
333
            foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
334
                switch ($propertyName) {
335
                    case 'Title':
336
                        $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));
337
                        break;
338
                    case 'Subject':
339
                        $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));
340
                        break;
341
                    case 'Author':
342
                        $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));
343
                        break;
344
                    case 'Created':
345
                        $creationDate = strtotime($propertyValue);
346
                        $docProps->setCreated($creationDate);
0 ignored issues
show
Documentation introduced by
$creationDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...Document\datetime>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
347
                        break;
348
                    case 'LastAuthor':
349
                        $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));
350
                        break;
351
                    case 'LastSaved':
352
                        $lastSaveDate = strtotime($propertyValue);
353
                        $docProps->setModified($lastSaveDate);
0 ignored issues
show
Documentation introduced by
$lastSaveDate is of type integer, but the function expects a object<PhpOffice\PhpSpre...Document\datetime>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
354
                        break;
355
                    case 'Company':
356
                        $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));
357
                        break;
358
                    case 'Category':
359
                        $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));
360
                        break;
361
                    case 'Manager':
362
                        $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));
363
                        break;
364
                    case 'Keywords':
365
                        $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));
366
                        break;
367
                    case 'Description':
368
                        $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));
369
                        break;
370
                }
371
            }
372
        }
373 1
        if (isset($xml->CustomDocumentProperties)) {
374
            foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
375
                $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
376
                $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
377
                $propertyType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_UNKNOWN;
378
                switch ((string) $propertyAttributes) {
379
                    case 'string':
380
                        $propertyType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_STRING;
381
                        $propertyValue = trim($propertyValue);
382
                        break;
383
                    case 'boolean':
384
                        $propertyType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_BOOLEAN;
385
                        $propertyValue = (bool) $propertyValue;
386
                        break;
387
                    case 'integer':
388
                        $propertyType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_INTEGER;
389
                        $propertyValue = intval($propertyValue);
390
                        break;
391
                    case 'float':
392
                        $propertyType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_FLOAT;
393
                        $propertyValue = floatval($propertyValue);
394
                        break;
395
                    case 'dateTime.tz':
396
                        $propertyType = \PhpOffice\PhpSpreadsheet\Document\Properties::PROPERTY_TYPE_DATE;
397
                        $propertyValue = strtotime(trim($propertyValue));
398
                        break;
399
                }
400
                $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
401
            }
402
        }
403
404 1
        foreach ($xml->Styles[0] as $style) {
405 1
            $style_ss = $style->attributes($namespaces['ss']);
406 1
            $styleID = (string) $style_ss['ID'];
407 1
            $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
408 1
            foreach ($style as $styleType => $styleData) {
409 1
                $styleAttributes = $styleData->attributes($namespaces['ss']);
410
                switch ($styleType) {
411 1
                    case 'Alignment':
412 1
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
413 1
                            $styleAttributeValue = (string) $styleAttributeValue;
414
                            switch ($styleAttributeKey) {
415 1
                                case 'Vertical':
416 1
                                    if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
417 1
                                        $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
418
                                    }
419 1
                                    break;
420 1
                                case 'Horizontal':
421 1
                                    if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
422 1
                                        $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
423
                                    }
424 1
                                    break;
425 1
                                case 'WrapText':
426 1
                                    $this->styles[$styleID]['alignment']['wrap'] = true;
427 1
                                    break;
428
                            }
429
                        }
430 1
                        break;
431 1
                    case 'Borders':
432 1
                        foreach ($styleData->Border as $borderStyle) {
433 1
                            $borderAttributes = $borderStyle->attributes($namespaces['ss']);
434 1
                            $thisBorder = [];
435 1
                            foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
436
                                switch ($borderStyleKey) {
437 1
                                    case 'LineStyle':
438 1
                                        $thisBorder['style'] = \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUM;
439 1
                                        break;
440 1
                                    case 'Weight':
441 1
                                        break;
442 1
                                    case 'Position':
443 1
                                        $borderPosition = strtolower($borderStyleValue);
444 1
                                        break;
445 1
                                    case 'Color':
446 1
                                        $borderColour = substr($borderStyleValue, 1);
447 1
                                        $thisBorder['color']['rgb'] = $borderColour;
448 1
                                        break;
449
                                }
450
                            }
451 1
                            if (!empty($thisBorder)) {
452 1
                                if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
453 1
                                    $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
0 ignored issues
show
Bug introduced by
The variable $borderPosition 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...
454
                                }
455
                            }
456
                        }
457 1
                        break;
458 1
                    case 'Font':
459 1
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
460 1
                            $styleAttributeValue = (string) $styleAttributeValue;
461
                            switch ($styleAttributeKey) {
462 1
                                case 'FontName':
463 1
                                    $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
464 1
                                    break;
465 1
                                case 'Size':
466 1
                                    $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
467 1
                                    break;
468 1 View Code Duplication
                                case '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...
469 1
                                    $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
470 1
                                    break;
471 1
                                case 'Bold':
472 1
                                    $this->styles[$styleID]['font']['bold'] = true;
473 1
                                    break;
474 1
                                case 'Italic':
475 1
                                    $this->styles[$styleID]['font']['italic'] = true;
476 1
                                    break;
477 1
                                case 'Underline':
478 1
                                    if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
479 1
                                        $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
480
                                    }
481 1
                                    break;
482
                            }
483
                        }
484 1
                        break;
485 1
                    case 'Interior':
486 1
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
487
                            switch ($styleAttributeKey) {
488 1 View Code Duplication
                                case '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...
489 1
                                    $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
490 1
                                    break;
491
                            }
492
                        }
493 1
                        break;
494 1
                    case 'NumberFormat':
495 1
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
496 1
                            $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
497
                            switch ($styleAttributeValue) {
498 1
                                case 'Short Date':
499 1
                                    $styleAttributeValue = 'dd/mm/yyyy';
500 1
                                    break;
501
                            }
502 1
                            if ($styleAttributeValue > '') {
503 1
                                $this->styles[$styleID]['numberformat']['code'] = $styleAttributeValue;
504
                            }
505
                        }
506 1
                        break;
507
                    case 'Protection':
508
                        foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
0 ignored issues
show
Unused Code introduced by
This foreach statement is empty and can be removed.

This check looks for foreach loops 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.

Consider removing the loop.

Loading history...
509
                        }
510 1
                        break;
511
                }
512
            }
513
        }
514
515 1
        $worksheetID = 0;
516 1
        $xml_ss = $xml->children($namespaces['ss']);
517
518 1
        foreach ($xml_ss->Worksheet as $worksheet) {
519 1
            $worksheet_ss = $worksheet->attributes($namespaces['ss']);
520
521 1
            if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
522 1
                (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
523
                continue;
524
            }
525
526
            // Create new Worksheet
527 1
            $spreadsheet->createSheet();
528 1
            $spreadsheet->setActiveSheetIndex($worksheetID);
529 1
            if (isset($worksheet_ss['Name'])) {
530 1
                $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
531
                //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
532
                //        formula cells... during the load, all formulae should be correct, and we're simply bringing
533
                //        the worksheet name in line with the formula, not the reverse
534 1
                $spreadsheet->getActiveSheet()->setTitle($worksheetName, false);
535
            }
536
537 1
            $columnID = 'A';
538 1
            if (isset($worksheet->Table->Column)) {
539 1
                foreach ($worksheet->Table->Column as $columnData) {
540 1
                    $columnData_ss = $columnData->attributes($namespaces['ss']);
541 1
                    if (isset($columnData_ss['Index'])) {
542 1
                        $columnID = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($columnData_ss['Index'] - 1);
543
                    }
544 1
                    if (isset($columnData_ss['Width'])) {
545 1
                        $columnWidth = $columnData_ss['Width'];
546 1
                        $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
547
                    }
548 1
                    ++$columnID;
549
                }
550
            }
551
552 1
            $rowID = 1;
553 1
            if (isset($worksheet->Table->Row)) {
554 1
                $additionalMergedCells = 0;
555 1
                foreach ($worksheet->Table->Row as $rowData) {
556 1
                    $rowHasData = false;
557 1
                    $row_ss = $rowData->attributes($namespaces['ss']);
558 1
                    if (isset($row_ss['Index'])) {
559 1
                        $rowID = (integer) $row_ss['Index'];
560
                    }
561
562 1
                    $columnID = 'A';
563 1
                    foreach ($rowData->Cell as $cell) {
564 1
                        $cell_ss = $cell->attributes($namespaces['ss']);
565 1
                        if (isset($cell_ss['Index'])) {
566 1
                            $columnID = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($cell_ss['Index'] - 1);
567
                        }
568 1
                        $cellRange = $columnID . $rowID;
569
570 1
                        if ($this->getReadFilter() !== null) {
571 1
                            if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
0 ignored issues
show
Bug introduced by
The variable $worksheetName 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...
572
                                continue;
573
                            }
574
                        }
575
576 1
                        if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
577 1
                            $columnTo = $columnID;
578 1 View Code Duplication
                            if (isset($cell_ss['MergeAcross'])) {
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...
579 1
                                $additionalMergedCells += (int) $cell_ss['MergeAcross'];
580 1
                                $columnTo = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(\PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($columnID) + $cell_ss['MergeAcross'] - 1);
581
                            }
582 1
                            $rowTo = $rowID;
583 1
                            if (isset($cell_ss['MergeDown'])) {
584 1
                                $rowTo = $rowTo + $cell_ss['MergeDown'];
585
                            }
586 1
                            $cellRange .= ':' . $columnTo . $rowTo;
587 1
                            $spreadsheet->getActiveSheet()->mergeCells($cellRange);
588
                        }
589
590 1
                        $cellIsSet = $hasCalculatedValue = false;
591 1
                        $cellDataFormula = '';
592 1
                        if (isset($cell_ss['Formula'])) {
593 1
                            $cellDataFormula = $cell_ss['Formula'];
594
                            // added this as a check for array formulas
595 1
                            if (isset($cell_ss['ArrayRange'])) {
596
                                $cellDataCSEFormula = $cell_ss['ArrayRange'];
0 ignored issues
show
Unused Code introduced by
$cellDataCSEFormula 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...
597
                            }
598 1
                            $hasCalculatedValue = true;
599
                        }
600 1
                        if (isset($cell->Data)) {
601 1
                            $cellValue = $cellData = $cell->Data;
602 1
                            $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NULL;
603 1
                            $cellData_ss = $cellData->attributes($namespaces['ss']);
604 1
                            if (isset($cellData_ss['Type'])) {
605 1
                                $cellDataType = $cellData_ss['Type'];
606
                                switch ($cellDataType) {
607
                                    /*
608
                                    const TYPE_STRING        = 's';
609
                                    const TYPE_FORMULA        = 'f';
610
                                    const TYPE_NUMERIC        = 'n';
611
                                    const TYPE_BOOL            = 'b';
612
                                    const TYPE_NULL            = 'null';
613
                                    const TYPE_INLINE        = 'inlineStr';
614
                                    const TYPE_ERROR        = 'e';
615
                                    */
616 1
                                    case 'String':
617 1
                                        $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
618 1
                                        $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING;
619 1
                                        break;
620 1
                                    case 'Number':
621 1
                                        $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC;
622 1
                                        $cellValue = (float) $cellValue;
623 1
                                        if (floor($cellValue) == $cellValue) {
624 1
                                            $cellValue = (integer) $cellValue;
625
                                        }
626 1
                                        break;
627 1
                                    case 'Boolean':
628 1
                                        $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_BOOL;
629 1
                                        $cellValue = ($cellValue != 0);
630 1
                                        break;
631 1
                                    case 'DateTime':
632 1
                                        $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC;
633 1
                                        $cellValue = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel(strtotime($cellValue));
634 1
                                        break;
635
                                    case 'Error':
636
                                        $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_ERROR;
637
                                        break;
638
                                }
639
                            }
640
641 1
                            if ($hasCalculatedValue) {
642 1
                                $type = \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA;
643 1
                                $columnNumber = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($columnID);
644 1
                                if (substr($cellDataFormula, 0, 3) == 'of:') {
645 1
                                    $cellDataFormula = substr($cellDataFormula, 3);
646 1
                                    $temp = explode('"', $cellDataFormula);
647 1
                                    $key = false;
648 1
                                    foreach ($temp as &$value) {
649
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
650 1
                                        if ($key = !$key) {
651 1
                                            $value = str_replace(['[.', '.', ']'], '', $value);
652
                                        }
653
                                    }
654
                                } else {
655
                                    //    Convert R1C1 style references to A1 style references (but only when not quoted)
656
                                    $temp = explode('"', $cellDataFormula);
657
                                    $key = false;
658 View Code Duplication
                                    foreach ($temp as &$value) {
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...
659
                                        //    Only replace in alternate array entries (i.e. non-quoted blocks)
660
                                        if ($key = !$key) {
661
                                            preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
662
                                            //    Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
663
                                            //        through the formula from left to right. Reversing means that we work right to left.through
664
                                            //        the formula
665
                                            $cellReferences = array_reverse($cellReferences);
666
                                            //    Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
667
                                            //        then modify the formula to use that new reference
668
                                            foreach ($cellReferences as $cellReference) {
669
                                                $rowReference = $cellReference[2][0];
670
                                                //    Empty R reference is the current row
671
                                                if ($rowReference == '') {
672
                                                    $rowReference = $rowID;
673
                                                }
674
                                                //    Bracketed R references are relative to the current row
675
                                                if ($rowReference{0} == '[') {
676
                                                    $rowReference = $rowID + trim($rowReference, '[]');
677
                                                }
678
                                                $columnReference = $cellReference[4][0];
679
                                                //    Empty C reference is the current column
680
                                                if ($columnReference == '') {
681
                                                    $columnReference = $columnNumber;
682
                                                }
683
                                                //    Bracketed C references are relative to the current column
684
                                                if ($columnReference{0} == '[') {
685
                                                    $columnReference = $columnNumber + trim($columnReference, '[]');
686
                                                }
687
                                                $A1CellReference = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($columnReference - 1) . $rowReference;
688
                                                $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
689
                                            }
690
                                        }
691
                                    }
692
                                }
693 1
                                unset($value);
694
                                //    Then rebuild the formula string
695 1
                                $cellDataFormula = implode('"', $temp);
696
                            }
697
698 1
                            $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
699 1
                            if ($hasCalculatedValue) {
700 1
                                $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
701
                            }
702 1
                            $cellIsSet = $rowHasData = true;
703
                        }
704
705 1
                        if (isset($cell->Comment)) {
706 1
                            $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
707 1
                            $author = 'unknown';
708 1
                            if (isset($commentAttributes->Author)) {
709
                                $author = (string) $commentAttributes->Author;
710
                            }
711 1
                            $node = $cell->Comment->Data->asXML();
712 1
                            $annotation = strip_tags($node);
713 1
                            $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
714
                        }
715
716 1
                        if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
717 1
                            $style = (string) $cell_ss['StyleID'];
718 1
                            if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
719 1
                                if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
720
                                    $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
721
                                }
722 1
                                $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
723
                            }
724
                        }
725 1
                        ++$columnID;
726 1
                        while ($additionalMergedCells > 0) {
727 1
                            ++$columnID;
728 1
                            --$additionalMergedCells;
729
                        }
730
                    }
731
732 1
                    if ($rowHasData) {
733 1
                        if (isset($row_ss['StyleID'])) {
734
                            $rowStyle = $row_ss['StyleID'];
0 ignored issues
show
Unused Code introduced by
$rowStyle 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...
735
                        }
736 1
                        if (isset($row_ss['Height'])) {
737 1
                            $rowHeight = $row_ss['Height'];
738 1
                            $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
739
                        }
740
                    }
741
742 1
                    ++$rowID;
743
                }
744
            }
745 1
            ++$worksheetID;
746
        }
747
748
        // Return
749 1
        return $spreadsheet;
750
    }
751
752 1
    protected static function convertStringEncoding($string, $charset)
753
    {
754 1
        if ($charset != 'UTF-8') {
755
            return \PhpOffice\PhpSpreadsheet\Shared\StringHelper::convertEncoding($string, 'UTF-8', $charset);
756
        }
757
758 1
        return $string;
759
    }
760
761 1
    protected function parseRichText($is = '')
762
    {
763 1
        $value = new \PhpOffice\PhpSpreadsheet\RichText();
764
765 1
        $value->createText(self::convertStringEncoding($is, $this->charSet));
766
767 1
        return $value;
768
    }
769
}
770