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

Workbook::writeBoundSheet()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 23
nc 4
nop 2
dl 0
loc 33
rs 8.5806
c 1
b 0
f 0
1
<?php
2
3
namespace PhpSpreadsheet\Writer\Excel5;
4
5
/**
6
 * \PhpSpreadsheet\Writer\Excel5\Workbook
7
 *
8
 * Copyright (c) 2006 - 2015 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 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
26
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
27
 * @version    ##VERSION##, ##DATE##
28
 */
29
30
// Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
31
// -----------------------------------------------------------------------------------------
32
// /*
33
// *  Module written/ported by Xavier Noguer <[email protected]>
34
// *
35
// *  The majority of this is _NOT_ my code.  I simply ported it from the
36
// *  PERL Spreadsheet::WriteExcel module.
37
// *
38
// *  The author of the Spreadsheet::WriteExcel module is John McNamara
39
// *  <[email protected]>
40
// *
41
// *  I _DO_ maintain this code, and John McNamara has nothing to do with the
42
// *  porting of this code to PHP.  Any questions directly related to this
43
// *  class library should be directed to me.
44
// *
45
// *  License Information:
46
// *
47
// *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
48
// *    Copyright (c) 2002-2003 Xavier Noguer [email protected]
49
// *
50
// *    This library is free software; you can redistribute it and/or
51
// *    modify it under the terms of the GNU Lesser General Public
52
// *    License as published by the Free Software Foundation; either
53
// *    version 2.1 of the License, or (at your option) any later version.
54
// *
55
// *    This library is distributed in the hope that it will be useful,
56
// *    but WITHOUT ANY WARRANTY; without even the implied warranty of
57
// *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
58
// *    Lesser General Public License for more details.
59
// *
60
// *    You should have received a copy of the GNU Lesser General Public
61
// *    License along with this library; if not, write to the Free Software
62
// *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
63
// */
64
class Workbook extends BIFFwriter
65
{
66
    /**
67
     * Formula parser
68
     *
69
     * @var \PhpSpreadsheet\Writer\Excel5\Parser
70
     */
71
    private $parser;
72
73
    /**
74
     * The BIFF file size for the workbook.
75
     * @var integer
76
     * @see calcSheetOffsets()
77
     */
78
    private $biffSize;
79
80
    /**
81
     * XF Writers
82
     * @var \PhpSpreadsheet\Writer\Excel5\Xf[]
83
     */
84
    private $xfWriters = array();
85
86
    /**
87
     * Array containing the colour palette
88
     * @var array
89
     */
90
    private $palette;
91
92
    /**
93
     * The codepage indicates the text encoding used for strings
94
     * @var integer
95
     */
96
    private $codepage;
97
98
    /**
99
     * The country code used for localization
100
     * @var integer
101
     */
102
    private $countryCode;
103
104
    /**
105
     * Workbook
106
     * @var \PhpSpreadsheet\Spreadsheet
107
     */
108
    private $spreadsheet;
109
110
    /**
111
     * Fonts writers
112
     *
113
     * @var \PhpSpreadsheet\Writer\Excel5\Font[]
114
     */
115
    private $fontWriters = array();
116
117
    /**
118
     * Added fonts. Maps from font's hash => index in workbook
119
     *
120
     * @var array
121
     */
122
    private $addedFonts = array();
123
124
    /**
125
     * Shared number formats
126
     *
127
     * @var array
128
     */
129
    private $numberFormats = array();
130
131
    /**
132
     * Added number formats. Maps from numberFormat's hash => index in workbook
133
     *
134
     * @var array
135
     */
136
    private $addedNumberFormats = array();
137
138
    /**
139
     * Sizes of the binary worksheet streams
140
     *
141
     * @var array
142
     */
143
    private $worksheetSizes = array();
144
145
    /**
146
     * Offsets of the binary worksheet streams relative to the start of the global workbook stream
147
     *
148
     * @var array
149
     */
150
    private $worksheetOffsets = array();
151
152
    /**
153
     * Total number of shared strings in workbook
154
     *
155
     * @var int
156
     */
157
    private $stringTotal;
158
159
    /**
160
     * Number of unique shared strings in workbook
161
     *
162
     * @var int
163
     */
164
    private $stringUnique;
165
166
    /**
167
     * Array of unique shared strings in workbook
168
     *
169
     * @var array
170
     */
171
    private $stringTable;
172
173
    /**
174
     * Color cache
175
     */
176
    private $colors;
177
178
    /**
179
     * Escher object corresponding to MSODRAWINGGROUP
180
     *
181
     * @var \PhpSpreadsheet\Shared\Escher
182
     */
183
    private $escher;
184
185
186
    /**
187
     * Class constructor
188
     *
189
     * @param \PhpSpreadsheet\Spreadsheet $spreadsheet The Workbook
190
     * @param int        &$str_total        Total number of strings
191
     * @param int        &$str_unique    Total number of unique strings
192
     * @param array        &$str_table        String Table
193
     * @param array        &$colors        Colour Table
194
     * @param mixed        $parser            The formula parser created for the Workbook
195
     */
196
    public function __construct(\PhpSpreadsheet\Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, $parser)
197
    {
198
        // It needs to call its parent's constructor explicitly
199
        parent::__construct();
200
201
        $this->parser        = $parser;
202
        $this->biffSize     = 0;
203
        $this->palette      = array();
204
        $this->countryCode = -1;
205
206
        $this->stringTotal      = &$str_total;
207
        $this->stringUnique     = &$str_unique;
208
        $this->stringTable      = &$str_table;
209
        $this->colors        = &$colors;
210
        $this->setPaletteXl97();
211
212
        $this->spreadsheet = $spreadsheet;
213
214
        // set BIFFwriter limit for CONTINUE records
215
        //        $this->_limit = 8224;
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% 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...
216
        $this->codepage = 0x04B0;
217
218
        // Add empty sheets and Build color cache
219
        $countSheets = $spreadsheet->getSheetCount();
220
        for ($i = 0; $i < $countSheets; ++$i) {
221
            $phpSheet = $spreadsheet->getSheet($i);
222
223
            $this->parser->setExtSheet($phpSheet->getTitle(), $i);  // Register worksheet name with parser
224
225
            $supbook_index = 0x00;
226
            $ref = pack('vvv', $supbook_index, $i, $i);
227
            $this->parser->references[] = $ref;  // Register reference with parser
228
229
            // Sheet tab colors?
230
            if ($phpSheet->isTabColorSet()) {
231
                $this->addColor($phpSheet->getTabColor()->getRGB());
232
            }
233
        }
234
    }
235
236
    /**
237
     * Add a new XF writer
238
     *
239
     * @param \PhpSpreadsheet\Style
240
     * @param boolean Is it a style XF?
241
     * @return int Index to XF record
242
     */
243
    public function addXfWriter($style, $isStyleXf = false)
244
    {
245
        $xfWriter = new Xf($style);
246
        $xfWriter->setIsStyleXf($isStyleXf);
247
248
        // Add the font if not already added
249
        $fontIndex = $this->addFont($style->getFont());
250
251
        // Assign the font index to the xf record
252
        $xfWriter->setFontIndex($fontIndex);
253
254
        // Background colors, best to treat these after the font so black will come after white in custom palette
255
        $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
256
        $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
257
        $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
258
        $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
259
        $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
260
        $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
261
        $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
262
263
        // Add the number format if it is not a built-in one and not already added
264
        if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
265
            $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
266
267
            if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
268
                $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
269
            } else {
270
                $numberFormatIndex = 164 + count($this->numberFormats);
271
                $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
272
                $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
273
            }
274
        } else {
275
            $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
276
        }
