Completed
Push — develop ( 2922a1...cfa1fe )
by Adrien
24:40
created

Spreadsheet::setProperties()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 3
cp 0
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
/**
6
 * PhpSpreadsheet
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 PHPOffice (http://www.github.com/PHPOffice)
26
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
27
 * @version    ##VERSION##, ##DATE##
28
 */
29
class Spreadsheet
30
{
31
    /**
32
     * Unique ID
33
     *
34
     * @var string
35
     */
36
    private $uniqueID;
37
38
    /**
39
     * Document properties
40
     *
41
     * @var Document\Properties
42
     */
43
    private $properties;
44
45
    /**
46
     * Document security
47
     *
48
     * @var Document\Security
49
     */
50
    private $security;
51
52
    /**
53
     * Collection of Worksheet objects
54
     *
55
     * @var Worksheet[]
56
     */
57
    private $workSheetCollection = [];
58
59
    /**
60
     * Calculation Engine
61
     *
62
     * @var Calculation
63
     */
64
    private $calculationEngine;
65
66
    /**
67
     * Active sheet index
68
     *
69
     * @var int
70
     */
71
    private $activeSheetIndex = 0;
72
73
    /**
74
     * Named ranges
75
     *
76
     * @var NamedRange[]
77
     */
78
    private $namedRanges = [];
79
80
    /**
81
     * CellXf supervisor
82
     *
83
     * @var Style
84
     */
85
    private $cellXfSupervisor;
86
87
    /**
88
     * CellXf collection
89
     *
90
     * @var Style[]
91
     */
92
    private $cellXfCollection = [];
93
94
    /**
95
     * CellStyleXf collection
96
     *
97
     * @var Style[]
98
     */
99
    private $cellStyleXfCollection = [];
100
101
    /**
102
     * hasMacros : this workbook have macros ?
103
     *
104
     * @var bool
105
     */
106
    private $hasMacros = false;
107
108
    /**
109
     * macrosCode : all macros code (the vbaProject.bin file, this include form, code,  etc.), null if no macro
110
     *
111
     * @var binary
112
     */
113
    private $macrosCode;
114
    /**
115
     * macrosCertificate : if macros are signed, contains vbaProjectSignature.bin file, null if not signed
116
     *
117
     * @var binary
118
     */
119
    private $macrosCertificate;
120
121
    /**
122
     * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI
123
     *
124
     * @var null|string
125
     */
126
    private $ribbonXMLData;
127
128
    /**
129
     * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
130
     * ignored if $ribbonXMLData is null
131
     *
132
     * @var null|array
133
     */
134
    private $ribbonBinObjects;
135
136
    /**
137
     * The workbook has macros ?
138
     *
139
     * @return bool
140
     */
141
    public function hasMacros()
142
    {
143
        return $this->hasMacros;
144
    }
145
146
    /**
147
     * Define if a workbook has macros
148
     *
149
     * @param bool $hasMacros true|false
150
     */
151
    public function setHasMacros($hasMacros = false)
152
    {
153
        $this->hasMacros = (bool) $hasMacros;
154
    }
155
156
    /**
157
     * Set the macros code
158
     *
159
     * @param string $macroCode string|null
160
     */
161
    public function setMacrosCode($macroCode = null)
162
    {
163
        $this->macrosCode = $macroCode;
0 ignored issues
show
Documentation Bug introduced by
It seems like $macroCode can also be of type string. However, the property $macrosCode is declared as type object<PhpOffice\PhpSpreadsheet\binary>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
164
        $this->setHasMacros(!is_null($macroCode));
165
    }
166
167
    /**
168
     * Return the macros code
169
     *
170
     * @return string|null
171
     */
172
    public function getMacrosCode()
173
    {
174
        return $this->macrosCode;
175
    }
176
177
    /**
178
     * Set the macros certificate
179
     *
180
     * @param string|null $certificate
181
     */
182
    public function setMacrosCertificate($certificate = null)
183
    {
184
        $this->macrosCertificate = $certificate;
0 ignored issues
show
Documentation Bug introduced by
It seems like $certificate can also be of type string. However, the property $macrosCertificate is declared as type object<PhpOffice\PhpSpreadsheet\binary>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
185
    }
186
187
    /**
188
     * Is the project signed ?
189
     *
190
     * @return bool true|false
191
     */
192
    public function hasMacrosCertificate()
193
    {
194
        return !is_null($this->macrosCertificate);
195
    }
196
197
    /**
198
     * Return the macros certificate
199
     *
200
     * @return string|null
201
     */
202
    public function getMacrosCertificate()
203
    {
204
        return $this->macrosCertificate;
205
    }
206
207
    /**
208
     * Remove all macros, certificate from spreadsheet
209
     */
210
    public function discardMacros()
211
    {
212
        $this->hasMacros = false;
213
        $this->macrosCode = null;
214
        $this->macrosCertificate = null;
215
    }
216
217
    /**
218
     * set ribbon XML data
219
     */
220
    public function setRibbonXMLData($target = null, $xmlData = null)
221
    {
222
        if (!is_null($target) && !is_null($xmlData)) {
223
            $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('target' => $target, 'data' => $xmlData) of type array<string,?,{"target":"?","data":"?"}> is incompatible with the declared type null|string of property $ribbonXMLData.

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...
224
        } else {
225
            $this->ribbonXMLData = null;
226
        }
227
    }
228
229
    /**
230
     * retrieve ribbon XML Data
231
     *
232
     * return string|null|array
233
     * @return string
234
     */
235
    public function getRibbonXMLData($what = 'all') //we need some constants here...
236
    {
237
        $returnData = null;
238
        $what = strtolower($what);
239
        switch ($what) {
240
            case 'all':
241
                $returnData = $this->ribbonXMLData;
242
                break;
243
            case 'target':
244
            case 'data':
245
                if (is_array($this->ribbonXMLData) && array_key_exists($what, $this->ribbonXMLData)) {
246
                    $returnData = $this->ribbonXMLData[$what];
247
                }
248
                break;
249
        }
250
251
        return $returnData;
252
    }
253
254
    /**
255
     * store binaries ribbon objects (pictures)
256
     */
257
    public function setRibbonBinObjects($BinObjectsNames = null, $BinObjectsData = null)
258
    {
259
        if (!is_null($BinObjectsNames) && !is_null($BinObjectsData)) {
260
            $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
261
        } else {
262
            $this->ribbonBinObjects = null;
263
        }
264
    }
265
    /**
266
     * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function)
267
     */
268
    private function getExtensionOnly($ThePath)
269
    {
270
        return pathinfo($ThePath, PATHINFO_EXTENSION);
271
    }
272
273
    /**
274
     * retrieve Binaries Ribbon Objects
275
     */
276
    public function getRibbonBinObjects($What = 'all')
277
    {
278
        $ReturnData = null;
279
        $What = strtolower($What);
280
        switch ($What) {
281
            case 'all':
282
                return $this->ribbonBinObjects;
283
                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...
284
            case 'names':
285
            case 'data':
286
                if (is_array($this->ribbonBinObjects) && array_key_exists($What, $this->ribbonBinObjects)) {
287
                    $ReturnData = $this->ribbonBinObjects[$What];
288
                }
289
                break;
290
            case 'types':
291
                if (is_array($this->ribbonBinObjects) &&
292
                    array_key_exists('data', $this->ribbonBinObjects) && is_array($this->ribbonBinObjects['data'])) {
293
                    $tmpTypes = array_keys($this->ribbonBinObjects['data']);
294
                    $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
295
                } else {
296
                    $ReturnData = []; // the caller want an array... not null if empty
297
                }
298
                break;
299
        }
300
301
        return $ReturnData;
302
    }
303
304
    /**
305
     * This workbook have a custom UI ?
306
     *
307
     * @return bool
308
     */
309
    public function hasRibbon()
310
    {
311
        return !is_null($this->ribbonXMLData);
312
    }
313
314
    /**
315
     * This workbook have additionnal object for the ribbon ?
316
     *
317
     * @return bool
318
     */
319
    public function hasRibbonBinObjects()
320
    {
321
        return !is_null($this->ribbonBinObjects);
322
    }
323
324
    /**
325
     * Check if a sheet with a specified code name already exists
326
     *
327
     * @param string $pSheetCodeName  Name of the worksheet to check
328
     * @return bool
329
     */
330 1
    public function sheetCodeNameExists($pSheetCodeName)
331
    {
332 1
        return $this->getSheetByCodeName($pSheetCodeName) !== null;
333
    }
334
335
    /**
336
     * Get sheet by code name. Warning : sheet don't have always a code name !
337
     *
338
     * @param string $pName Sheet name
339
     * @return Worksheet
340
     */
341 1 View Code Duplication
    public function getSheetByCodeName($pName = '')
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...
342
    {
343 1
        $worksheetCount = count($this->workSheetCollection);
344 1
        for ($i = 0; $i < $worksheetCount; ++$i) {
345
            if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
346
                return $this->workSheetCollection[$i];
347
            }
348
        }
349
350 1
        return null;
351
    }
