Completed
Push — develop ( e1f81f...539a89 )
by Adrien
16:11
created

Spreadsheet::setSecurity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpSpreadsheet;
4
5
/**
6
 * PhpSpreadsheet
7
 *
8
 * Copyright (c) 2006 - 2016 PhpSpreadsheet
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23
 *
24
 * @category   PHPSpreadsheet
25
 * @copyright  Copyright (c) 2006 PHPOffice (http://www.github.com/PHPOffice)
26
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
27
 * @version    ##VERSION##, ##DATE##
28
 */
29
class Spreadsheet
30
{
31
    /**
32
     * Unique ID
33
     *
34
     * @var string
35
     */
36
    private $uniqueID;
37
38
    /**
39
     * Document properties
40
     *
41
     * @var Document\Properties
42
     */
43
    private $properties;
44
45
    /**
46
     * Document security
47
     *
48
     * @var Document\Security
49
     */
50
    private $security;
51
52
    /**
53
     * Collection of Worksheet objects
54
     *
55
     * @var Worksheet[]
56
     */
57
    private $workSheetCollection = array();
58
59
    /**
60
     * Calculation Engine
61
     *
62
     * @var Calculation
63
     */
64
    private $calculationEngine;
65
66
    /**
67
     * Active sheet index
68
     *
69
     * @var integer
70
     */
71
    private $activeSheetIndex = 0;
72
73
    /**
74
     * Named ranges
75
     *
76
     * @var NamedRange[]
77
     */
78
    private $namedRanges = array();
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 = array();
93
94
    /**
95
     * CellStyleXf collection
96
     *
97
     * @var Style[]
98
     */
99
    private $cellStyleXfCollection = array();
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 true if workbook has macros, false if not
140
    */
141
    public function hasMacros()
142
    {
143
        return $this->hasMacros;
144
    }
145
146
    /**
147
    * Define if a workbook has macros
148
    *
149
    * @param boolean $hasMacros true|false
150
    */
151
    public function setHasMacros($hasMacros = false)
152
    {
153
        $this->hasMacros = (bool) $hasMacros;
154
    }
155
156
    /**
157
    * Set the macros code
158
    *
159
    * @param string $macroCode string|null
160
    */
161
    public function setMacrosCode($macroCode = null)
162
    {
163
        $this->macrosCode = $macroCode;
0 ignored issues
show
Documentation Bug introduced by
It seems like $macroCode can also be of type string. However, the property $macrosCode is declared as type object<PhpSpreadsheet\binary>. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
164
        $this->setHasMacros(!is_null($macroCode));
165
    }
166
167
    /**
168
    * Return the macros code
169
    *
170
    * @return string|null
171
    */
172
    public function getMacrosCode()
173
    {
174
        return $this->macrosCode;
175
    }
176
177
    /**
178
    * Set the macros certificate
179
    *
180
    * @param string|null $Certificate
0 ignored issues
show
Documentation introduced by
There is no parameter named $Certificate. Did you maybe mean $certificate?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
381
382
        // Create the default style
383
        $this->addCellXf(new Style);
384
        $this->addCellStyleXf(new Style);
385
    }
386
387
    /**
388
     * Code to execute when this worksheet is unset()
389
     *
390
     */
391
    public function __destruct()
392
    {
393
        $this->calculationEngine = null;
394
        $this->disconnectWorksheets();
395
    }
396
397
    /**
398
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
399
     *    typically so that the PhpSpreadsheet object can be unset
400
     *
401
     */
402
    public function disconnectWorksheets()
403
    {
404
        $worksheet = null;
405
        foreach ($this->workSheetCollection as $k => &$worksheet) {
406
            $worksheet->disconnectCells();
407
            $this->workSheetCollection[$k] = null;
408
        }
409
        unset($worksheet);
410
        $this->workSheetCollection = array();
411
    }
412
413
    /**
414
     * Return the calculation engine for this worksheet
415
     *
416
     * @return Calculation
417
     */
418
    public function getCalculationEngine()
419
    {
420
        return $this->calculationEngine;
421
    }    //    function getCellCacheController()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1072
                $cell = $sheet->getCell($cellID);
1073
                ++$countReferencesCellXf[$cell->getXfIndex()];
1074
            }
1075
1076
            // from row dimensions
1077
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1078
                if ($rowDimension->getXfIndex() !== null) {
1079
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1080
                }
1081
            }
1082
1083
            // from column dimensions
1084
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1085
                ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1086
            }
1087
        }
1088
1089
        // remove cellXfs without references and create mapping so we can update xfIndex
1090
        // for all cells and columns
1091
        $countNeededCellXfs = 0;
1092
        foreach ($this->cellXfCollection as $index => $cellXf) {
1093
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1094
                ++$countNeededCellXfs;
1095
            } else {
1096
                unset($this->cellXfCollection[$index]);
1097
            }
1098
            $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...
1099
        }
1100
        $this->cellXfCollection = array_values($this->cellXfCollection);
1101
1102
        // update the index for all cellXfs
1103
        foreach ($this->cellXfCollection as $i => $cellXf) {
1104
            $cellXf->setIndex($i);
1105
        }
1106
1107
        // make sure there is always at least one cellXf (there should be)
1108
        if (empty($this->cellXfCollection)) {
1109
            $this->cellXfCollection[] = new Style();
1110
        }
1111
1112
        // update the xfIndex for all cells, row dimensions, column dimensions
1113
        foreach ($this->getWorksheetIterator() as $sheet) {
1114
            // for all cells
1115 View Code Duplication
            foreach ($sheet->getCellCollection(false) as $cellID) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1116
                $cell = $sheet->getCell($cellID);
1117
                $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...
1118
            }
1119
1120
            // for all row dimensions
1121
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1122
                if ($rowDimension->getXfIndex() !== null) {
1123
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1124
                }
1125
            }
1126
1127
            // for all column dimensions
1128
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1129
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1130
            }
1131
1132
            // also do garbage collection for all the sheets
1133
            $sheet->garbageCollect();
1134
        }
1135
    }
1136
1137
    /**
1138
     * Return the unique ID value assigned to this spreadsheet workbook
1139
     *
1140
     * @return string
1141
     */
1142
    public function getID()
1143
    {
1144
        return $this->uniqueID;
1145
    }
1146
}
1147