277
278
        // Assign the number format index to xf record
279
        $xfWriter->setNumberFormatIndex($numberFormatIndex);
280
281
        $this->xfWriters[] = $xfWriter;
282
283
        $xfIndex = count($this->xfWriters) - 1;
284
        return $xfIndex;
285
    }
286
287
    /**
288
     * Add a font to added fonts
289
     *
290
     * @param \PhpSpreadsheet\Style\Font $font
291
     * @return int Index to FONT record
292
     */
293
    public function addFont(\PhpSpreadsheet\Style\Font $font)
294
    {
295
        $fontHashCode = $font->getHashCode();
296
        if (isset($this->addedFonts[$fontHashCode])) {
297
            $fontIndex = $this->addedFonts[$fontHashCode];
298
        } else {
299
            $countFonts = count($this->fontWriters);
300
            $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
301
302
            $fontWriter = new \PhpSpreadsheet\Writer\Excel5\Font($font);
303
            $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
304
            $this->fontWriters[] = $fontWriter;
305
306
            $this->addedFonts[$fontHashCode] = $fontIndex;
307
        }
308
        return $fontIndex;
309
    }
310
311
    /**
312
     * Alter color palette adding a custom color
313
     *
314
     * @param string $rgb E.g. 'FF00AA'
315
     * @return int Color index
316
     */
317
    private function addColor($rgb)
318
    {
319
        if (!isset($this->colors[$rgb])) {
320
            if (count($this->colors) < 57) {
321
                // then we add a custom color altering the palette
322
                $colorIndex = 8 + count($this->colors);
323
                $this->palette[$colorIndex] =
324
                    array(
325
                        hexdec(substr($rgb, 0, 2)),
326
                        hexdec(substr($rgb, 2, 2)),
327
                        hexdec(substr($rgb, 4)),
328
                        0
329
                    );
330
                $this->colors[$rgb] = $colorIndex;
331
            } else {
332
                // no room for more custom colors, just map to black
333
                $colorIndex = 0;
334
            }
335
        } else {
336
            // fetch already added custom color
337
            $colorIndex = $this->colors[$rgb];
338
        }
339
340
        return $colorIndex;
341
    }
342
343
    /**
344
     * Sets the colour palette to the Excel 97+ default.
345
     *
346
     * @access private
347
     */
348
    private function setPaletteXl97()
349
    {
350
        $this->palette = array(
351
            0x08 => array(0x00, 0x00, 0x00, 0x00),
352
            0x09 => array(0xff, 0xff, 0xff, 0x00),
353
            0x0A => array(0xff, 0x00, 0x00, 0x00),
354
            0x0B => array(0x00, 0xff, 0x00, 0x00),
355
            0x0C => array(0x00, 0x00, 0xff, 0x00),
356
            0x0D => array(0xff, 0xff, 0x00, 0x00),
357
            0x0E => array(0xff, 0x00, 0xff, 0x00),
358
            0x0F => array(0x00, 0xff, 0xff, 0x00),
359
            0x10 => array(0x80, 0x00, 0x00, 0x00),
360
            0x11 => array(0x00, 0x80, 0x00, 0x00),
361
            0x12 => array(0x00, 0x00, 0x80, 0x00),
362
            0x13 => array(0x80, 0x80, 0x00, 0x00),
363
            0x14 => array(0x80, 0x00, 0x80, 0x00),
364
            0x15 => array(0x00, 0x80, 0x80, 0x00),
365
            0x16 => array(0xc0, 0xc0, 0xc0, 0x00),
366
            0x17 => array(0x80, 0x80, 0x80, 0x00),
367
            0x18 => array(0x99, 0x99, 0xff, 0x00),
368
            0x19 => array(0x99, 0x33, 0x66, 0x00),
369
            0x1A => array(0xff, 0xff, 0xcc, 0x00),
370
            0x1B => array(0xcc, 0xff, 0xff, 0x00),
371
            0x1C => array(0x66, 0x00, 0x66, 0x00),
372
            0x1D => array(0xff, 0x80, 0x80, 0x00),
373
            0x1E => array(0x00, 0x66, 0xcc, 0x00),
374
            0x1F => array(0xcc, 0xcc, 0xff, 0x00),
375
            0x20 => array(0x00, 0x00, 0x80, 0x00),
376
            0x21 => array(0xff, 0x00, 0xff, 0x00),
377
            0x22 => array(0xff, 0xff, 0x00, 0x00),
378
            0x23 => array(0x00, 0xff, 0xff, 0x00),
379
            0x24 => array(0x80, 0x00, 0x80, 0x00),
380
            0x25 => array(0x80, 0x00, 0x00, 0x00),
381
            0x26 => array(0x00, 0x80, 0x80, 0x00),
382
            0x27 => array(0x00, 0x00, 0xff, 0x00),
383
            0x28 => array(0x00, 0xcc, 0xff, 0x00),
384
            0x29 => array(0xcc, 0xff, 0xff, 0x00),
385
            0x2A => array(0xcc, 0xff, 0xcc, 0x00),
386
            0x2B => array(0xff, 0xff, 0x99, 0x00),
387
            0x2C => array(0x99, 0xcc, 0xff, 0x00),
388
            0x2D => array(0xff, 0x99, 0xcc, 0x00),
389
            0x2E => array(0xcc, 0x99, 0xff, 0x00),
390
            0x2F => array(0xff, 0xcc, 0x99, 0x00),
391
            0x30 => array(0x33, 0x66, 0xff, 0x00),
392
            0x31 => array(0x33, 0xcc, 0xcc, 0x00),
393
            0x32 => array(0x99, 0xcc, 0x00, 0x00),
394
            0x33 => array(0xff, 0xcc, 0x00, 0x00),
395
            0x34 => array(0xff, 0x99, 0x00, 0x00),
396
            0x35 => array(0xff, 0x66, 0x00, 0x00),
397
            0x36 => array(0x66, 0x66, 0x99, 0x00),
398
            0x37 => array(0x96, 0x96, 0x96, 0x00),
399
            0x38 => array(0x00, 0x33, 0x66, 0x00),
400
            0x39 => array(0x33, 0x99, 0x66, 0x00),
401
            0x3A => array(0x00, 0x33, 0x00, 0x00),
402
            0x3B => array(0x33, 0x33, 0x00, 0x00),
403
            0x3C => array(0x99, 0x33, 0x00, 0x00),
404
            0x3D => array(0x99, 0x33, 0x66, 0x00),
405
            0x3E => array(0x33, 0x33, 0x99, 0x00),
406
            0x3F => array(0x33, 0x33, 0x33, 0x00),
407
        );
408
    }