352
353
    /**
354
     * Create a new PhpSpreadsheet with one Worksheet
355
     */
356 1
    public function __construct()
357
    {
358 1
        $this->uniqueID = uniqid();
359 1
        $this->calculationEngine = new Calculation($this);
360
361
        // Initialise worksheet collection and add one worksheet
362 1
        $this->workSheetCollection = [];
363 1
        $this->workSheetCollection[] = new Worksheet($this);
364 1
        $this->activeSheetIndex = 0;
365
366
        // Create document properties
367 1
        $this->properties = new Document\Properties();
368
369
        // Create document security
370 1
        $this->security = new Document\Security();
371
372
        // Set named ranges
373 1
        $this->namedRanges = [];
374
375
        // Create the cellXf supervisor
376 1
        $this->cellXfSupervisor = new Style(true);
377 1
        $this->cellXfSupervisor->bindParent($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<PhpOffice\PhpSpreadsheet\Spreadsheet>, but the function expects a object<PhpOffice\PhpSpreadsheet\Style>.

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...
378
379
        // Create the default style
380 1
        $this->addCellXf(new Style());
381 1
        $this->addCellStyleXf(new Style());
382 1
    }
383
384
    /**
385
     * Code to execute when this worksheet is unset()
386
     */
387
    public function __destruct()
388
    {
389
        $this->calculationEngine = null;
390
        $this->disconnectWorksheets();
391
    }
392
393
    /**
394
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
395
     *    typically so that the PhpSpreadsheet object can be unset
396
     */
397
    public function disconnectWorksheets()
398
    {
399
        $worksheet = null;
400
        foreach ($this->workSheetCollection as $k => &$worksheet) {
401
            $worksheet->disconnectCells();
402
            $this->workSheetCollection[$k] = null;
403
        }
404
        unset($worksheet);
405
        $this->workSheetCollection = [];
406
    }
407
408
    /**
409
     * Return the calculation engine for this worksheet
410
     *
411
     * @return Calculation
412
     */
413 1
    public function getCalculationEngine()
414
    {
415 1
        return $this->calculationEngine;
416
    }    //    function getCellCacheController()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
417
418
    /**
419
     * Get properties
420
     *
421
     * @return Document\Properties
422
     */
423
    public function getProperties()
424
    {
425
        return $this->properties;
426
    }
427
428
    /**
429
     * Set properties
430
     *
431
     * @param Document\Properties    $pValue
432
     */
433
    public function setProperties(Document\Properties $pValue)
434
    {
435
        $this->properties = $pValue;
436
    }
437
438
    /**
439
     * Get security
440
     *
441
     * @return Document\Security
442
     */
443
    public function getSecurity()
444
    {
445
        return $this->security;
446
    }
447
448
    /**
449
     * Set security
450
     *
451
     * @param Document\Security    $pValue
452
     */
453
    public function setSecurity(Document\Security $pValue)
454
    {
455
        $this->security = $pValue;
456
    }
457
458
    /**
459
     * Get active sheet
460
     *
461
     * @throws Exception
462
     * @return Worksheet
463
     */
464 1
    public function getActiveSheet()
465
    {
466 1
        return $this->getSheet($this->activeSheetIndex);
467
    }
468
469
    /**
470
     * Create sheet and add it to this workbook
471
     *
472
     * @param  int|null $iSheetIndex Index where sheet should go (0,1,..., or null for last)
473
     * @throws Exception
474
     * @return Worksheet
475
     */
476
    public function createSheet($iSheetIndex = null)
477
    {
478
        $newSheet = new Worksheet($this);
479
        $this->addSheet($newSheet, $iSheetIndex);
480
481
        return $newSheet;
482
    }
483
484
    /**
485
     * Check if a sheet with a specified name already exists
486
     *
487
     * @param  string $pSheetName  Name of the worksheet to check
488
     * @return bool
489
     */
490 1
    public function sheetNameExists($pSheetName)
491
    {
492 1
        return $this->getSheetByName($pSheetName) !== null;
493
    }
494
495
    /**
496
     * Add sheet
497
     *
498
     * @param  Worksheet $pSheet
499
     * @param  int|null $iSheetIndex Index where sheet should go (0,1,..., or null for last)
500
     * @throws Exception
501
     * @return Worksheet
502
     */
503
    public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
504
    {
505
        if ($this->sheetNameExists($pSheet->getTitle())) {
506
            throw new Exception(
507
                "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first."
508
            );
509
        }
510
511
        if ($iSheetIndex === null) {
512
            if ($this->activeSheetIndex < 0) {
513
                $this->activeSheetIndex = 0;
514
            }
515
            $this->workSheetCollection[] = $pSheet;
516
        } else {
517
            // Insert the sheet at the requested index
518
            array_splice(
519
                $this->workSheetCollection,
520
                $iSheetIndex,
521
                0,
522
                [$pSheet]
523
            );
524
525
            // Adjust active sheet index if necessary
526
            if ($this->activeSheetIndex >= $iSheetIndex) {
527
                ++$this->activeSheetIndex;
528
            }
529
        }
530
531
        if ($pSheet->getParent() === null) {
532
            $pSheet->rebindParent($this);
533
        }
534
535
        return $pSheet;
536
    }
537
538
    /**
539
     * Remove sheet by index
540
     *
541
     * @param  int $pIndex Active sheet index
542
     * @throws Exception
543
     */
544
    public function removeSheetByIndex($pIndex = 0)
545
    {
546
        $numSheets = count($this->workSheetCollection);
547
        if ($pIndex > $numSheets - 1) {
548
            throw new Exception(
549
                "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
550
            );
551
        } else {
552
            array_splice($this->workSheetCollection, $pIndex, 1);
553
        }
554
        // Adjust active sheet index if necessary
555
        if (($this->activeSheetIndex >= $pIndex) &&
556
            ($pIndex > count($this->workSheetCollection) - 1)) {
557
            --$this->activeSheetIndex;
558
        }
559
    }
560
561
    /**
562
     * Get sheet by index
563
     *
564
     * @param  int $pIndex Sheet index
565
     * @throws Exception
566
     * @return Worksheet
567
     */
568 1
    public function getSheet($pIndex = 0)
569
    {
570 1
        if (!isset($this->workSheetCollection[$pIndex])) {
571
            $numSheets = $this->getSheetCount();
572
            throw new Exception(
573
                "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}."
574
            );
575
        }
576
577 1
        return $this->workSheetCollection[$pIndex];
578
    }
579
580
    /**
581
     * Get all sheets
582
     *
583
     * @return Worksheet[]
584
     */
585
    public function getAllSheets()
586
    {
587
        return $this->workSheetCollection;
588
    }
589
590
    /**
591
     * Get sheet by name
592
     *
593
     * @param  string $pName Sheet name
594
     * @return Worksheet
595
     */
596 1 View Code Duplication
    public function getSheetByName($pName = '')
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...
597
    {
598 1
        $worksheetCount = count($this->workSheetCollection);
599 1
        for ($i = 0; $i < $worksheetCount; ++$i) {
600
            if ($this->workSheetCollection[$i]->getTitle() === $pName) {
601
                return $this->workSheetCollection[$i];
602
            }
603
        }
604
605 1
        return null;
606
    }
607
608
    /**
609
     * Get index for sheet
610
     *
611
     * @param  Worksheet $pSheet
612
     * @throws Exception
613
     * @return int index
614
     */
615
    public function getIndex(Worksheet $pSheet)
616
    {
617
        foreach ($this->workSheetCollection as $key => $value) {
618
            if ($value->getHashCode() == $pSheet->getHashCode()) {
619
                return $key;
620
            }
621
        }
622
623
        throw new Exception('Sheet does not exist.');
624
    }
625
626
    /**
627
     * Set index for sheet by sheet name.
628
     *
629
     * @param  string $sheetName Sheet name to modify index for
630
     * @param  int $newIndex New index for the sheet
631
     * @throws Exception
632
     * @return int New sheet index
633
     */
634
    public function setIndexByName($sheetName, $newIndex)
635
    {
636
        $oldIndex = $this->getIndex($this->getSheetByName($sheetName));
0 ignored issues
show
Bug introduced by
It seems like $this->getSheetByName($sheetName) can be null; however, getIndex() 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...
637
        $pSheet = array_splice(
638
            $this->workSheetCollection,
639
            $oldIndex,
640
            1
641
        );
642
        array_splice(
643
            $this->workSheetCollection,
644
            $newIndex,
645
            0,
646
            $pSheet
647
        );
648
649
        return $newIndex;
650
    }
651
652
    /**
653
     * Get sheet count
654
     *
655
     * @return int
656
     */
657
    public function getSheetCount()
658
    {
659
        return count($this->workSheetCollection);
660
    }
661
662
    /**
663
     * Get active sheet index
664
     *
665
     * @return int Active sheet index
666
     */
667
    public function getActiveSheetIndex()
668
    {
669
        return $this->activeSheetIndex;
670
    }
671
672
    /**
673
     * Set active sheet index
674
     *
675
     * @param  int $pIndex Active sheet index
676
     * @throws Exception
677
     * @return Worksheet
678
     */
679
    public function setActiveSheetIndex($pIndex = 0)
680
    {
681
        $numSheets = count($this->workSheetCollection);
682
683
        if ($pIndex > $numSheets - 1) {
684
            throw new Exception(
685
                "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
686
            );
687
        } else {
688
            $this->activeSheetIndex = $pIndex;
689
        }
690
691
        return $this->getActiveSheet();
692
    }
693
694
    /**
695
     * Set active sheet index by name
696
     *
697
     * @param  string $pValue Sheet title
698
     * @throws Exception
699
     * @return Worksheet
700
     */
701
    public function setActiveSheetIndexByName($pValue = '')
702
    {
703
        if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
704
            $this->setActiveSheetIndex($this->getIndex($worksheet));
705
706
            return $worksheet;
707
        }
708
709
        throw new Exception('Workbook does not contain sheet:' . $pValue);
710
    }
