Completed
Push — develop ( 4fd8e7...d3e769 )
by Adrien
14:12 queued 07:04
created

Spreadsheet::getSheetCount()   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 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
class Spreadsheet
6
{
7
    /**
8
     * Unique ID.
9
     *
10
     * @var string
11
     */
12
    private $uniqueID;
13
14
    /**
15
     * Document properties.
16
     *
17
     * @var Document\Properties
18
     */
19
    private $properties;
20
21
    /**
22
     * Document security.
23
     *
24
     * @var Document\Security
25
     */
26
    private $security;
27
28
    /**
29
     * Collection of Worksheet objects.
30
     *
31
     * @var Worksheet[]
32
     */
33
    private $workSheetCollection = [];
34
35
    /**
36
     * Calculation Engine.
37
     *
38
     * @var Calculation
39
     */
40
    private $calculationEngine;
41
42
    /**
43
     * Active sheet index.
44
     *
45
     * @var int
46
     */
47
    private $activeSheetIndex = 0;
48
49
    /**
50
     * Named ranges.
51
     *
52
     * @var NamedRange[]
53
     */
54
    private $namedRanges = [];
55
56
    /**
57
     * CellXf supervisor.
58
     *
59
     * @var Style
60
     */
61
    private $cellXfSupervisor;
62
63
    /**
64
     * CellXf collection.
65
     *
66
     * @var Style[]
67
     */
68
    private $cellXfCollection = [];
69
70
    /**
71
     * CellStyleXf collection.
72
     *
73
     * @var Style[]
74
     */
75
    private $cellStyleXfCollection = [];
76
77
    /**
78
     * hasMacros : this workbook have macros ?
79
     *
80
     * @var bool
81
     */
82
    private $hasMacros = false;
83
84
    /**
85
     * macrosCode : all macros code (the vbaProject.bin file, this include form, code,  etc.), null if no macro.
86
     *
87
     * @var binary
88
     */
89
    private $macrosCode;
90
    /**
91
     * macrosCertificate : if macros are signed, contains vbaProjectSignature.bin file, null if not signed.
92
     *
93
     * @var binary
94
     */
95
    private $macrosCertificate;
96
97
    /**
98
     * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
99
     *
100
     * @var null|string
101
     */
102
    private $ribbonXMLData;
103
104
    /**
105
     * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
106
     * ignored if $ribbonXMLData is null.
107
     *
108
     * @var null|array
109
     */
110
    private $ribbonBinObjects;
111
112
    /**
113
     * The workbook has macros ?
114
     *
115
     * @return bool
116
     */
117
    public function hasMacros()
118
    {
119
        return $this->hasMacros;
120
    }
121
122
    /**
123
     * Define if a workbook has macros.
124
     *
125
     * @param bool $hasMacros true|false
126
     */
127
    public function setHasMacros($hasMacros)
128
    {
129
        $this->hasMacros = (bool) $hasMacros;
130
    }
131
132
    /**
133
     * Set the macros code.
134
     *
135
     * @param string $macroCode string|null
136
     */
137
    public function setMacrosCode($macroCode)
138
    {
139
        $this->macrosCode = $macroCode;
0 ignored issues
show
Documentation Bug introduced by
It seems like $macroCode of type string is incompatible with the declared type object<PhpOffice\PhpSpreadsheet\binary> of property $macrosCode.

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...
140
        $this->setHasMacros($macroCode !== null);
141
    }
142
143
    /**
144
     * Return the macros code.
145
     *
146
     * @return null|string
147
     */
148
    public function getMacrosCode()
149
    {
150
        return $this->macrosCode;
151
    }
152
153
    /**
154
     * Set the macros certificate.
155
     *
156
     * @param null|string $certificate
157
     */
158
    public function setMacrosCertificate($certificate)
159
    {
160
        $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...
161
    }
162
163
    /**
164
     * Is the project signed ?
165
     *
166
     * @return bool true|false
167
     */
168
    public function hasMacrosCertificate()
169
    {
170
        return $this->macrosCertificate !== null;
171
    }
172
173
    /**
174
     * Return the macros certificate.
175
     *
176
     * @return null|string
177
     */
178
    public function getMacrosCertificate()
179
    {
180
        return $this->macrosCertificate;
181
    }
182
183
    /**
184
     * Remove all macros, certificate from spreadsheet.
185
     */
186
    public function discardMacros()
187
    {
188
        $this->hasMacros = false;
189
        $this->macrosCode = null;
190
        $this->macrosCertificate = null;
191
    }
192
193
    /**
194
     * set ribbon XML data.
195
     *
196
     * @param null|mixed $target
197
     * @param null|mixed $xmlData
198
     */
199
    public function setRibbonXMLData($target, $xmlData)
200
    {
201
        if ($target !== null && $xmlData !== null) {
202
            $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...
203
        } else {
204
            $this->ribbonXMLData = null;
205
        }
206
    }
207
208
    /**
209
     * retrieve ribbon XML Data.
210
     *
211
     * return string|null|array
212
     *
213
     * @param string $what
214
     *
215
     * @return string
216
     */
217
    public function getRibbonXMLData($what = 'all') //we need some constants here...
218
    {
219
        $returnData = null;
220
        $what = strtolower($what);
221
        switch ($what) {
222
            case 'all':
223
                $returnData = $this->ribbonXMLData;
224
225
                break;
226
            case 'target':
227
            case 'data':
228
                if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) {
229
                    $returnData = $this->ribbonXMLData[$what];
230
                }
231
232
                break;
233
        }
234
235
        return $returnData;
236
    }