409
410
    /**
411
     * Assemble worksheets into a workbook and send the BIFF data to an OLE
412
     * storage.
413
     *
414
     * @param    array    $pWorksheetSizes    The sizes in bytes of the binary worksheet streams
415
     * @return    string    Binary data for workbook stream
416
     */
417
    public function writeWorkbook($pWorksheetSizes = null)
418
    {
419
        $this->worksheetSizes = $pWorksheetSizes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $pWorksheetSizes can be null. However, the property $worksheetSizes is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
420
421
        // Calculate the number of selected worksheet tabs and call the finalization
422
        // methods for each worksheet
423
        $total_worksheets = $this->spreadsheet->getSheetCount();
424
425
        // Add part 1 of the Workbook globals, what goes before the SHEET records
426
        $this->storeBof(0x0005);
427
        $this->writeCodepage();
428
        $this->writeWindow1();
429
430
        $this->writeDateMode();
431
        $this->writeAllFonts();
432
        $this->writeAllNumberFormats();
433
        $this->writeAllXfs();
434
        $this->writeAllStyles();
435
        $this->writePalette();
436
437
        // Prepare part 3 of the workbook global stream, what goes after the SHEET records
438
        $part3 = '';
439
        if ($this->countryCode != -1) {
440
            $part3 .= $this->writeCountry();
441
        }
442
        $part3 .= $this->writeRecalcId();
443
444
        $part3 .= $this->writeSupbookInternal();
445
        /* TODO: store external SUPBOOK records and XCT and CRN records
446
        in case of external references for BIFF8 */
447
        $part3 .= $this->writeExternalsheetBiff8();
448
        $part3 .= $this->writeAllDefinedNamesBiff8();
449
        $part3 .= $this->writeMsoDrawingGroup();
450
        $part3 .= $this->writeSharedStringsTable();
451
452
        $part3 .= $this->writeEof();
453
454
        // Add part 2 of the Workbook globals, the SHEET records
455
        $this->calcSheetOffsets();
456
        for ($i = 0; $i < $total_worksheets; ++$i) {
457
            $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
458
        }
459
460
        // Add part 3 of the Workbook globals
461
        $this->_data .= $part3;
462
463
        return $this->_data;
464
    }
465
466
    /**
467
     * Calculate offsets for Worksheet BOF records.
468
     *
469
     * @access private
470
     */
471
    private function calcSheetOffsets()
472
    {
473
        $boundsheet_length = 10;  // fixed length for a BOUNDSHEET record
474
475
        // size of Workbook globals part 1 + 3
476
        $offset            = $this->_datasize;
477
478
        // add size of Workbook globals part 2, the length of the SHEET records
479
        $total_worksheets = count($this->spreadsheet->getAllSheets());
480
        foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
481
            $offset += $boundsheet_length + strlen(\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
482
        }
483
484
        // add the sizes of each of the Sheet substreams, respectively
485
        for ($i = 0; $i < $total_worksheets; ++$i) {
486
            $this->worksheetOffsets[$i] = $offset;
487
            $offset += $this->worksheetSizes[$i];
488
        }
489
        $this->biffSize = $offset;
490
    }
491
492
    /**
493
     * Store the Excel FONT records.
494
     */
495
    private function writeAllFonts()
496
    {
497
        foreach ($this->fontWriters as $fontWriter) {
498
            $this->append($fontWriter->writeFont());
499
        }
500
    }
501
502
    /**
503
     * Store user defined numerical formats i.e. FORMAT records
504
     */
505
    private function writeAllNumberFormats()
506
    {
507
        foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
508
            $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
509
        }
510
    }
511
512
    /**
513
     * Write all XF records.
514
     */
515
    private function writeAllXfs()
516
    {
517
        foreach ($this->xfWriters as $xfWriter) {
518
            $this->append($xfWriter->writeXf());
519
        }
520
    }
521
522
    /**
523
     * Write all STYLE records.
524
     */
525
    private function writeAllStyles()
526
    {
527
        $this->writeStyle();
528
    }
529
530
    /**
531
     * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
532
     * the NAME records.
533
     */
534
    private function writeExternals()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
535
    {
536
        $countSheets = $this->spreadsheet->getSheetCount();
537
        // Create EXTERNCOUNT with number of worksheets
538
        $this->writeExternalCount($countSheets);
539
540
        // Create EXTERNSHEET for each worksheet
541
        for ($i = 0; $i < $countSheets; ++$i) {
542
            $this->writeExternalSheet($this->spreadsheet->getSheet($i)->getTitle());
543
        }
544
    }
545
546
    /**
547
     * Write the NAME record to define the print area and the repeat rows and cols.
548
     */
549
    private function writeNames()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