711
712
    /**
713
     * Get sheet names
714
     *
715
     * @return string[]
716
     */
717
    public function getSheetNames()
718
    {
719
        $returnValue = [];
720
        $worksheetCount = $this->getSheetCount();
721
        for ($i = 0; $i < $worksheetCount; ++$i) {
722
            $returnValue[] = $this->getSheet($i)->getTitle();
723
        }
724
725
        return $returnValue;
726
    }
727
728
    /**
729
     * Add external sheet
730
     *
731
     * @param  Worksheet $pSheet External sheet to add
732
     * @param  int|null $iSheetIndex Index where sheet should go (0,1,..., or null for last)
733
     * @throws Exception
734
     * @return Worksheet
735
     */
736
    public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
737
    {
738
        if ($this->sheetNameExists($pSheet->getTitle())) {
739
            throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first.");
740
        }
741
742
        // count how many cellXfs there are in this workbook currently, we will need this below
743
        $countCellXfs = count($this->cellXfCollection);
744
745
        // copy all the shared cellXfs from the external workbook and append them to the current
746
        foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
747
            $this->addCellXf(clone $cellXf);
748
        }
749
750
        // move sheet to this workbook
751
        $pSheet->rebindParent($this);
752
753
        // update the cellXfs
754
        foreach ($pSheet->getCellCollection(false) as $cellID) {
755
            $cell = $pSheet->getCell($cellID);
756
            $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
757
        }
