Completed
Push — develop ( 672893...dd9590 )
by Adrien
28:52 queued 21:58
created

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