550
    {
551
        // total number of sheets
552
        $total_worksheets = $this->spreadsheet->getSheetCount();
553
554
        // Create the print area NAME records
555
        for ($i = 0; $i < $total_worksheets; ++$i) {
556
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
557
            // Write a Name record if the print area has been defined
558
            if ($sheetSetup->isPrintAreaSet()) {
559
                // Print area
560
                $printArea = \PhpSpreadsheet\Cell::splitRange($sheetSetup->getPrintArea());
561
                $printArea = $printArea[0];
562
                $printArea[0] = \PhpSpreadsheet\Cell::coordinateFromString($printArea[0]);
563
                $printArea[1] = \PhpSpreadsheet\Cell::coordinateFromString($printArea[1]);
564
565
                $print_rowmin = $printArea[0][1] - 1;
566
                $print_rowmax = $printArea[1][1] - 1;
567
                $print_colmin = \PhpSpreadsheet\Cell::columnIndexFromString($printArea[0][0]) - 1;
568
                $print_colmax = \PhpSpreadsheet\Cell::columnIndexFromString($printArea[1][0]) - 1;
569
570
                $this->writeNameShort(
571
                    $i, // sheet index
572
                    0x06, // NAME type
573
                    $print_rowmin,
574
                    $print_rowmax,
575
                    $print_colmin,
576
                    $print_colmax
577
                );
578
            }
579
        }
580
581
        // Create the print title NAME records
582
        for ($i = 0; $i < $total_worksheets; ++$i) {
583
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
584
585
            // simultaneous repeatColumns repeatRows
586
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
587
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
588
                $colmin = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
589
                $colmax = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
590
591
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
592
                $rowmin = $repeat[0] - 1;
593
                $rowmax = $repeat[1] - 1;
594
595
                $this->writeNameLong(
596
                    $i, // sheet index
597
                    0x07, // NAME type
598
                    $rowmin,
599
                    $rowmax,
600
                    $colmin,
601
                    $colmax
602
                );
603
604
            // (exclusive) either repeatColumns or repeatRows
605
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
606
                // Columns to repeat
607 View Code Duplication
                if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
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...
608
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
609
                    $colmin = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
610
                    $colmax = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
611
                } else {
612
                    $colmin = 0;
613
                    $colmax = 255;
614
                }
615
616
                // Rows to repeat
617 View Code Duplication
                if ($sheetSetup->isRowsToRepeatAtTopSet()) {
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...
618
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
619
                    $rowmin = $repeat[0] - 1;
620
                    $rowmax = $repeat[1] - 1;
621
                } else {
622
                    $rowmin = 0;
623
                    $rowmax = 65535;
624
                }
625
626
                $this->writeNameShort(
627
                    $i, // sheet index
628
                    0x07, // NAME type
629
                    $rowmin,
630
                    $rowmax,
631
                    $colmin,
632
                    $colmax
633
                );
634
            }
635
        }
636
    }
637
638
    /**
639
     * Writes all the DEFINEDNAME records (BIFF8).
640
     * So far this is only used for repeating rows/columns (print titles) and print areas
641
     */
642
    private function writeAllDefinedNamesBiff8()
643
    {
644
        $chunk = '';
645
646
        // Named ranges
647
        if (count($this->spreadsheet->getNamedRanges()) > 0) {
648
            // Loop named ranges
649
            $namedRanges = $this->spreadsheet->getNamedRanges();
650
            foreach ($namedRanges as $namedRange) {
651
                // Create absolute coordinate
652
                $range = \PhpSpreadsheet\Cell::splitRange($namedRange->getRange());
653 View Code Duplication
                for ($i = 0; $i < count($range); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
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...
654
                    $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . \PhpSpreadsheet\Cell::absoluteCoordinate($range[$i][0]);
655
                    if (isset($range[$i][1])) {
656
                        $range[$i][1] = \PhpSpreadsheet\Cell::absoluteCoordinate($range[$i][1]);
657
                    }
658
                }
659
                $range = \PhpSpreadsheet\Cell::buildRange($range); // e.g. Sheet1!$A$1:$B$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% 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...
660
661
                // parse formula
662
                try {
663
                    $error = $this->parser->parse($range);
0 ignored issues
show
Unused Code introduced by
$error is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
664
                    $formulaData = $this->parser->toReversePolish();
665
666
                    // make sure tRef3d is of type tRef3dR (0x3A)
667
                    if (isset($formulaData{0}) and ($formulaData{0} == "\x7A" or $formulaData{0} == "\x5A")) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
668
                        $formulaData = "\x3A" . substr($formulaData, 1);
669
                    }
670
671
                    if ($namedRange->getLocalOnly()) {
672
                        // local scope
673
                        $scope = $this->spreadsheet->getIndex($namedRange->getScope()) + 1;
0 ignored issues
show
Bug introduced by
It seems like $namedRange->getScope() 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...
674
                    } else {
675
                        // global scope
676
                        $scope = 0;
677
                    }
678
                    $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
679
                } catch (\PhpSpreadsheet\Exception $e) {
680
                    // do nothing
681
                }
682
            }
683
        }
684
685
        // total number of sheets
686
        $total_worksheets = $this->spreadsheet->getSheetCount();
687
688
        // write the print titles (repeating rows, columns), if any
689
        for ($i = 0; $i < $total_worksheets; ++$i) {
690
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
691
            // simultaneous repeatColumns repeatRows
692
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
693
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
694
                $colmin = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
695
                $colmax = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
696
697
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
698
                $rowmin = $repeat[0] - 1;
699
                $rowmax = $repeat[1] - 1;
700
701
                // construct formula data manually
702
                $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
703
                $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
704
                $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
705
                $formulaData .= pack('C', 0x10); // tList
706
707
                // store the DEFINEDNAME record
708
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
709
710
            // (exclusive) either repeatColumns or repeatRows
711
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
712
                // Columns to repeat
713 View Code Duplication
                if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
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...
714
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
715
                    $colmin = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
716
                    $colmax = \PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
717
                } else {
718
                    $colmin = 0;
719
                    $colmax = 255;
720
                }
721
                // Rows to repeat
722 View Code Duplication
                if ($sheetSetup->isRowsToRepeatAtTopSet()) {
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...
723
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
724
                    $rowmin = $repeat[0] - 1;
725
                    $rowmax = $repeat[1] - 1;
726
                } else {
727
                    $rowmin = 0;
728
                    $rowmax = 65535;
729
                }
730
731
                // construct formula data manually because parser does not recognize absolute 3d cell references
732
                $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
733
734
                // store the DEFINEDNAME record
735
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
736
            }
737
        }
738
739
        // write the print areas, if any