758
759
        return $this->addSheet($pSheet, $iSheetIndex);
760
    }
761
762
    /**
763
     * Get named ranges
764
     *
765
     * @return NamedRange[]
766
     */
767
    public function getNamedRanges()
768
    {
769
        return $this->namedRanges;
770
    }
771
772
    /**
773
     * Add named range
774
     *
775
     * @param  NamedRange $namedRange
776
     * @return bool
777
     */
778
    public function addNamedRange(NamedRange $namedRange)
779
    {
780
        if ($namedRange->getScope() == null) {
781
            // global scope
782
            $this->namedRanges[$namedRange->getName()] = $namedRange;
783
        } else {
784
            // local scope
785
            $this->namedRanges[$namedRange->getScope()->getTitle() . '!' . $namedRange->getName()] = $namedRange;
786
        }
787
788
        return true;
789
    }
790
791
    /**
792
     * Get named range
793
     *
794
     * @param  string $namedRange
795
     * @param  Worksheet|null $pSheet Scope. Use null for global scope
796
     * @return NamedRange|null
797
     */
798
    public function getNamedRange($namedRange, Worksheet $pSheet = null)
799
    {
800
        $returnValue = null;
801
802
        if ($namedRange != '' && ($namedRange !== null)) {
803
            // first look for global defined name
804
            if (isset($this->namedRanges[$namedRange])) {
805
                $returnValue = $this->namedRanges[$namedRange];
806
            }
807
808
            // then look for local defined name (has priority over global defined name if both names exist)
809
            if (($pSheet !== null) && isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
810
                $returnValue = $this->namedRanges[$pSheet->getTitle() . '!' . $namedRange];
811
            }
812
        }
813
814
        return $returnValue;
815
    }