237
238
    /**
239
     * store binaries ribbon objects (pictures).
240
     *
241
     * @param null|mixed $BinObjectsNames
242
     * @param null|mixed $BinObjectsData
243
     */
244
    public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData)
245
    {
246
        if ($BinObjectsNames !== null && $BinObjectsData !== null) {
247
            $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
248
        } else {
249
            $this->ribbonBinObjects = null;
250
        }
251
    }
252
253
    /**
254
     * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
255
     *
256
     * @param mixed $ThePath
257
     */
258
    private function getExtensionOnly($ThePath)
259
    {
260
        return pathinfo($ThePath, PATHINFO_EXTENSION);
261
    }
262
263
    /**
264
     * retrieve Binaries Ribbon Objects.
265
     *
266
     * @param mixed $what
267
     */
268
    public function getRibbonBinObjects($what = 'all')
269
    {
270
        $ReturnData = null;
271
        $what = strtolower($what);
272
        switch ($what) {
273
            case 'all':
274
                return $this->ribbonBinObjects;
275
                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...
276
            case 'names':
277
            case 'data':
278
                if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
279
                    $ReturnData = $this->ribbonBinObjects[$what];
280
                }
281
282
                break;
283
            case 'types':
284
                if (is_array($this->ribbonBinObjects) &&
285
                    isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])) {
286
                    $tmpTypes = array_keys($this->ribbonBinObjects['data']);
287
                    $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
288
                } else {
289
                    $ReturnData = []; // the caller want an array... not null if empty
290
                }
291
292
                break;
293
        }
294
295
        return $ReturnData;
296
    }
297
298
    /**
299
     * This workbook have a custom UI ?
300
     *
301
     * @return bool
302
     */
303
    public function hasRibbon()
304
    {
305
        return $this->ribbonXMLData !== null;
306
    }
307
308
    /**
309
     * This workbook have additionnal object for the ribbon ?
310
     *
311
     * @return bool
312
     */
313
    public function hasRibbonBinObjects()
314
    {
315
        return $this->ribbonBinObjects !== null;
316
    }