740
        for ($i = 0; $i < $total_worksheets; ++$i) {
741
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
742
            if ($sheetSetup->isPrintAreaSet()) {
743
                // Print area, e.g. A3:J6,H1:X20
744
                $printArea = \PhpSpreadsheet\Cell::splitRange($sheetSetup->getPrintArea());
745
                $countPrintArea = count($printArea);
746
747
                $formulaData = '';
748
                for ($j = 0; $j < $countPrintArea; ++$j) {
749
                    $printAreaRect = $printArea[$j]; // e.g. A3:J6
750
                    $printAreaRect[0] = \PhpSpreadsheet\Cell::coordinateFromString($printAreaRect[0]);
751
                    $printAreaRect[1] = \PhpSpreadsheet\Cell::coordinateFromString($printAreaRect[1]);
752
753
                    $print_rowmin = $printAreaRect[0][1] - 1;
754
                    $print_rowmax = $printAreaRect[1][1] - 1;
755
                    $print_colmin = \PhpSpreadsheet\Cell::columnIndexFromString($printAreaRect[0][0]) - 1;
756
                    $print_colmax = \PhpSpreadsheet\Cell::columnIndexFromString($printAreaRect[1][0]) - 1;
757
758
                    // construct formula data manually because parser does not recognize absolute 3d cell references
759
                    $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
760
761
                    if ($j > 0) {
762
                        $formulaData .= pack('C', 0x10); // list operator token ','
763
                    }
764
                }
765
766
                // store the DEFINEDNAME record
767
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
768
            }
769
        }
770
771
        // write autofilters, if any
772
        for ($i = 0; $i < $total_worksheets; ++$i) {
773
            $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
774
            $autoFilterRange = $sheetAutoFilter->getRange();
775
            if (!empty($autoFilterRange)) {
776
                $rangeBounds = \PhpSpreadsheet\Cell::rangeBoundaries($autoFilterRange);
777
778
                //Autofilter built in name
779
                $name = pack('C', 0x0D);
780
781
                $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
782
            }
783
        }
784
785
        return $chunk;
786
    }
787
788
    /**
789
     * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data
790
     *
791
     * @param    string        $name            The name in UTF-8
792
     * @param    string        $formulaData    The binary formula data
793
     * @param    string        $sheetIndex        1-based sheet index the defined name applies to. 0 = global
794
     * @param    boolean        $isBuiltIn        Built-in name?
795
     * @return    string    Complete binary record data
796
     */
797
    private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