816
817
    /**
818
     * Remove named range
819
     *
820
     * @param  string  $namedRange
821
     * @param  Worksheet|null  $pSheet  Scope: use null for global scope.
822
     * @return Spreadsheet
823
     */
824
    public function removeNamedRange($namedRange, Worksheet $pSheet = null)
825
    {
826
        if ($pSheet === null) {
827
            if (isset($this->namedRanges[$namedRange])) {
828
                unset($this->namedRanges[$namedRange]);
829
            }
830
        } else {
831
            if (isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
832
                unset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]);
833
            }
834
        }
835
836
        return $this;
837
    }
838
839
    /**
840
     * Get worksheet iterator
841
     *
842
     * @return Worksheet\Iterator
843
     */
844
    public function getWorksheetIterator()
845
    {
846
        return new Worksheet\Iterator($this);
847
    }
848
849
    /**
850
     * Copy workbook (!= clone!)
851
     *
852
     * @return Spreadsheet
853
     */
854
    public function copy()
855
    {
856
        $copied = clone $this;
857
858
        $worksheetCount = count($this->workSheetCollection);
859
        for ($i = 0; $i < $worksheetCount; ++$i) {
860
            $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
861
            $this->workSheetCollection[$i]->rebindParent($this);
862
        }
863
864
        return $copied;
865
    }