317
318
    /**
319
     * Check if a sheet with a specified code name already exists.
320
     *
321
     * @param string $pSheetCodeName Name of the worksheet to check
322
     *
323
     * @return bool
324
     */
325
    public function sheetCodeNameExists($pSheetCodeName)
326
    {
327
        return $this->getSheetByCodeName($pSheetCodeName) !== null;
328
    }
329
330
    /**
331
     * Get sheet by code name. Warning : sheet don't have always a code name !
332
     *
333
     * @param string $pName Sheet name
334
     *
335
     * @return Worksheet
336
     */
337 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...
338
    {
339
        $worksheetCount = count($this->workSheetCollection);
340
        for ($i = 0; $i < $worksheetCount; ++$i) {
341
            if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
342
                return $this->workSheetCollection[$i];
343
            }
344
        }
345
346
        return null;
347
    }
348
349
    /**
350
     * Create a new PhpSpreadsheet with one Worksheet.
351
     */
352 76
    public function __construct()
353
    {
354 76
        $this->uniqueID = uniqid('', true);
355 76
        $this->calculationEngine = new Calculation($this);
356
357
        // Initialise worksheet collection and add one worksheet
358
        $this->workSheetCollection = [];
359
        $this->workSheetCollection[] = new Worksheet($this);
360
        $this->activeSheetIndex = 0;
361
362
        // Create document properties
363
        $this->properties = new Document\Properties();
364
365
        // Create document security
366
        $this->security = new Document\Security();
367
368
        // Set named ranges
369
        $this->namedRanges = [];
370
371
        // Create the cellXf supervisor
372
        $this->cellXfSupervisor = new Style(true);
373
        $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\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...
374
375
        // Create the default style
376
        $this->addCellXf(new Style());
377
        $this->addCellStyleXf(new Style());
378
    }
379
380
    /**
381
     * Code to execute when this worksheet is unset().
382
     */
383
    public function __destruct()
384
    {
385
        $this->calculationEngine = null;
386
        $this->disconnectWorksheets();
387
    }
388
389
    /**
390
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
391
     * typically so that the PhpSpreadsheet object can be unset.
392
     */
393
    public function disconnectWorksheets()
394
    {
395
        $worksheet = null;
396
        foreach ($this->workSheetCollection as $k => &$worksheet) {
397
            $worksheet->disconnectCells();
398
            $this->workSheetCollection[$k] = null;
399
        }
400
        unset($worksheet);
401
        $this->workSheetCollection = [];
402
    }
403
404
    /**
405
     * Return the calculation engine for this worksheet.
406
     *
407
     * @return Calculation
408
     */
409
    public function getCalculationEngine()
410
    {
411
        return $this->calculationEngine;
412
    }
413
414
    /**
415
     * Get properties.
416
     *
417
     * @return Document\Properties
418
     */
419
    public function getProperties()
420
    {
421
        return $this->properties;
422
    }
423
424
    /**
425
     * Set properties.
426
     *
427
     * @param Document\Properties $pValue
428
     */
429
    public function setProperties(Document\Properties $pValue)
430
    {
431
        $this->properties = $pValue;
432
    }
433
434
    /**
435
     * Get security.
436
     *
437
     * @return Document\Security
438
     */
439
    public function getSecurity()
440
    {
441
        return $this->security;
442
    }
443
444
    /**
445
     * Set security.
446
     *
447
     * @param Document\Security $pValue
448
     */
449
    public function setSecurity(Document\Security $pValue)
450
    {
451
        $this->security = $pValue;
452
    }
453
454
    /**
455
     * Get active sheet.
456
     *
457
     * @throws Exception
458
     *
459
     * @return Worksheet
460
     */
461
    public function getActiveSheet()
462
    {
463
        return $this->getSheet($this->activeSheetIndex);
464
    }
465
466
    /**
467
     * Create sheet and add it to this workbook.
468
     *
469
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
470
     *
471
     * @throws Exception
472
     *
473
     * @return Worksheet
474
     */
