Completed
Push — develop ( d3e769...440bfe )
by Adrien
22:10
created

Spreadsheet::getActiveSheetIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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 56
    public function hasMacros()
118
    {
119 56
        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 56
    public function hasRibbon()
304
    {
305 56
        return $this->ribbonXMLData !== null;
306
    }
307
308
    /**
309
     * This workbook have additionnal object for the ribbon ?
310
     *
311
     * @return bool
312
     */
313 56
    public function hasRibbonBinObjects()
314
    {
315 56
        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 124
    public function sheetCodeNameExists($pSheetCodeName)
326
    {
327 124
        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 124 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 124
        $worksheetCount = count($this->workSheetCollection);
340 124
        for ($i = 0; $i < $worksheetCount; ++$i) {
341 41
            if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
342 38
                return $this->workSheetCollection[$i];
343
            }
344
        }
345
346 124
        return null;
347
    }
348
349
    /**
350
     * Create a new PhpSpreadsheet with one Worksheet.
351
     */
352 124
    public function __construct()
353
    {
354 124
        $this->uniqueID = uniqid('', true);
355 124
        $this->calculationEngine = new Calculation($this);
356
357
        // Initialise worksheet collection and add one worksheet
358 124
        $this->workSheetCollection = [];
359 124
        $this->workSheetCollection[] = new Worksheet($this);
360 124
        $this->activeSheetIndex = 0;
361
362
        // Create document properties
363 124
        $this->properties = new Document\Properties();
364
365
        // Create document security
366 124
        $this->security = new Document\Security();
367
368
        // Set named ranges
369 124
        $this->namedRanges = [];
370
371
        // Create the cellXf supervisor
372 124
        $this->cellXfSupervisor = new Style(true);
373 124
        $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 124
        $this->addCellXf(new Style());
377 124
        $this->addCellStyleXf(new Style());
378 124
    }
379
380
    /**
381
     * Code to execute when this worksheet is unset().
382
     */
383 1
    public function __destruct()
384
    {
385 1
        $this->calculationEngine = null;
386 1
        $this->disconnectWorksheets();
387 1
    }
388
389
    /**
390
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
391
     * typically so that the PhpSpreadsheet object can be unset.
392
     */
393 1
    public function disconnectWorksheets()
394
    {
395 1
        $worksheet = null;
396 1
        foreach ($this->workSheetCollection as $k => &$worksheet) {
397 1
            $worksheet->disconnectCells();
398 1
            $this->workSheetCollection[$k] = null;
399
        }
400 1
        unset($worksheet);
401 1
        $this->workSheetCollection = [];
402 1
    }
403
404
    /**
405
     * Return the calculation engine for this worksheet.
406
     *
407
     * @return Calculation
408
     */
409 124
    public function getCalculationEngine()
410
    {
411 124
        return $this->calculationEngine;
412
    }
413
414
    /**
415
     * Get properties.
416
     *
417
     * @return Document\Properties
418
     */
419 86
    public function getProperties()
420
    {
421 86
        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 56
    public function getSecurity()
440
    {
441 56
        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 111
    public function getActiveSheet()
462
    {
463 111
        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 55
    public function createSheet($sheetIndex = null)
476
    {
477 55
        $newSheet = new Worksheet($this);
478 55
        $this->addSheet($newSheet, $sheetIndex);
479
480 55
        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 124
    public function sheetNameExists($pSheetName)
491
    {
492 124
        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 56
    public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
506
    {
507 56
        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 56
        if ($iSheetIndex === null) {
514 56
            if ($this->activeSheetIndex < 0) {
515 31
                $this->activeSheetIndex = 0;
516
            }
517 56
            $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 56
        if ($pSheet->getParent() === null) {
534
            $pSheet->rebindParent($this);
535
        }
536
537 56
        return $pSheet;
538
    }
539
540
    /**
541
     * Remove sheet by index.
542
     *
543
     * @param int $pIndex Active sheet index
544
     *
545
     * @throws Exception
546
     */
547 31
    public function removeSheetByIndex($pIndex)
548
    {
549 31
        $numSheets = count($this->workSheetCollection);
550 31
        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 31
        array_splice($this->workSheetCollection, $pIndex, 1);
556
557
        // Adjust active sheet index if necessary
558 31
        if (($this->activeSheetIndex >= $pIndex) &&
559 31
            ($pIndex > count($this->workSheetCollection) - 1)) {
560 31
            --$this->activeSheetIndex;
561
        }
562 31
    }
563
564
    /**
565
     * Get sheet by index.
566
     *
567
     * @param int $pIndex Sheet index
568
     *
569
     * @throws Exception
570
     *
571
     * @return Worksheet
572
     */
573 124
    public function getSheet($pIndex)
574
    {
575 124
        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 124
        return $this->workSheetCollection[$pIndex];
584
    }
585
586
    /**
587
     * Get all sheets.
588
     *
589
     * @return Worksheet[]
590
     */
591 39
    public function getAllSheets()
592
    {
593 39
        return $this->workSheetCollection;
594
    }
595
596
    /**
597
     * Get sheet by name.
598
     *
599
     * @param string $pName Sheet name
600
     *
601
     * @return Worksheet
602
     */
603 124 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 124
        $worksheetCount = count($this->workSheetCollection);
606 124
        for ($i = 0; $i < $worksheetCount; ++$i) {
607 82
            if ($this->workSheetCollection[$i]->getTitle() === $pName) {
608 64
                return $this->workSheetCollection[$i];
609
            }
610
        }
611
612 124
        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 90
    public function getIndex(Worksheet $pSheet)
625
    {
626 90
        foreach ($this->workSheetCollection as $key => $value) {
627 90
            if ($value->getHashCode() == $pSheet->getHashCode()) {
628 90
                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 84
    public function getSheetCount()
669
    {
670 84
        return count($this->workSheetCollection);
671
    }
672
673
    /**
674
     * Get active sheet index.
675
     *
676
     * @return int Active sheet index
677
     */
678 58
    public function getActiveSheetIndex()
679
    {
680 58
        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 84
    public function setActiveSheetIndex($pIndex)
693
    {
694 84
        $numSheets = count($this->workSheetCollection);
695
696 84
        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 84
        $this->activeSheetIndex = $pIndex;
702
703 84
        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 3
    public function setActiveSheetIndexByName($pValue)
716
    {
717 3
        if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
718 3
            $this->setActiveSheetIndex($this->getIndex($worksheet));
719
720 3
            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 8
    public function getSheetNames()
732
    {
733 8
        $returnValue = [];
734 8
        $worksheetCount = $this->getSheetCount();
735 8
        for ($i = 0; $i < $worksheetCount; ++$i) {
736 8
            $returnValue[] = $this->getSheet($i)->getTitle();
737
        }
738
739 8
        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 1
    public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
753
    {
754 1
        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 1
        $countCellXfs = count($this->cellXfCollection);
760
761
        // copy all the shared cellXfs from the external workbook and append them to the current
762 1
        foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
763 1
            $this->addCellXf(clone $cellXf);
764
        }
765
766
        // move sheet to this workbook
767 1
        $pSheet->rebindParent($this);
768
769
        // update the cellXfs
770 1
        foreach ($pSheet->getCoordinates(false) as $coordinate) {
771 1
            $cell = $pSheet->getCell($coordinate);
772 1
            $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
773
        }
774
775 1
        return $this->addSheet($pSheet, $iSheetIndex);
776
    }
777
778
    /**
779
     * Get named ranges.
780
     *
781
     * @return NamedRange[]
782
     */
783 62
    public function getNamedRanges()
784
    {
785 62
        return $this->namedRanges;
786
    }
787
788
    /**
789
     * Add named range.
790
     *
791
     * @param NamedRange $namedRange
792
     *
793
     * @return bool
794
     */
795 4
    public function addNamedRange(NamedRange $namedRange)
796
    {
797 4
        if ($namedRange->getScope() == null) {
798
            // global scope
799 4
            $this->namedRanges[$namedRange->getName()] = $namedRange;
800
        } else {
801
            // local scope
802
            $this->namedRanges[$namedRange->getScope()->getTitle() . '!' . $namedRange->getName()] = $namedRange;
803
        }
804
805 4
        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 4
    public function getNamedRange($namedRange, Worksheet $pSheet = null)
817
    {
818 4
        $returnValue = null;
819
820 4
        if ($namedRange != '' && ($namedRange !== null)) {
821
            // first look for global defined name
822 4
            if (isset($this->namedRanges[$namedRange])) {
823 2
                $returnValue = $this->namedRanges[$namedRange];
824
            }
825
826
            // then look for local defined name (has priority over global defined name if both names exist)
827 4
            if (($pSheet !== null) && isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
828
                $returnValue = $this->namedRanges[$pSheet->getTitle() . '!' . $namedRange];
829
            }
830
        }
831
832 4
        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 1
    public function removeNamedRange($namedRange, Worksheet $pSheet = null)
844
    {
845 1
        if ($pSheet === null) {
846
            if (isset($this->namedRanges[$namedRange])) {
847
                unset($this->namedRanges[$namedRange]);
848
            }
849
        } else {
850 1
            if (isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
851
                unset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]);
852
            }
853
        }
854
855 1
        return $this;
856
    }
857
858
    /**
859
     * Get worksheet iterator.
860
     *
861
     * @return Worksheet\Iterator
862
     */
863 67
    public function getWorksheetIterator()
864
    {
865 67
        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 77
    public function getCellXfCollection()
904
    {
905 77
        return $this->cellXfCollection;
906
    }
907
908
    /**
909
     * Get cellXf by index.
910
     *
911
     * @param int $pIndex
912
     *
913
     * @return Style
914
     */
915 71
    public function getCellXfByIndex($pIndex)
916
    {
917 71
        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 48
    public function getCellXfByHashCode($pValue)
928
    {
929 48
        foreach ($this->cellXfCollection as $cellXf) {
930 48
            if ($cellXf->getHashCode() == $pValue) {
931 48
                return $cellXf;
932
            }
933
        }
934
935 48
        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 62
    public function getDefaultStyle()
958
    {
959 62
        if (isset($this->cellXfCollection[0])) {
960 62
            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 124
    public function addCellXf(Style $style)
972
    {
973 124
        $this->cellXfCollection[] = $style;
974 124
        $style->setIndex(count($this->cellXfCollection) - 1);
975 124
    }
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 28
    public function removeCellXfByIndex($pIndex)
985
    {
986 28
        if ($pIndex > count($this->cellXfCollection) - 1) {
987
            throw new Exception('CellXf index is out of bounds.');
988
        }
989
990
        // first remove the cellXf
991 28
        array_splice($this->cellXfCollection, $pIndex, 1);
992
993
        // then update cellXf indexes for cells
994 28
        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 28
    }
1008
1009
    /**
1010
     * Get the cellXf supervisor.
1011
     *
1012
     * @return Style
1013
     */
1014 46
    public function getCellXfSupervisor()
1015
    {
1016 46
        return $this->cellXfSupervisor;
1017
    }
1018
1019
    /**
1020
     * Get the workbook collection of cellStyleXfs.
1021
     *
1022
     * @return Style[]
1023
     */
1024 39
    public function getCellStyleXfCollection()
1025
    {
1026 39
        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 124
    public function addCellStyleXf(Style $pStyle)
1065
    {
1066 124
        $this->cellStyleXfCollection[] = $pStyle;
1067 124
        $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
1068 124
    }
1069
1070
    /**
1071
     * Remove cellStyleXf by index.
1072
     *
1073
     * @param int $pIndex Index to cellXf
1074
     *
1075
     * @throws Exception
1076
     */
1077 28
    public function removeCellStyleXfByIndex($pIndex)
1078
    {
1079 28
        if ($pIndex > count($this->cellStyleXfCollection) - 1) {
1080
            throw new Exception('CellStyleXf index is out of bounds.');
1081
        }
1082 28
        array_splice($this->cellStyleXfCollection, $pIndex, 1);
1083 28
    }
1084
1085
    /**
1086
     * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1087
     * and columns in the workbook.
1088
     */
1089 62
    public function garbageCollect()
1090
    {
1091
        // how many references are there to each cellXf ?
1092 62
        $countReferencesCellXf = [];
1093 62
        foreach ($this->cellXfCollection as $index => $cellXf) {
1094 62
            $countReferencesCellXf[$index] = 0;
1095
        }
1096
1097 62
        foreach ($this->getWorksheetIterator() as $sheet) {
1098
            // from cells
1099 62 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 62
                $cell = $sheet->getCell($coordinate);
1101 62
                ++$countReferencesCellXf[$cell->getXfIndex()];
1102
            }
1103
1104
            // from row dimensions
1105 62
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1106 39
                if ($rowDimension->getXfIndex() !== null) {
1107 39
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1108
                }
1109
            }
1110
1111
            // from column dimensions
1112 62
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1113 62
                ++$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 62
        $countNeededCellXfs = 0;
1120 62
        foreach ($this->cellXfCollection as $index => $cellXf) {
1121 62
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1122 62
                ++$countNeededCellXfs;
1123
            } else {
1124 15
                unset($this->cellXfCollection[$index]);
1125
            }
1126 62
            $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 62
        $this->cellXfCollection = array_values($this->cellXfCollection);
1129
1130
        // update the index for all cellXfs
1131 62
        foreach ($this->cellXfCollection as $i => $cellXf) {
1132 62
            $cellXf->setIndex($i);
1133
        }
1134
1135
        // make sure there is always at least one cellXf (there should be)
1136 62
        if (empty($this->cellXfCollection)) {
1137
            $this->cellXfCollection[] = new Style();
1138
        }
1139
1140
        // update the xfIndex for all cells, row dimensions, column dimensions
1141 62
        foreach ($this->getWorksheetIterator() as $sheet) {
1142
            // for all cells
1143 62 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 62
                $cell = $sheet->getCell($coordinate);
1145 62
                $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 62
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1150 39
                if ($rowDimension->getXfIndex() !== null) {
1151 39
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1152
                }
1153
            }
1154
1155
            // for all column dimensions
1156 62
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1157 26
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1158
            }
1159
1160
            // also do garbage collection for all the sheets
1161 62
            $sheet->garbageCollect();
1162
        }
1163 62
    }
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