866
867
    /**
868
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
869
     */
870
    public function __clone()
871
    {
872
        foreach ($this as $key => $val) {
0 ignored issues
show
Bug introduced by
The expression $this of type this<PhpOffice\PhpSpreadsheet\Spreadsheet> is not traversable.
Loading history...
873
            if (is_object($val) || (is_array($val))) {
874
                $this->{$key} = unserialize(serialize($val));
875
            }
876
        }
877
    }
878
879
    /**
880
     * Get the workbook collection of cellXfs
881
     *
882
     * @return Style[]
883
     */
884
    public function getCellXfCollection()
885
    {
886
        return $this->cellXfCollection;
887
    }
888
889
    /**
890
     * Get cellXf by index
891
     *
892
     * @param  int $pIndex
893
     * @return Style
894
     */
895
    public function getCellXfByIndex($pIndex = 0)
896
    {
897
        return $this->cellXfCollection[$pIndex];
898
    }
899
900
    /**
901
     * Get cellXf by hash code
902
     *
903
     * @param  string $pValue
904
     * @return Style|false
905
     */
906
    public function getCellXfByHashCode($pValue = '')
907
    {
908
        foreach ($this->cellXfCollection as $cellXf) {
909
            if ($cellXf->getHashCode() == $pValue) {
910
                return $cellXf;
911
            }
912
        }
913
914
        return false;
915
    }