475
    public function createSheet($sheetIndex = null)
476
    {
477
        $newSheet = new Worksheet($this);
478
        $this->addSheet($newSheet, $sheetIndex);
479
480
        return $newSheet;
481
    }
482
483
    /**
484
     * Check if a sheet with a specified name already exists.
485
     *
486
     * @param string $pSheetName Name of the worksheet to check
487
     *
488
     * @return bool
489
     */
490
    public function sheetNameExists($pSheetName)
491
    {
492
        return $this->getSheetByName($pSheetName) !== null;
493
    }
494
495
    /**
496
     * Add sheet.
497
     *
498
     * @param Worksheet $pSheet
499
     * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
500
     *
501
     * @throws Exception
502
     *
503
     * @return Worksheet
504
     */
505
    public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
506
    {
507
        if ($this->sheetNameExists($pSheet->getTitle())) {
508
            throw new Exception(
509
                "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first."
510
            );
511
        }
512
513
        if ($iSheetIndex === null) {
514
            if ($this->activeSheetIndex < 0) {
515
                $this->activeSheetIndex = 0;
516
            }
517
            $this->workSheetCollection[] = $pSheet;
518
        } else {
519
            // Insert the sheet at the requested index
520
            array_splice(
521
                $this->workSheetCollection,
522
                $iSheetIndex,
523
                0,
524
                [$pSheet]
525
            );
526
527
            // Adjust active sheet index if necessary
528
            if ($this->activeSheetIndex >= $iSheetIndex) {
529
                ++$this->activeSheetIndex;
530
            }
531
        }
532
533
        if ($pSheet->getParent() === null) {
534
            $pSheet->rebindParent($this);
535
        }
536
537
        return $pSheet;
538
    }
539
540
    /**
541
     * Remove sheet by index.
542
     *
543
     * @param int $pIndex Active sheet index
544
     *
545
     * @throws Exception
546
     */
547
    public function removeSheetByIndex($pIndex)
548
    {
549
        $numSheets = count($this->workSheetCollection);
550
        if ($pIndex > $numSheets - 1) {
551
            throw new Exception(
552
                "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
553
            );
554
        }
555
        array_splice($this->workSheetCollection, $pIndex, 1);
556
557
        // Adjust active sheet index if necessary
558
        if (($this->activeSheetIndex >= $pIndex) &&
559
            ($pIndex > count($this->workSheetCollection) - 1)) {
560
            --$this->activeSheetIndex;
561
        }
562
    }
563
564
    /**
565
     * Get sheet by index.
566
     *
567
     * @param int $pIndex Sheet index
568
     *
569
     * @throws Exception
570
     *
571
     * @return Worksheet
572
     */
573
    public function getSheet($pIndex)
574
    {
575
        if (!isset($this->workSheetCollection[$pIndex])) {
576
            $numSheets = $this->getSheetCount();
577
578
            throw new Exception(
579
                "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}."
580
            );
581
        }
582
583
        return $this->workSheetCollection[$pIndex];
584
    }
585
586
    /**
587
     * Get all sheets.
588
     *
589
     * @return Worksheet[]
590
     */
591
    public function getAllSheets()
592
    {
593
        return $this->workSheetCollection;
594
    }
595
596
    /**
597
     * Get sheet by name.
598
     *
599
     * @param string $pName Sheet name
600
     *
601
     * @return Worksheet
602
     */
603 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...
604
    {
605
        $worksheetCount = count($this->workSheetCollection);
606
        for ($i = 0; $i < $worksheetCount; ++$i) {
607
            if ($this->workSheetCollection[$i]->getTitle() === $pName) {
608
                return $this->workSheetCollection[$i];
609
            }
610
        }
611
612
        return null;
613
    }
614
615
    /**
616
     * Get index for sheet.
617
     *
618
     * @param Worksheet $pSheet
619
     *
620
     * @throws Exception
621
     *
622
     * @return int index
623
     */