798
    {
799
        $record = 0x0018;
800
801
        // option flags
802
        $options = $isBuiltIn ? 0x20 : 0x00;
803
804
        // length of the name, character count
805
        $nlen = \PhpSpreadsheet\Shared\StringHelper::countCharacters($name);
806
807
        // name with stripped length field
808
        $name = substr(\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
809
810
        // size of the formula (in bytes)
811
        $sz = strlen($formulaData);
812
813
        // combine the parts
814
        $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
815
            . $name . $formulaData;
816
        $length = strlen($data);
817
818
        $header = pack('vv', $record, $length);
819
820
        return $header . $data;
821
    }
822
823
    /**
824
     * Write a short NAME record
825
     *
826
     * @param    string         $name
827
     * @param    string         $sheetIndex        1-based sheet index the defined name applies to. 0 = global
828
     * @param    integer[][]  $rangeBounds    range boundaries
829
     * @param    boolean      $isHidden
830
     * @return    string    Complete binary record data
831
     * */
832
    private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
833
    {
834
        $record = 0x0018;
835
836
        // option flags
837
        $options = ($isHidden  ? 0x21 : 0x00);
838
839
        $extra  = pack(
840
            'Cvvvvv',
841
            0x3B,
842
            $sheetIndex - 1,
843
            $rangeBounds[0][1] - 1,
844
            $rangeBounds[1][1] - 1,
845
            $rangeBounds[0][0] - 1,
846
            $rangeBounds[1][0] - 1
847
        );
848
849
        // size of the formula (in bytes)
850
        $sz = strlen($extra);
851
852
        // combine the parts
853
        $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
854
            . $name . $extra;
855
        $length = strlen($data);
856
857
        $header = pack('vv', $record, $length);
858
859
        return $header . $data;
860
    }
861
862
    /**
863
     * Stores the CODEPAGE biff record.
864
     */
865
    private function writeCodepage()
866
    {
867
        $record = 0x0042;             // Record identifier
868
        $length = 0x0002;             // Number of bytes to follow
869
        $cv  = $this->codepage;   // The code page
870
871
        $header = pack('vv', $record, $length);
872
        $data   = pack('v', $cv);
873
874
        $this->append($header . $data);
875
    }
876
877
    /**
878
     * Write Excel BIFF WINDOW1 record.
879
     */
880
    private function writeWindow1()
881
    {
882
        $record = 0x003D;   // Record identifier
883
        $length = 0x0012;   // Number of bytes to follow
884
885
        $xWn  = 0x0000;     // Horizontal position of window
886
        $yWn  = 0x0000;     // Vertical position of window
887
        $dxWn = 0x25BC;     // Width of window
888
        $dyWn = 0x1572;     // Height of window
889
890
        $grbit = 0x0038;    // Option flags
891
892
        // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
893
        $ctabsel = 1;       // Number of workbook tabs selected
894
895
        $wTabRatio = 0x0258;    // Tab to scrollbar ratio
896
897
        // not supported by PhpSpreadsheet, set to 0
898
        $itabFirst = 0;     // 1st displayed worksheet
899
        $itabCur   = $this->spreadsheet->getActiveSheetIndex();    // Active worksheet
900
901
        $header = pack("vv", $record, $length);
902
        $data   = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
903
        $this->append($header . $data);
904
    }
905
906
    /**
907
     * Writes Excel BIFF BOUNDSHEET record.
908
     *
909
     * @param \PhpSpreadsheet\Worksheet  $sheet Worksheet name
910
     * @param integer $offset    Location of worksheet BOF
911
     */
912
    private function writeBoundSheet($sheet, $offset)
913
    {
914
        $sheetname = $sheet->getTitle();
915
        $record    = 0x0085;                    // Record identifier
916
917
        // sheet state
918
        switch ($sheet->getSheetState()) {
919
            case \PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE:
920
                $ss = 0x00;
921
                break;
922
            case \PhpSpreadsheet\Worksheet::SHEETSTATE_HIDDEN:
923
                $ss = 0x01;
924
                break;
925
            case \PhpSpreadsheet\Worksheet::SHEETSTATE_VERYHIDDEN:
926
                $ss = 0x02;
927
                break;
928
            default:
929
                $ss = 0x00;
930
                break;
931
        }
932
933
        // sheet type
934
        $st = 0x00;
935
936
        $grbit = 0x0000;                    // Visibility and sheet type
0 ignored issues
show
Unused Code introduced by
$grbit is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
937
938
        $data = pack("VCC", $offset, $ss, $st);
939
        $data .= \PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
940
941
        $length = strlen($data);
942
        $header = pack("vv", $record, $length);
943
        $this->append($header . $data);
944
    }
945
946
    /**
947
     * Write Internal SUPBOOK record
948
     */
949 View Code Duplication
    private function writeSupbookInternal()
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...
950
    {
951
        $record    = 0x01AE;   // Record identifier
952
        $length    = 0x0004;   // Bytes to follow
953
954
        $header    = pack("vv", $record, $length);
955
        $data      = pack("vv", $this->spreadsheet->getSheetCount(), 0x0401);
956
        return $this->writeData($header . $data);
957
    }
958
959
    /**
960
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
961
     * formulas.
962
     *
963
     */
964
    private function writeExternalsheetBiff8()
965
    {
966
        $totalReferences = count($this->parser->references);
967
        $record = 0x0017;                     // Record identifier
968
        $length = 2 + 6 * $totalReferences;  // Number of bytes to follow
969
970
        $supbook_index = 0;           // FIXME: only using internal SUPBOOK record
0 ignored issues
show
Unused Code introduced by
$supbook_index is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
971
        $header = pack("vv", $record, $length);
972
        $data   = pack('v', $totalReferences);
973
        for ($i = 0; $i < $totalReferences; ++$i) {
974
            $data .= $this->parser->references[$i];
975
        }
976
        return $this->writeData($header . $data);
977
    }
978
979
    /**
980
     * Write Excel BIFF STYLE records.
981
     */
982
    private function writeStyle()
983
    {
984
        $record = 0x0293;   // Record identifier
985
        $length = 0x0004;   // Bytes to follow
986
987
        $ixfe    = 0x8000;  // Index to cell style XF
988
        $BuiltIn = 0x00;     // Built-in style
989
        $iLevel  = 0xff;     // Outline style level
990
991
        $header = pack("vv", $record, $length);
992
        $data   = pack("vCC", $ixfe, $BuiltIn, $iLevel);
993
        $this->append($header . $data);
994
    }
995
996
    /**
997
     * Writes Excel FORMAT record for non "built-in" numerical formats.
998
     *
999
     * @param string  $format Custom format string
1000
     * @param integer $ifmt   Format index code
1001
     */
1002 View Code Duplication
    private function writeNumberFormat($format, $ifmt)
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...
1003
    {
1004
        $record = 0x041E;    // Record identifier
1005
1006
        $numberFormatString = \PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeLong($format);
1007
        $length = 2 + strlen($numberFormatString);    // Number of bytes to follow
1008
1009
1010
        $header = pack("vv", $record, $length);
1011
        $data   = pack("v", $ifmt) .  $numberFormatString;
1012
        $this->append($header . $data);
1013
    }
1014
1015
    /**
1016
     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
1017
     */
1018
    private function writeDateMode()
1019
    {
1020
        $record = 0x0022;   // Record identifier
1021
        $length = 0x0002;   // Bytes to follow
1022
1023
        $f1904  = (\PhpSpreadsheet\Shared\Date::getExcelCalendar() == \PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904)
1024
            ? 1
1025
            : 0;   // Flag for 1904 date system
1026
1027
        $header = pack("vv", $record, $length);
1028
        $data   = pack("v", $f1904);
1029
        $this->append($header . $data);
1030
    }
1031
1032
    /**
1033
     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
1034
     * references in the workbook.
1035
     *
1036
     * Excel only stores references to external sheets that are used in NAME.
1037
     * The workbook NAME record is required to define the print area and the repeat
1038
     * rows and columns.
1039
     *
1040
     * A similar method is used in Worksheet.php for a slightly different purpose.
1041
     *
1042
     * @param integer $cxals Number of external references
1043
     */
1044
    private function writeExternalCount($cxals)
1045
    {
1046
        $record = 0x0016;   // Record identifier
1047
        $length = 0x0002;   // Number of bytes to follow
1048
1049
        $header = pack("vv", $record, $length);
1050
        $data   = pack("v", $cxals);
1051
        $this->append($header . $data);
1052
    }
1053
1054
    /**
1055
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
1056
     * formulas. NAME record is required to define the print area and the repeat
1057
     * rows and columns.
1058
     *
1059
     * A similar method is used in Worksheet.php for a slightly different purpose.
1060
     *
1061
     * @param string $sheetname Worksheet name
1062
     */
1063 View Code Duplication
    private function writeExternalSheet($sheetname)
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...
1064
    {
1065
        $record = 0x0017;                     // Record identifier
1066
        $length = 0x02 + strlen($sheetname);  // Number of bytes to follow
1067
1068
        $cch    = strlen($sheetname);         // Length of sheet name
1069
        $rgch   = 0x03;                       // Filename encoding
1070
1071
        $header = pack("vv", $record, $length);
1072
        $data   = pack("CC", $cch, $rgch);
1073
        $this->append($header . $data . $sheetname);
1074
    }
1075
1076
    /**
1077
     * Store the NAME record in the short format that is used for storing the print
1078
     * area, repeat rows only and repeat columns only.
1079
     *
1080
     * @param integer $index  Sheet index
1081
     * @param integer $type   Built-in name type
1082
     * @param integer $rowmin Start row
1083
     * @param integer $rowmax End row
1084
     * @param integer $colmin Start colum
1085
     * @param integer $colmax End column
1086
     */
1087
    private function writeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
1088
    {
1089
        $record = 0x0018;       // Record identifier
1090
        $length = 0x0024;       // Number of bytes to follow
1091
1092
        $grbit  = 0x0020;       // Option flags
1093
        $chKey  = 0x00;         // Keyboard shortcut
1094
        $cch    = 0x01;         // Length of text name
1095
        $cce    = 0x0015;       // Length of text definition
1096
        $ixals  = $index + 1;   // Sheet index
1097
        $itab   = $ixals;       // Equal to ixals
1098
        $cchCustMenu    = 0x00;         // Length of cust menu text
1099
        $cchDescription = 0x00;         // Length of description text
1100
        $cchHelptopic   = 0x00;         // Length of help topic text
1101
        $cchStatustext  = 0x00;         // Length of status bar text
1102
        $rgch           = $type;        // Built-in name type
1103
1104
        $unknown03 = 0x3b;
1105
        $unknown04 = 0xffff - $index;
1106
        $unknown05 = 0x0000;
1107
        $unknown06 = 0x0000;
1108
        $unknown07 = 0x1087;
1109
        $unknown08 = 0x8005;
1110
1111
        $header = pack("vv", $record, $length);
1112
        $data = pack("v", $grbit);
1113
        $data .= pack("C", $chKey);
1114
        $data .= pack("C", $cch);
1115
        $data .= pack("v", $cce);
1116
        $data .= pack("v", $ixals);
1117
        $data .= pack("v", $itab);
1118
        $data .= pack("C", $cchCustMenu);
1119
        $data .= pack("C", $cchDescription);
1120
        $data .= pack("C", $cchHelptopic);
1121
        $data .= pack("C", $cchStatustext);
1122
        $data .= pack("C", $rgch);
1123
        $data .= pack("C", $unknown03);
1124
        $data .= pack("v", $unknown04);
1125
        $data .= pack("v", $unknown05);
1126
        $data .= pack("v", $unknown06);
1127
        $data .= pack("v", $unknown07);
1128
        $data .= pack("v", $unknown08);
1129
        $data .= pack("v", $index);
1130
        $data .= pack("v", $index);
1131
        $data .= pack("v", $rowmin);
1132
        $data .= pack("v", $rowmax);
1133
        $data .= pack("C", $colmin);
1134
        $data .= pack("C", $colmax);
1135
        $this->append($header . $data);
1136
    }
1137
1138
    /**
1139
     * Store the NAME record in the long format that is used for storing the repeat
1140
     * rows and columns when both are specified. This shares a lot of code with
1141
     * writeNameShort() but we use a separate method to keep the code clean.
1142
     * Code abstraction for reuse can be carried too far, and I should know. ;-)
1143
     *
1144
     * @param integer $index Sheet index
1145
     * @param integer $type  Built-in name type
1146
     * @param integer $rowmin Start row
1147
     * @param integer $rowmax End row
1148
     * @param integer $colmin Start colum
1149
     * @param integer $colmax End column
1150
     */
1151
    private function writeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
1152
    {
1153
        $record          = 0x0018;       // Record identifier
1154
        $length          = 0x003d;       // Number of bytes to follow
1155
        $grbit           = 0x0020;       // Option flags
1156
        $chKey           = 0x00;         // Keyboard shortcut
1157
        $cch             = 0x01;         // Length of text name
1158
        $cce             = 0x002e;       // Length of text definition
1159
        $ixals           = $index + 1;   // Sheet index
1160
        $itab            = $ixals;       // Equal to ixals
1161
        $cchCustMenu     = 0x00;         // Length of cust menu text
1162
        $cchDescription  = 0x00;         // Length of description text
1163
        $cchHelptopic    = 0x00;         // Length of help topic text
1164
        $cchStatustext   = 0x00;         // Length of status bar text
1165
        $rgch            = $type;        // Built-in name type
1166
1167
        $unknown01       = 0x29;
1168
        $unknown02       = 0x002b;
1169
        $unknown03       = 0x3b;
1170
        $unknown04       = 0xffff-$index;
1171
        $unknown05       = 0x0000;
1172
        $unknown06       = 0x0000;
1173
        $unknown07       = 0x1087;
1174
        $unknown08       = 0x8008;
1175
1176
        $header = pack("vv", $record, $length);
1177
        $data = pack("v", $grbit);
1178
        $data .= pack("C", $chKey);
1179
        $data .= pack("C", $cch);
1180
        $data .= pack("v", $cce);
1181
        $data .= pack("v", $ixals);
1182
        $data .= pack("v", $itab);
1183
        $data .= pack("C", $cchCustMenu);
1184
        $data .= pack("C", $cchDescription);
1185
        $data .= pack("C", $cchHelptopic);
1186
        $data .= pack("C", $cchStatustext);
1187
        $data .= pack("C", $rgch);
1188
        $data .= pack("C", $unknown01);
1189
        $data .= pack("v", $unknown02);
1190
        // Column definition
1191
        $data .= pack("C", $unknown03);
1192
        $data .= pack("v", $unknown04);
1193
        $data .= pack("v", $unknown05);
1194
        $data .= pack("v", $unknown06);
1195
        $data .= pack("v", $unknown07);
1196
        $data .= pack("v", $unknown08);
1197
        $data .= pack("v", $index);
1198
        $data .= pack("v", $index);
1199
        $data .= pack("v", 0x0000);
1200
        $data .= pack("v", 0x3fff);
1201
        $data .= pack("C", $colmin);
1202
        $data .= pack("C", $colmax);
1203
        // Row definition
1204
        $data .= pack("C", $unknown03);
1205
        $data .= pack("v", $unknown04);
1206
        $data .= pack("v", $unknown05);
1207
        $data .= pack("v", $unknown06);
1208
        $data .= pack("v", $unknown07);
1209
        $data .= pack("v", $unknown08);
1210
        $data .= pack("v", $index);
1211
        $data .= pack("v", $index);
1212
        $data .= pack("v", $rowmin);
1213
        $data .= pack("v", $rowmax);
1214
        $data .= pack("C", 0x00);
1215
        $data .= pack("C", 0xff);
1216
        // End of data
1217
        $data .= pack("C", 0x10);
1218
        $this->append($header . $data);
1219
    }
1220
1221
    /**
1222
     * Stores the COUNTRY record for localization
1223
     *
1224
     * @return string
1225
     */
1226 View Code Duplication
    private function writeCountry()
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...
1227
    {
1228
        $record = 0x008C;    // Record identifier
1229
        $length = 4;         // Number of bytes to follow
1230
1231
        $header = pack('vv', $record, $length);
1232
        /* using the same country code always for simplicity */
1233
        $data = pack('vv', $this->countryCode, $this->countryCode);
1234
        //$this->append($header . $data);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
1235
        return $this->writeData($header . $data);
1236
    }
1237
1238
    /**
1239
     * Write the RECALCID record
1240
     *
1241
     * @return string
1242
     */
1243 View Code Duplication
    private function writeRecalcId()
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...
1244
    {
1245
        $record = 0x01C1;    // Record identifier
1246
        $length = 8;         // Number of bytes to follow
1247
1248
        $header = pack('vv', $record, $length);
1249
1250
        // by inspection of real Excel files, MS Office Excel 2007 writes this
1251
        $data = pack('VV', 0x000001C1, 0x00001E667);
1252
1253
        return $this->writeData($header . $data);
1254
    }
1255
1256
    /**
1257
     * Stores the PALETTE biff record.
1258
     */
1259
    private function writePalette()
1260
    {
1261
        $aref = $this->palette;
1262
1263
        $record = 0x0092;                // Record identifier
1264
        $length = 2 + 4 * count($aref);  // Number of bytes to follow
1265
        $ccv    = count($aref);          // Number of RGB values to follow
1266
        $data = '';                      // The RGB data
1267
1268
        // Pack the RGB data
1269
        foreach ($aref as $color) {
1270
            foreach ($color as $byte) {
1271
                $data .= pack("C", $byte);
1272
            }
1273
        }
1274
1275
        $header = pack("vvv", $record, $length, $ccv);
1276
        $this->append($header . $data);
1277
    }
1278
1279
    /**
1280
     * Handling of the SST continue blocks is complicated by the need to include an
1281
     * additional continuation byte depending on whether the string is split between
1282
     * blocks or whether it starts at the beginning of the block. (There are also
1283
     * additional complications that will arise later when/if Rich Strings are
1284
     * supported).
1285
     *
1286
     * The Excel documentation says that the SST record should be followed by an
1287
     * EXTSST record. The EXTSST record is a hash table that is used to optimise
1288
     * access to SST. However, despite the documentation it doesn't seem to be
1289
     * required so we will ignore it.
1290
     *
1291
     * @return string Binary data
1292
     */
1293
    private function writeSharedStringsTable()
1294
    {
1295
        // maximum size of record data (excluding record header)
1296
        $continue_limit = 8224;
1297
1298
        // initialize array of record data blocks
1299
        $recordDatas = array();
1300
1301
        // start SST record data block with total number of strings, total number of unique strings
1302
        $recordData = pack("VV", $this->stringTotal, $this->stringUnique);
1303
1304
        // loop through all (unique) strings in shared strings table
1305
        foreach (array_keys($this->stringTable) as $string) {
1306
            // here $string is a BIFF8 encoded string
1307
1308
            // length = character count
1309
            $headerinfo = unpack("vlength/Cencoding", $string);
1310
1311
            // currently, this is always 1 = uncompressed
1312
            $encoding = $headerinfo["encoding"];
1313
1314
            // initialize finished writing current $string
1315
            $finished = false;
1316
1317
            while ($finished === false) {
1318
                // normally, there will be only one cycle, but if string cannot immediately be written as is
1319
                // there will be need for more than one cylcle, if string longer than one record data block, there
1320
                // may be need for even more cycles
1321
1322
                if (strlen($recordData) + strlen($string) <= $continue_limit) {
1323
                    // then we can write the string (or remainder of string) without any problems
1324
                    $recordData .= $string;
1325
1326
                    if (strlen($recordData) + strlen($string) == $continue_limit) {
1327
                        // we close the record data block, and initialize a new one
1328
                        $recordDatas[] = $recordData;
1329
                        $recordData = '';
1330
                    }
1331
1332
                    // we are finished writing this string
1333
                    $finished = true;
1334
                } else {
1335
                    // special treatment writing the string (or remainder of the string)
1336
                    // If the string is very long it may need to be written in more than one CONTINUE record.
1337
1338
                    // check how many bytes more there is room for in the current record
1339
                    $space_remaining = $continue_limit - strlen($recordData);
1340
1341
                    // minimum space needed
1342
                    // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
1343
                    // compressed:   2 byte string length length field + 1 byte option flags + 1 byte character
1344
                    $min_space_needed = ($encoding == 1) ? 5 : 4;
1345
1346
                    // We have two cases
1347
                    // 1. space remaining is less than minimum space needed
1348
                    //        here we must waste the space remaining and move to next record data block
1349
                    // 2. space remaining is greater than or equal to minimum space needed
1350
                    //        here we write as much as we can in the current block, then move to next record data block
1351
1352
                    // 1. space remaining is less than minimum space needed
1353
                    if ($space_remaining < $min_space_needed) {
1354
                        // we close the block, store the block data
1355
                        $recordDatas[] = $recordData;
1356
1357
                        // and start new record data block where we start writing the string
1358
                        $recordData = '';
1359
1360
                    // 2. space remaining is greater than or equal to minimum space needed
1361
                    } else {
1362
                        // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
1363
                        $effective_space_remaining = $space_remaining;
1364
1365
                        // for uncompressed strings, sometimes effective space remaining is reduced by 1
1366
                        if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
1367
                            --$effective_space_remaining;
1368
                        }
1369
1370
                        // one block fininshed, store the block data
1371
                        $recordData .= substr($string, 0, $effective_space_remaining);
1372
1373
                        $string = substr($string, $effective_space_remaining); // for next cycle in while loop
1374
                        $recordDatas[] = $recordData;
1375
1376
                        // start new record data block with the repeated option flags
1377
                        $recordData = pack('C', $encoding);
1378
                    }
1379
                }
1380
            }
1381
        }
1382
1383
        // Store the last record data block unless it is empty
1384
        // if there was no need for any continue records, this will be the for SST record data block itself
1385
        if (strlen($recordData) > 0) {
1386
            $recordDatas[] = $recordData;
1387
        }
1388
1389
        // combine into one chunk with all the blocks SST, CONTINUE,...
1390
        $chunk = '';
1391
        foreach ($recordDatas as $i => $recordData) {
1392
            // first block should have the SST record header, remaing should have CONTINUE header
1393
            $record = ($i == 0) ? 0x00FC : 0x003C;
1394
1395
            $header = pack("vv", $record, strlen($recordData));
1396
            $data = $header . $recordData;
1397
1398
            $chunk .= $this->writeData($data);
1399
        }
1400
1401
        return $chunk;
1402
    }
1403
1404
    /**
1405
     * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
1406
     */
1407
    private function writeMsoDrawingGroup()
1408
    {
1409
        // write the Escher stream if necessary
1410
        if (isset($this->escher)) {
1411
            $writer = new \PhpSpreadsheet\Writer\Excel5\Escher($this->escher);
1412
            $data = $writer->close();
1413
1414
            $record = 0x00EB;
1415
            $length = strlen($data);
1416
            $header = pack("vv", $record, $length);
1417
1418
            return $this->writeData($header . $data);
1419
        } else {
1420
            return '';
1421
        }
1422
    }
1423
1424
    /**
1425
     * Get Escher object
1426
     *
1427
     * @return \PhpSpreadsheet\Shared\Escher
1428
     */
1429
    public function getEscher()
1430
    {
1431
        return $this->escher;
1432
    }
1433
1434
    /**
1435
     * Set Escher object
1436
     *
1437
     * @param \PhpSpreadsheet\Shared\Escher $pValue
1438
     */
1439
    public function setEscher(\PhpSpreadsheet\Shared\Escher $pValue = null)
1440
    {
1441
        $this->escher = $pValue;
1442
    }
1443
}
1444