916
917
    /**
918
     * Check if style exists in style collection
919
     *
920
     * @param  Style $pCellStyle
921
     * @return bool
922
     */
923
    public function cellXfExists($pCellStyle = null)
924
    {
925
        return in_array($pCellStyle, $this->cellXfCollection, true);
926
    }
927
928
    /**
929
     * Get default style
930
     *
931
     * @throws Exception
932
     * @return Style
933
     */
934
    public function getDefaultStyle()
935
    {
936
        if (isset($this->cellXfCollection[0])) {
937
            return $this->cellXfCollection[0];
938
        }
939
        throw new Exception('No default style found for this workbook');
940
    }
941
942
    /**
943
     * Add a cellXf to the workbook
944
     *
945
     * @param Style $style
946
     */
947 1
    public function addCellXf(Style $style)
948
    {
949 1
        $this->cellXfCollection[] = $style;
950 1
        $style->setIndex(count($this->cellXfCollection) - 1);
951 1
    }
952
953
    /**
954
     * Remove cellXf by index. It is ensured that all cells get their xf index updated.
955
     *
956
     * @param int $pIndex Index to cellXf
957
     * @throws Exception
958
     */
959
    public function removeCellXfByIndex($pIndex = 0)
960
    {
961
        if ($pIndex > count($this->cellXfCollection) - 1) {
962
            throw new Exception('CellXf index is out of bounds.');
963
        } else {
964
            // first remove the cellXf
965
            array_splice($this->cellXfCollection, $pIndex, 1);
966
967
            // then update cellXf indexes for cells
968
            foreach ($this->workSheetCollection as $worksheet) {
969
                foreach ($worksheet->getCellCollection(false) as $cellID) {
970
                    $cell = $worksheet->getCell($cellID);
971
                    $xfIndex = $cell->getXfIndex();
972
                    if ($xfIndex > $pIndex) {
973
                        // decrease xf index by 1
974
                        $cell->setXfIndex($xfIndex - 1);
975
                    } elseif ($xfIndex == $pIndex) {
976
                        // set to default xf index 0
977
                        $cell->setXfIndex(0);
978
                    }
979
                }
980
            }
981
        }
982
    }
983
984
    /**
985
     * Get the cellXf supervisor
986
     *
987
     * @return Style
988
     */
989
    public function getCellXfSupervisor()
990
    {
991
        return $this->cellXfSupervisor;
992
    }
993
994
    /**
995
     * Get the workbook collection of cellStyleXfs
996
     *
997
     * @return Style[]
998
     */
999
    public function getCellStyleXfCollection()
1000
    {
1001
        return $this->cellStyleXfCollection;
1002
    }
1003
1004
    /**
1005
     * Get cellStyleXf by index
1006
     *
1007
     * @param int $pIndex Index to cellXf
1008
     * @return Style
1009
     */
1010
    public function getCellStyleXfByIndex($pIndex = 0)
1011
    {
1012
        return $this->cellStyleXfCollection[$pIndex];
1013
    }
1014
1015
    /**
1016
     * Get cellStyleXf by hash code
1017
     *
1018
     * @param  string $pValue
1019
     * @return Style|false
1020
     */
1021
    public function getCellStyleXfByHashCode($pValue = '')
1022
    {
1023
        foreach ($this->cellStyleXfCollection as $cellStyleXf) {
1024
            if ($cellStyleXf->getHashCode() == $pValue) {
1025
                return $cellStyleXf;
1026
            }
1027
        }
1028
1029
        return false;
1030
    }