624
    public function getIndex(Worksheet $pSheet)
625
    {
626
        foreach ($this->workSheetCollection as $key => $value) {
627
            if ($value->getHashCode() == $pSheet->getHashCode()) {
628
                return $key;
629
            }
630
        }
631
632
        throw new Exception('Sheet does not exist.');
633
    }
634
635
    /**
636
     * Set index for sheet by sheet name.
637
     *
638
     * @param string $sheetName Sheet name to modify index for
639
     * @param int $newIndex New index for the sheet
640
     *
641
     * @throws Exception
642
     *
643
     * @return int New sheet index
644
     */
645
    public function setIndexByName($sheetName, $newIndex)
646
    {
647
        $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...
648
        $pSheet = array_splice(
649
            $this->workSheetCollection,
650
            $oldIndex,
651
            1
652
        );
653
        array_splice(
654
            $this->workSheetCollection,
655
            $newIndex,
656
            0,
657
            $pSheet
658
        );
659
660
        return $newIndex;
661
    }
662
663
    /**
664
     * Get sheet count.
665
     *
666
     * @return int
667
     */
668
    public function getSheetCount()
669
    {
670
        return count($this->workSheetCollection);
671
    }
672
673
    /**
674
     * Get active sheet index.
675
     *
676
     * @return int Active sheet index
677
     */
678
    public function getActiveSheetIndex()
679
    {
680
        return $this->activeSheetIndex;
681
    }
682
683
    /**
684
     * Set active sheet index.
685
     *
686
     * @param int $pIndex Active sheet index
687
     *
688
     * @throws Exception
689
     *
690
     * @return Worksheet
691
     */
692
    public function setActiveSheetIndex($pIndex)
693
    {
694
        $numSheets = count($this->workSheetCollection);
695
696
        if ($pIndex > $numSheets - 1) {
697
            throw new Exception(
698
                "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
699
            );
700
        }
701
        $this->activeSheetIndex = $pIndex;
702
703
        return $this->getActiveSheet();
704
    }
705
706
    /**
707
     * Set active sheet index by name.
708
     *
709
     * @param string $pValue Sheet title
710
     *
711
     * @throws Exception
712
     *
713
     * @return Worksheet
714
     */
715
    public function setActiveSheetIndexByName($pValue)
716
    {
717
        if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
718
            $this->setActiveSheetIndex($this->getIndex($worksheet));
719
720
            return $worksheet;
721
        }
722
723
        throw new Exception('Workbook does not contain sheet:' . $pValue);
724
    }
725
726
    /**
727
     * Get sheet names.
728
     *
729
     * @return string[]
730
     */
731
    public function getSheetNames()
732
    {
733
        $returnValue = [];
734
        $worksheetCount = $this->getSheetCount();
735
        for ($i = 0; $i < $worksheetCount; ++$i) {
736
            $returnValue[] = $this->getSheet($i)->getTitle();
737
        }
738
739
        return $returnValue;
740
    }
741
742
    /**
743
     * Add external sheet.
744
     *
745
     * @param Worksheet $pSheet External sheet to add
746
     * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
747
     *
748
     * @throws Exception
749
     *
750
     * @return Worksheet
751
     */
752
    public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
753
    {
754
        if ($this->sheetNameExists($pSheet->getTitle())) {
755
            throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first.");
756
        }
757
758
        // count how many cellXfs there are in this workbook currently, we will need this below
759
        $countCellXfs = count($this->cellXfCollection);
760
761
        // copy all the shared cellXfs from the external workbook and append them to the current
762
        foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
763
            $this->addCellXf(clone $cellXf);
764
        }
765
766
        // move sheet to this workbook
767
        $pSheet->rebindParent($this);
768
769
        // update the cellXfs
770
        foreach ($pSheet->getCoordinates(false) as $coordinate) {
771
            $cell = $pSheet->getCell($coordinate);
772
            $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
773
        }
774
775
        return $this->addSheet($pSheet, $iSheetIndex);
776
    }
777
778
    /**
779
     * Get named ranges.
780
     *
781
     * @return NamedRange[]
782
     */
783
    public function getNamedRanges()
784
    {
785
        return $this->namedRanges;
786
    }
787
788
    /**
789
     * Add named range.
790
     *
791
     * @param NamedRange $namedRange
792
     *
793
     * @return bool
794
     */
795
    public function addNamedRange(NamedRange $namedRange)
796
    {
797
        if ($namedRange->getScope() == null) {
798
            // global scope
799
            $this->namedRanges[$namedRange->getName()] = $namedRange;
800
        } else {
801
            // local scope
802
            $this->namedRanges[$namedRange->getScope()->getTitle() . '!' . $namedRange->getName()] = $namedRange;
803
        }
804
805
        return true;
806
    }
807
808
    /**
809
     * Get named range.
810
     *
811
     * @param string $namedRange
812
     * @param null|Worksheet $pSheet Scope. Use null for global scope
813
     *
814
     * @return null|NamedRange
815
     */
816
    public function getNamedRange($namedRange, Worksheet $pSheet = null)
817
    {
818
        $returnValue = null;
819
820
        if ($namedRange != '' && ($namedRange !== null)) {
821
            // first look for global defined name
822
            if (isset($this->namedRanges[$namedRange])) {
823
                $returnValue = $this->namedRanges[$namedRange];
824
            }
825
826
            // then look for local defined name (has priority over global defined name if both names exist)
827
            if (($pSheet !== null) && isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
828
                $returnValue = $this->namedRanges[$pSheet->getTitle() . '!' . $namedRange];
829
            }
830
        }
831
832
        return $returnValue;
833
    }
834
835
    /**
836
     * Remove named range.
837
     *
838
     * @param string $namedRange
839
     * @param null|Worksheet $pSheet scope: use null for global scope
840
     *
841
     * @return Spreadsheet
842
     */
843
    public function removeNamedRange($namedRange, Worksheet $pSheet = null)
844
    {
845
        if ($pSheet === null) {
846
            if (isset($this->namedRanges[$namedRange])) {
847
                unset($this->namedRanges[$namedRange]);
848
            }
849
        } else {
850
            if (isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
851
                unset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]);
852
            }
853
        }
854
855
        return $this;
856
    }
857
858
    /**
859
     * Get worksheet iterator.
860
     *
861
     * @return Worksheet\Iterator
862
     */
863
    public function getWorksheetIterator()
864
    {
865
        return new Worksheet\Iterator($this);
866
    }
867
868
    /**
869
     * Copy workbook (!= clone!).
870
     *
871
     * @return Spreadsheet
872
     */
873
    public function copy()
874
    {
875
        $copied = clone $this;
876
877
        $worksheetCount = count($this->workSheetCollection);
878
        for ($i = 0; $i < $worksheetCount; ++$i) {
879
            $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
880
            $this->workSheetCollection[$i]->rebindParent($this);
881
        }
882
883
        return $copied;
884
    }
885
886
    /**
887
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
888
     */
889
    public function __clone()
890
    {
891
        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...
892
            if (is_object($val) || (is_array($val))) {
893
                $this->{$key} = unserialize(serialize($val));
894
            }
895
        }
896
    }
897
898
    /**
899
     * Get the workbook collection of cellXfs.
900
     *
901
     * @return Style[]
902
     */
903
    public function getCellXfCollection()
904
    {
905
        return $this->cellXfCollection;
906
    }
907
908
    /**
909
     * Get cellXf by index.
910
     *
911
     * @param int $pIndex
912
     *
913
     * @return Style
914
     */
915
    public function getCellXfByIndex($pIndex)
916
    {
917
        return $this->cellXfCollection[$pIndex];
918
    }