1031
1032
    /**
1033
     * Add a cellStyleXf to the workbook
1034
     *
1035
     * @param Style $pStyle
1036
     */
1037 1
    public function addCellStyleXf(Style $pStyle)
1038
    {
1039 1
        $this->cellStyleXfCollection[] = $pStyle;
1040 1
        $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
1041 1
    }
1042
1043
    /**
1044
     * Remove cellStyleXf by index
1045
     *
1046
     * @param int $pIndex Index to cellXf
1047
     * @throws Exception
1048
     */
1049
    public function removeCellStyleXfByIndex($pIndex = 0)
1050
    {
1051
        if ($pIndex > count($this->cellStyleXfCollection) - 1) {
1052
            throw new Exception('CellStyleXf index is out of bounds.');
1053
        } else {
1054
            array_splice($this->cellStyleXfCollection, $pIndex, 1);
1055
        }
1056
    }
1057
1058
    /**
1059
     * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1060
     * and columns in the workbook
1061
     */
1062
    public function garbageCollect()
1063
    {
1064
        // how many references are there to each cellXf ?
1065
        $countReferencesCellXf = [];
1066
        foreach ($this->cellXfCollection as $index => $cellXf) {
1067
            $countReferencesCellXf[$index] = 0;
1068
        }
1069
1070
        foreach ($this->getWorksheetIterator() as $sheet) {
1071
            // from cells
1072 View Code Duplication
            foreach ($sheet->getCellCollection(false) as $cellID) {
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...
1073
                $cell = $sheet->getCell($cellID);
1074
                ++$countReferencesCellXf[$cell->getXfIndex()];
1075
            }
1076
1077
            // from row dimensions
1078
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1079
                if ($rowDimension->getXfIndex() !== null) {
1080
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1081
                }
1082
            }
1083
1084
            // from column dimensions
1085
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1086
                ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1087
            }
1088
        }
1089
1090
        // remove cellXfs without references and create mapping so we can update xfIndex
1091
        // for all cells and columns
1092
        $countNeededCellXfs = 0;
1093
        foreach ($this->cellXfCollection as $index => $cellXf) {
1094
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1095
                ++$countNeededCellXfs;
1096
            } else {
1097
                unset($this->cellXfCollection[$index]);
1098
            }
1099
            $map[$index] = $countNeededCellXfs - 1;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$map was never initialized. Although not strictly required by PHP, it is generally a good practice to add $map = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1100
        }
1101
        $this->cellXfCollection = array_values($this->cellXfCollection);
1102
1103
        // update the index for all cellXfs
1104
        foreach ($this->cellXfCollection as $i => $cellXf) {
1105
            $cellXf->setIndex($i);
1106
        }
1107
1108
        // make sure there is always at least one cellXf (there should be)
1109
        if (empty($this->cellXfCollection)) {
1110
            $this->cellXfCollection[] = new Style();
1111
        }
1112
1113
        // update the xfIndex for all cells, row dimensions, column dimensions
1114
        foreach ($this->getWorksheetIterator() as $sheet) {
1115
            // for all cells
1116 View Code Duplication
            foreach ($sheet->getCellCollection(false) as $cellID) {
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...
1117
                $cell = $sheet->getCell($cellID);
1118
                $cell->setXfIndex($map[$cell->getXfIndex()]);
0 ignored issues
show
Bug introduced by
The variable $map 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...
1119
            }
1120
1121
            // for all row dimensions
1122
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1123
                if ($rowDimension->getXfIndex() !== null) {
1124
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1125
                }
1126
            }
1127
1128
            // for all column dimensions
1129
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1130
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1131
            }
1132
1133
            // also do garbage collection for all the sheets
1134
            $sheet->garbageCollect();
1135
        }
1136
    }
1137
1138
    /**
1139
     * Return the unique ID value assigned to this spreadsheet workbook
1140
     *
1141
     * @return string
1142
     */
1143
    public function getID()
1144
    {
1145
        return $this->uniqueID;
1146
    }
1147
}
1148