919
920
    /**
921
     * Get cellXf by hash code.
922
     *
923
     * @param string $pValue
924
     *
925
     * @return false|Style
926
     */
927
    public function getCellXfByHashCode($pValue)
928
    {
929
        foreach ($this->cellXfCollection as $cellXf) {
930
            if ($cellXf->getHashCode() == $pValue) {
931
                return $cellXf;
932
            }
933
        }
934
935
        return false;
936
    }
937
938
    /**
939
     * Check if style exists in style collection.
940
     *
941
     * @param Style $pCellStyle
942
     *
943
     * @return bool
944
     */
945
    public function cellXfExists($pCellStyle)
946
    {
947
        return in_array($pCellStyle, $this->cellXfCollection, true);
948
    }
949
950
    /**
951
     * Get default style.
952
     *
953
     * @throws Exception
954
     *
955
     * @return Style
956
     */
957
    public function getDefaultStyle()
958
    {
959
        if (isset($this->cellXfCollection[0])) {
960
            return $this->cellXfCollection[0];
961
        }
962
963
        throw new Exception('No default style found for this workbook');
964
    }
965
966
    /**
967
     * Add a cellXf to the workbook.
968
     *
969
     * @param Style $style
970
     */
971
    public function addCellXf(Style $style)
972
    {
973
        $this->cellXfCollection[] = $style;
974
        $style->setIndex(count($this->cellXfCollection) - 1);
975
    }
976
977
    /**
978
     * Remove cellXf by index. It is ensured that all cells get their xf index updated.
979
     *
980
     * @param int $pIndex Index to cellXf
981
     *
982
     * @throws Exception
983
     */
984
    public function removeCellXfByIndex($pIndex)
985
    {
986
        if ($pIndex > count($this->cellXfCollection) - 1) {
987
            throw new Exception('CellXf index is out of bounds.');
988
        }
989
990
        // first remove the cellXf
991
        array_splice($this->cellXfCollection, $pIndex, 1);
992
993
        // then update cellXf indexes for cells
994
        foreach ($this->workSheetCollection as $worksheet) {
995
            foreach ($worksheet->getCoordinates(false) as $coordinate) {
996
                $cell = $worksheet->getCell($coordinate);
997
                $xfIndex = $cell->getXfIndex();
998
                if ($xfIndex > $pIndex) {
999
                    // decrease xf index by 1
1000
                    $cell->setXfIndex($xfIndex - 1);
1001
                } elseif ($xfIndex == $pIndex) {
1002
                    // set to default xf index 0
1003
                    $cell->setXfIndex(0);
1004
                }
1005
            }
1006
        }
1007
    }
1008
1009
    /**
1010
     * Get the cellXf supervisor.
1011
     *
1012
     * @return Style
1013
     */
1014
    public function getCellXfSupervisor()
1015
    {
1016
        return $this->cellXfSupervisor;
1017
    }
1018
1019
    /**
1020
     * Get the workbook collection of cellStyleXfs.
1021
     *
1022
     * @return Style[]
1023
     */
1024
    public function getCellStyleXfCollection()
1025
    {
1026
        return $this->cellStyleXfCollection;
1027
    }
1028
1029
    /**
1030
     * Get cellStyleXf by index.
1031
     *
1032
     * @param int $pIndex Index to cellXf
1033
     *
1034
     * @return Style
1035
     */
1036
    public function getCellStyleXfByIndex($pIndex)
1037
    {
1038
        return $this->cellStyleXfCollection[$pIndex];
1039
    }
1040
1041
    /**
1042
     * Get cellStyleXf by hash code.
1043
     *
1044
     * @param string $pValue
1045
     *
1046
     * @return false|Style
1047
     */
1048
    public function getCellStyleXfByHashCode($pValue)
1049
    {
1050
        foreach ($this->cellStyleXfCollection as $cellStyleXf) {
1051
            if ($cellStyleXf->getHashCode() == $pValue) {
1052
                return $cellStyleXf;
1053
            }
1054
        }
1055
1056
        return false;
1057
    }
1058
1059
    /**
1060
     * Add a cellStyleXf to the workbook.
1061
     *
1062
     * @param Style $pStyle
1063
     */
1064
    public function addCellStyleXf(Style $pStyle)
1065
    {
1066
        $this->cellStyleXfCollection[] = $pStyle;
1067
        $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
1068
    }
1069
1070
    /**
1071
     * Remove cellStyleXf by index.
1072
     *
1073
     * @param int $pIndex Index to cellXf
1074
     *
1075
     * @throws Exception
1076
     */
1077
    public function removeCellStyleXfByIndex($pIndex)
1078
    {
1079
        if ($pIndex > count($this->cellStyleXfCollection) - 1) {
1080
            throw new Exception('CellStyleXf index is out of bounds.');
1081
        }
1082
        array_splice($this->cellStyleXfCollection, $pIndex, 1);
1083
    }
1084
1085
    /**
1086
     * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1087
     * and columns in the workbook.
1088
     */
1089
    public function garbageCollect()
1090
    {
1091
        // how many references are there to each cellXf ?
1092
        $countReferencesCellXf = [];
1093
        foreach ($this->cellXfCollection as $index => $cellXf) {
1094
            $countReferencesCellXf[$index] = 0;
1095
        }
1096
1097
        foreach ($this->getWorksheetIterator() as $sheet) {
1098
            // from cells
1099 View Code Duplication
            foreach ($sheet->getCoordinates(false) as $coordinate) {
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...
1100
                $cell = $sheet->getCell($coordinate);
1101
                ++$countReferencesCellXf[$cell->getXfIndex()];
1102
            }
1103
1104
            // from row dimensions
1105
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1106
                if ($rowDimension->getXfIndex() !== null) {
1107
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1108
                }
1109
            }
1110
1111
            // from column dimensions
1112
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1113
                ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1114
            }
1115
        }
1116
1117
        // remove cellXfs without references and create mapping so we can update xfIndex
1118
        // for all cells and columns
1119
        $countNeededCellXfs = 0;
1120
        foreach ($this->cellXfCollection as $index => $cellXf) {
1121
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1122
                ++$countNeededCellXfs;
1123
            } else {
1124
                unset($this->cellXfCollection[$index]);
1125
            }
1126
            $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...
1127
        }
1128
        $this->cellXfCollection = array_values($this->cellXfCollection);
1129
1130
        // update the index for all cellXfs
1131
        foreach ($this->cellXfCollection as $i => $cellXf) {
1132
            $cellXf->setIndex($i);
1133
        }
1134
1135
        // make sure there is always at least one cellXf (there should be)
1136
        if (empty($this->cellXfCollection)) {
1137
            $this->cellXfCollection[] = new Style();
1138
        }
1139
1140
        // update the xfIndex for all cells, row dimensions, column dimensions
1141
        foreach ($this->getWorksheetIterator() as $sheet) {
1142
            // for all cells
1143 View Code Duplication
            foreach ($sheet->getCoordinates(false) as $coordinate) {
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...
1144
                $cell = $sheet->getCell($coordinate);
1145
                $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...
1146
            }
1147
1148
            // for all row dimensions
1149
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1150
                if ($rowDimension->getXfIndex() !== null) {
1151
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1152
                }
1153
            }
1154
1155
            // for all column dimensions
1156
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1157
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1158
            }
1159
1160
            // also do garbage collection for all the sheets
1161
            $sheet->garbageCollect();
1162
        }
1163
    }
1164
1165
    /**
1166
     * Return the unique ID value assigned to this spreadsheet workbook.
1167
     *
1168
     * @return string
1169
     */
1170
    public function getID()
1171
    {
1172
        return $this->uniqueID;
1173
    }
1174
}
1175