Completed
Push — develop ( 2922a1...cfa1fe )
by Adrien
24:40
created

Workbook::writeWindow1()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 0
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 17
cp 0
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Excel5;
4
5
/**
6
 * Copyright (c) 2006 - 2015 PhpSpreadsheet
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 *
22
 * @category   PhpSpreadsheet
23
 * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
24
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
25
 * @version    ##VERSION##, ##DATE##
26
 */
27
28
// Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
29
// -----------------------------------------------------------------------------------------
30
// /*
31
// *  Module written/ported by Xavier Noguer <[email protected]>
32
// *
33
// *  The majority of this is _NOT_ my code.  I simply ported it from the
34
// *  PERL Spreadsheet::WriteExcel module.
35
// *
36
// *  The author of the Spreadsheet::WriteExcel module is John McNamara
37
// *  <[email protected]>
38
// *
39
// *  I _DO_ maintain this code, and John McNamara has nothing to do with the
40
// *  porting of this code to PHP.  Any questions directly related to this
41
// *  class library should be directed to me.
42
// *
43
// *  License Information:
44
// *
45
// *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
46
// *    Copyright (c) 2002-2003 Xavier Noguer [email protected]
47
// *
48
// *    This library is free software; you can redistribute it and/or
49
// *    modify it under the terms of the GNU Lesser General Public
50
// *    License as published by the Free Software Foundation; either
51
// *    version 2.1 of the License, or (at your option) any later version.
52
// *
53
// *    This library is distributed in the hope that it will be useful,
54
// *    but WITHOUT ANY WARRANTY; without even the implied warranty of
55
// *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
56
// *    Lesser General Public License for more details.
57
// *
58
// *    You should have received a copy of the GNU Lesser General Public
59
// *    License along with this library; if not, write to the Free Software
60
// *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
61
// */
62
class Workbook extends BIFFwriter
63
{
64
    /**
65
     * Formula parser
66
     *
67
     * @var \PhpOffice\PhpSpreadsheet\Writer\Excel5\Parser
68
     */
69
    private $parser;
70
71
    /**
72
     * The BIFF file size for the workbook.
73
     * @var int
74
     * @see calcSheetOffsets()
75
     */
76
    private $biffSize;
77
78
    /**
79
     * XF Writers
80
     * @var \PhpOffice\PhpSpreadsheet\Writer\Excel5\Xf[]
81
     */
82
    private $xfWriters = [];
83
84
    /**
85
     * Array containing the colour palette
86
     * @var array
87
     */
88
    private $palette;
89
90
    /**
91
     * The codepage indicates the text encoding used for strings
92
     * @var int
93
     */
94
    private $codepage;
95
96
    /**
97
     * The country code used for localization
98
     * @var int
99
     */
100
    private $countryCode;
101
102
    /**
103
     * Workbook
104
     * @var \PhpOffice\PhpSpreadsheet\Spreadsheet
105
     */
106
    private $spreadsheet;
107
108
    /**
109
     * Fonts writers
110
     *
111
     * @var \PhpOffice\PhpSpreadsheet\Writer\Excel5\Font[]
112
     */
113
    private $fontWriters = [];
114
115
    /**
116
     * Added fonts. Maps from font's hash => index in workbook
117
     *
118
     * @var array
119
     */
120
    private $addedFonts = [];
121
122
    /**
123
     * Shared number formats
124
     *
125
     * @var array
126
     */
127
    private $numberFormats = [];
128
129
    /**
130
     * Added number formats. Maps from numberFormat's hash => index in workbook
131
     *
132
     * @var array
133
     */
134
    private $addedNumberFormats = [];
135
136
    /**
137
     * Sizes of the binary worksheet streams
138
     *
139
     * @var array
140
     */
141
    private $worksheetSizes = [];
142
143
    /**
144
     * Offsets of the binary worksheet streams relative to the start of the global workbook stream
145
     *
146
     * @var array
147
     */
148
    private $worksheetOffsets = [];
149
150
    /**
151
     * Total number of shared strings in workbook
152
     *
153
     * @var int
154
     */
155
    private $stringTotal;
156
157
    /**
158
     * Number of unique shared strings in workbook
159
     *
160
     * @var int
161
     */
162
    private $stringUnique;
163
164
    /**
165
     * Array of unique shared strings in workbook
166
     *
167
     * @var array
168
     */
169
    private $stringTable;
170
171
    /**
172
     * Color cache
173
     */
174
    private $colors;
175
176
    /**
177
     * Escher object corresponding to MSODRAWINGGROUP
178
     *
179
     * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
180
     */
181
    private $escher;
182
183
    /**
184
     * Class constructor
185
     *
186
     * @param \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet The Workbook
187
     * @param int        $str_total        Total number of strings
188
     * @param int        $str_unique    Total number of unique strings
189
     * @param array        $str_table        String Table
190
     * @param array        $colors        Colour Table
191
     * @param Parser        $parser            The formula parser created for the Workbook
192
     */
193
    public function __construct(\PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser)
194
    {
195
        // It needs to call its parent's constructor explicitly
196
        parent::__construct();
197
198
        $this->parser = $parser;
199
        $this->biffSize = 0;
200
        $this->palette = [];
201
        $this->countryCode = -1;
202
203
        $this->stringTotal = &$str_total;
204
        $this->stringUnique = &$str_unique;
205
        $this->stringTable = &$str_table;
206
        $this->colors = &$colors;
207
        $this->setPaletteXl97();
208
209
        $this->spreadsheet = $spreadsheet;
210
211
        $this->codepage = 0x04B0;
212
213
        // Add empty sheets and Build color cache
214
        $countSheets = $spreadsheet->getSheetCount();
215
        for ($i = 0; $i < $countSheets; ++$i) {
216
            $phpSheet = $spreadsheet->getSheet($i);
217
218
            $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
219
220
            $supbook_index = 0x00;
221
            $ref = pack('vvv', $supbook_index, $i, $i);
222
            $this->parser->references[] = $ref; // Register reference with parser
223
224
            // Sheet tab colors?
225
            if ($phpSheet->isTabColorSet()) {
226
                $this->addColor($phpSheet->getTabColor()->getRGB());
227
            }
228
        }
229
    }
230
231
    /**
232
     * Add a new XF writer
233
     *
234
     * @param \PhpOffice\PhpSpreadsheet\Style
235
     * @param bool Is it a style XF?
236
     * @return int Index to XF record
237
     */
238
    public function addXfWriter($style, $isStyleXf = false)
239
    {
240
        $xfWriter = new Xf($style);
241
        $xfWriter->setIsStyleXf($isStyleXf);
242
243
        // Add the font if not already added
244
        $fontIndex = $this->addFont($style->getFont());
245
246
        // Assign the font index to the xf record
247
        $xfWriter->setFontIndex($fontIndex);
248
249
        // Background colors, best to treat these after the font so black will come after white in custom palette
250
        $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
251
        $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
252
        $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
253
        $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
254
        $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
255
        $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
256
        $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
257
258
        // Add the number format if it is not a built-in one and not already added
259
        if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
260
            $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
261
262
            if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
263
                $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
264
            } else {
265
                $numberFormatIndex = 164 + count($this->numberFormats);
266
                $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
267
                $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
268
            }
269
        } else {
270
            $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
271
        }
272
273
        // Assign the number format index to xf record
274
        $xfWriter->setNumberFormatIndex($numberFormatIndex);
275
276
        $this->xfWriters[] = $xfWriter;
277
278
        $xfIndex = count($this->xfWriters) - 1;
279
280
        return $xfIndex;
281
    }
282
283
    /**
284
     * Add a font to added fonts
285
     *
286
     * @param \PhpOffice\PhpSpreadsheet\Style\Font $font
287
     * @return int Index to FONT record
288
     */
289
    public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
290
    {
291
        $fontHashCode = $font->getHashCode();
292
        if (isset($this->addedFonts[$fontHashCode])) {
293
            $fontIndex = $this->addedFonts[$fontHashCode];
294
        } else {
295
            $countFonts = count($this->fontWriters);
296
            $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
297
298
            $fontWriter = new \PhpOffice\PhpSpreadsheet\Writer\Excel5\Font($font);
299
            $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
300
            $this->fontWriters[] = $fontWriter;
301
302
            $this->addedFonts[$fontHashCode] = $fontIndex;
303
        }
304
305
        return $fontIndex;
306
    }
307
308
    /**
309
     * Alter color palette adding a custom color
310
     *
311
     * @param string $rgb E.g. 'FF00AA'
312
     * @return int Color index
313
     */
314
    private function addColor($rgb)
315
    {
316
        if (!isset($this->colors[$rgb])) {
317
            if (count($this->colors) < 57) {
318
                // then we add a custom color altering the palette
319
                $colorIndex = 8 + count($this->colors);
320
                $this->palette[$colorIndex] =
321
                    [
322
                        hexdec(substr($rgb, 0, 2)),
323
                        hexdec(substr($rgb, 2, 2)),
324
                        hexdec(substr($rgb, 4)),
325
                        0,
326
                    ];
327
                $this->colors[$rgb] = $colorIndex;
328
            } else {
329
                // no room for more custom colors, just map to black
330
                $colorIndex = 0;
331
            }
332
        } else {
333
            // fetch already added custom color
334
            $colorIndex = $this->colors[$rgb];
335
        }
336
337
        return $colorIndex;
338
    }
339
340
    /**
341
     * Sets the colour palette to the Excel 97+ default.
342
     */
343
    private function setPaletteXl97()
344
    {
345
        $this->palette = [
346
            0x08 => [0x00, 0x00, 0x00, 0x00],
347
            0x09 => [0xff, 0xff, 0xff, 0x00],
348
            0x0A => [0xff, 0x00, 0x00, 0x00],
349
            0x0B => [0x00, 0xff, 0x00, 0x00],
350
            0x0C => [0x00, 0x00, 0xff, 0x00],
351
            0x0D => [0xff, 0xff, 0x00, 0x00],
352
            0x0E => [0xff, 0x00, 0xff, 0x00],
353
            0x0F => [0x00, 0xff, 0xff, 0x00],
354
            0x10 => [0x80, 0x00, 0x00, 0x00],
355
            0x11 => [0x00, 0x80, 0x00, 0x00],
356
            0x12 => [0x00, 0x00, 0x80, 0x00],
357
            0x13 => [0x80, 0x80, 0x00, 0x00],
358
            0x14 => [0x80, 0x00, 0x80, 0x00],
359
            0x15 => [0x00, 0x80, 0x80, 0x00],
360
            0x16 => [0xc0, 0xc0, 0xc0, 0x00],
361
            0x17 => [0x80, 0x80, 0x80, 0x00],
362
            0x18 => [0x99, 0x99, 0xff, 0x00],
363
            0x19 => [0x99, 0x33, 0x66, 0x00],
364
            0x1A => [0xff, 0xff, 0xcc, 0x00],
365
            0x1B => [0xcc, 0xff, 0xff, 0x00],
366
            0x1C => [0x66, 0x00, 0x66, 0x00],
367
            0x1D => [0xff, 0x80, 0x80, 0x00],
368
            0x1E => [0x00, 0x66, 0xcc, 0x00],
369
            0x1F => [0xcc, 0xcc, 0xff, 0x00],
370
            0x20 => [0x00, 0x00, 0x80, 0x00],
371
            0x21 => [0xff, 0x00, 0xff, 0x00],
372
            0x22 => [0xff, 0xff, 0x00, 0x00],
373
            0x23 => [0x00, 0xff, 0xff, 0x00],
374
            0x24 => [0x80, 0x00, 0x80, 0x00],
375
            0x25 => [0x80, 0x00, 0x00, 0x00],
376
            0x26 => [0x00, 0x80, 0x80, 0x00],
377
            0x27 => [0x00, 0x00, 0xff, 0x00],
378
            0x28 => [0x00, 0xcc, 0xff, 0x00],
379
            0x29 => [0xcc, 0xff, 0xff, 0x00],
380
            0x2A => [0xcc, 0xff, 0xcc, 0x00],
381
            0x2B => [0xff, 0xff, 0x99, 0x00],
382
            0x2C => [0x99, 0xcc, 0xff, 0x00],
383
            0x2D => [0xff, 0x99, 0xcc, 0x00],
384
            0x2E => [0xcc, 0x99, 0xff, 0x00],
385
            0x2F => [0xff, 0xcc, 0x99, 0x00],
386
            0x30 => [0x33, 0x66, 0xff, 0x00],
387
            0x31 => [0x33, 0xcc, 0xcc, 0x00],
388
            0x32 => [0x99, 0xcc, 0x00, 0x00],
389
            0x33 => [0xff, 0xcc, 0x00, 0x00],
390
            0x34 => [0xff, 0x99, 0x00, 0x00],
391
            0x35 => [0xff, 0x66, 0x00, 0x00],
392
            0x36 => [0x66, 0x66, 0x99, 0x00],
393
            0x37 => [0x96, 0x96, 0x96, 0x00],
394
            0x38 => [0x00, 0x33, 0x66, 0x00],
395
            0x39 => [0x33, 0x99, 0x66, 0x00],
396
            0x3A => [0x00, 0x33, 0x00, 0x00],
397
            0x3B => [0x33, 0x33, 0x00, 0x00],
398
            0x3C => [0x99, 0x33, 0x00, 0x00],
399
            0x3D => [0x99, 0x33, 0x66, 0x00],
400
            0x3E => [0x33, 0x33, 0x99, 0x00],
401
            0x3F => [0x33, 0x33, 0x33, 0x00],
402
        ];
403
    }
404
405
    /**
406
     * Assemble worksheets into a workbook and send the BIFF data to an OLE
407
     * storage.
408
     *
409
     * @param    array    $pWorksheetSizes    The sizes in bytes of the binary worksheet streams
410
     * @return    string    Binary data for workbook stream
411
     */
412
    public function writeWorkbook($pWorksheetSizes = null)
413
    {
414
        $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...
415
416
        // Calculate the number of selected worksheet tabs and call the finalization
417
        // methods for each worksheet
418
        $total_worksheets = $this->spreadsheet->getSheetCount();
419
420
        // Add part 1 of the Workbook globals, what goes before the SHEET records
421
        $this->storeBof(0x0005);
422
        $this->writeCodepage();
423
        $this->writeWindow1();
424
425
        $this->writeDateMode();
426
        $this->writeAllFonts();
427
        $this->writeAllNumberFormats();
428
        $this->writeAllXfs();
429
        $this->writeAllStyles();
430
        $this->writePalette();
431
432
        // Prepare part 3 of the workbook global stream, what goes after the SHEET records
433
        $part3 = '';
434
        if ($this->countryCode != -1) {
435
            $part3 .= $this->writeCountry();
436
        }
437
        $part3 .= $this->writeRecalcId();
438
439
        $part3 .= $this->writeSupbookInternal();
440
        /* TODO: store external SUPBOOK records and XCT and CRN records
441
        in case of external references for BIFF8 */
442
        $part3 .= $this->writeExternalsheetBiff8();
443
        $part3 .= $this->writeAllDefinedNamesBiff8();
444
        $part3 .= $this->writeMsoDrawingGroup();
445
        $part3 .= $this->writeSharedStringsTable();
446
447
        $part3 .= $this->writeEof();
448
449
        // Add part 2 of the Workbook globals, the SHEET records
450
        $this->calcSheetOffsets();
451
        for ($i = 0; $i < $total_worksheets; ++$i) {
452
            $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
453
        }
454
455
        // Add part 3 of the Workbook globals
456
        $this->_data .= $part3;
457
458
        return $this->_data;
459
    }
460
461
    /**
462
     * Calculate offsets for Worksheet BOF records.
463
     */
464
    private function calcSheetOffsets()
465
    {
466
        $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
467
468
        // size of Workbook globals part 1 + 3
469
        $offset = $this->_datasize;
470
471
        // add size of Workbook globals part 2, the length of the SHEET records
472
        $total_worksheets = count($this->spreadsheet->getAllSheets());
473
        foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
474
            $offset += $boundsheet_length + strlen(\PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
475
        }
476
477
        // add the sizes of each of the Sheet substreams, respectively
478
        for ($i = 0; $i < $total_worksheets; ++$i) {
479
            $this->worksheetOffsets[$i] = $offset;
480
            $offset += $this->worksheetSizes[$i];
481
        }
482
        $this->biffSize = $offset;
483
    }
484
485
    /**
486
     * Store the Excel FONT records.
487
     */
488
    private function writeAllFonts()
489
    {
490
        foreach ($this->fontWriters as $fontWriter) {
491
            $this->append($fontWriter->writeFont());
492
        }
493
    }
494
495
    /**
496
     * Store user defined numerical formats i.e. FORMAT records
497
     */
498
    private function writeAllNumberFormats()
499
    {
500
        foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
501
            $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
502
        }
503
    }
504
505
    /**
506
     * Write all XF records.
507
     */
508
    private function writeAllXfs()
509
    {
510
        foreach ($this->xfWriters as $xfWriter) {
511
            $this->append($xfWriter->writeXf());
512
        }
513
    }
514
515
    /**
516
     * Write all STYLE records.
517
     */
518
    private function writeAllStyles()
519
    {
520
        $this->writeStyle();
521
    }
522
523
    /**
524
     * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
525
     * the NAME records.
526
     */
527
    private function writeExternals()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
528
    {
529
        $countSheets = $this->spreadsheet->getSheetCount();
530
        // Create EXTERNCOUNT with number of worksheets
531
        $this->writeExternalCount($countSheets);
532
533
        // Create EXTERNSHEET for each worksheet
534
        for ($i = 0; $i < $countSheets; ++$i) {
535
            $this->writeExternalSheet($this->spreadsheet->getSheet($i)->getTitle());
536
        }
537
    }
538
539
    /**
540
     * Write the NAME record to define the print area and the repeat rows and cols.
541
     */
542
    private function writeNames()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
543
    {
544
        // total number of sheets
545
        $total_worksheets = $this->spreadsheet->getSheetCount();
546
547
        // Create the print area NAME records
548
        for ($i = 0; $i < $total_worksheets; ++$i) {
549
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
550
            // Write a Name record if the print area has been defined
551
            if ($sheetSetup->isPrintAreaSet()) {
552
                // Print area
553
                $printArea = \PhpOffice\PhpSpreadsheet\Cell::splitRange($sheetSetup->getPrintArea());
554
                $printArea = $printArea[0];
555
                $printArea[0] = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($printArea[0]);
556
                $printArea[1] = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($printArea[1]);
557
558
                $print_rowmin = $printArea[0][1] - 1;
559
                $print_rowmax = $printArea[1][1] - 1;
560
                $print_colmin = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($printArea[0][0]) - 1;
561
                $print_colmax = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($printArea[1][0]) - 1;
562
563
                $this->writeNameShort(
564
                    $i, // sheet index
565
                    0x06, // NAME type
566
                    $print_rowmin,
567
                    $print_rowmax,
568
                    $print_colmin,
569
                    $print_colmax
570
                );
571
            }
572
        }
573
574
        // Create the print title NAME records
575
        for ($i = 0; $i < $total_worksheets; ++$i) {
576
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
577
578
            // simultaneous repeatColumns repeatRows
579
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
580
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
581
                $colmin = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
582
                $colmax = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
583
584
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
585
                $rowmin = $repeat[0] - 1;
586
                $rowmax = $repeat[1] - 1;
587
588
                $this->writeNameLong(
589
                    $i, // sheet index
590
                    0x07, // NAME type
591
                    $rowmin,
592
                    $rowmax,
593
                    $colmin,
594
                    $colmax
595
                );
596
597
            // (exclusive) either repeatColumns or repeatRows
598
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
599
                // Columns to repeat
600 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...
601
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
602
                    $colmin = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
603
                    $colmax = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
604
                } else {
605
                    $colmin = 0;
606
                    $colmax = 255;
607
                }
608
609
                // Rows to repeat
610 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...
611
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
612
                    $rowmin = $repeat[0] - 1;
613
                    $rowmax = $repeat[1] - 1;
614
                } else {
615
                    $rowmin = 0;
616
                    $rowmax = 65535;
617
                }
618
619
                $this->writeNameShort(
620
                    $i, // sheet index
621
                    0x07, // NAME type
622
                    $rowmin,
623
                    $rowmax,
624
                    $colmin,
625
                    $colmax
626
                );
627
            }
628
        }
629
    }
630
631
    /**
632
     * Writes all the DEFINEDNAME records (BIFF8).
633
     * So far this is only used for repeating rows/columns (print titles) and print areas
634
     */
635
    private function writeAllDefinedNamesBiff8()
636
    {
637
        $chunk = '';
638
639
        // Named ranges
640
        if (count($this->spreadsheet->getNamedRanges()) > 0) {
641
            // Loop named ranges
642
            $namedRanges = $this->spreadsheet->getNamedRanges();
643
            foreach ($namedRanges as $namedRange) {
644
                // Create absolute coordinate
645
                $range = \PhpOffice\PhpSpreadsheet\Cell::splitRange($namedRange->getRange());
646 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...
647
                    $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . \PhpOffice\PhpSpreadsheet\Cell::absoluteCoordinate($range[$i][0]);
648
                    if (isset($range[$i][1])) {
649
                        $range[$i][1] = \PhpOffice\PhpSpreadsheet\Cell::absoluteCoordinate($range[$i][1]);
650
                    }
651
                }
652
                $range = \PhpOffice\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...
653
654
                // parse formula
655
                try {
656
                    $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...
657
                    $formulaData = $this->parser->toReversePolish();
658
659
                    // make sure tRef3d is of type tRef3dR (0x3A)
660
                    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...
661
                        $formulaData = "\x3A" . substr($formulaData, 1);
662
                    }
663
664
                    if ($namedRange->getLocalOnly()) {
665
                        // local scope
666
                        $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...
667
                    } else {
668
                        // global scope
669
                        $scope = 0;
670
                    }
671
                    $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
672
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
673
                    // do nothing
674
                }
675
            }
676
        }
677
678
        // total number of sheets
679
        $total_worksheets = $this->spreadsheet->getSheetCount();
680
681
        // write the print titles (repeating rows, columns), if any
682
        for ($i = 0; $i < $total_worksheets; ++$i) {
683
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
684
            // simultaneous repeatColumns repeatRows
685
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
686
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
687
                $colmin = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
688
                $colmax = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
689
690
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
691
                $rowmin = $repeat[0] - 1;
692
                $rowmax = $repeat[1] - 1;
693
694
                // construct formula data manually
695
                $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
696
                $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
697
                $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
698
                $formulaData .= pack('C', 0x10); // tList
699
700
                // store the DEFINEDNAME record
701
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
702
703
            // (exclusive) either repeatColumns or repeatRows
704
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
705
                // Columns to repeat
706 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...
707
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
708
                    $colmin = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[0]) - 1;
709
                    $colmax = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($repeat[1]) - 1;
710
                } else {
711
                    $colmin = 0;
712
                    $colmax = 255;
713
                }
714
                // Rows to repeat
715 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...
716
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
717
                    $rowmin = $repeat[0] - 1;
718
                    $rowmax = $repeat[1] - 1;
719
                } else {
720
                    $rowmin = 0;
721
                    $rowmax = 65535;
722
                }
723
724
                // construct formula data manually because parser does not recognize absolute 3d cell references
725
                $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
726
727
                // store the DEFINEDNAME record
728
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
729
            }
730
        }
731
732
        // write the print areas, if any
733
        for ($i = 0; $i < $total_worksheets; ++$i) {
734
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
735
            if ($sheetSetup->isPrintAreaSet()) {
736
                // Print area, e.g. A3:J6,H1:X20
737
                $printArea = \PhpOffice\PhpSpreadsheet\Cell::splitRange($sheetSetup->getPrintArea());
738
                $countPrintArea = count($printArea);
739
740
                $formulaData = '';
741
                for ($j = 0; $j < $countPrintArea; ++$j) {
742
                    $printAreaRect = $printArea[$j]; // e.g. A3:J6
743
                    $printAreaRect[0] = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($printAreaRect[0]);
744
                    $printAreaRect[1] = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($printAreaRect[1]);
745
746
                    $print_rowmin = $printAreaRect[0][1] - 1;
747
                    $print_rowmax = $printAreaRect[1][1] - 1;
748
                    $print_colmin = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($printAreaRect[0][0]) - 1;
749
                    $print_colmax = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($printAreaRect[1][0]) - 1;
750
751
                    // construct formula data manually because parser does not recognize absolute 3d cell references
752
                    $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
753
754
                    if ($j > 0) {
755
                        $formulaData .= pack('C', 0x10); // list operator token ','
756
                    }
757
                }
758
759
                // store the DEFINEDNAME record
760
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
761
            }
762
        }
763
764
        // write autofilters, if any
765
        for ($i = 0; $i < $total_worksheets; ++$i) {
766
            $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
767
            $autoFilterRange = $sheetAutoFilter->getRange();
768
            if (!empty($autoFilterRange)) {
769
                $rangeBounds = \PhpOffice\PhpSpreadsheet\Cell::rangeBoundaries($autoFilterRange);
770
771
                //Autofilter built in name
772
                $name = pack('C', 0x0D);
773
774
                $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
775
            }
776
        }
777
778
        return $chunk;
779
    }
780
781
    /**
782
     * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data
783
     *
784
     * @param    string        $name            The name in UTF-8
785
     * @param    string        $formulaData    The binary formula data
786
     * @param    string        $sheetIndex        1-based sheet index the defined name applies to. 0 = global
787
     * @param    bool        $isBuiltIn        Built-in name?
788
     * @return    string    Complete binary record data
789
     */
790
    private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
791
    {
792
        $record = 0x0018;
793
794
        // option flags
795
        $options = $isBuiltIn ? 0x20 : 0x00;
796
797
        // length of the name, character count
798
        $nlen = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::countCharacters($name);
799
800
        // name with stripped length field
801
        $name = substr(\PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
802
803
        // size of the formula (in bytes)
804
        $sz = strlen($formulaData);
805
806
        // combine the parts
807
        $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
808
            . $name . $formulaData;
809
        $length = strlen($data);
810
811
        $header = pack('vv', $record, $length);
812
813
        return $header . $data;
814
    }
815
816
    /**
817
     * Write a short NAME record
818
     *
819
     * @param    string         $name
820
     * @param    string         $sheetIndex        1-based sheet index the defined name applies to. 0 = global
821
     * @param    integer[][]  $rangeBounds    range boundaries
822
     * @param    bool      $isHidden
823
     * @return    string    Complete binary record data
824
     * */
825
    private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
826
    {
827
        $record = 0x0018;
828
829
        // option flags
830
        $options = ($isHidden ? 0x21 : 0x00);
831
832
        $extra = pack(
833
            'Cvvvvv',
834
            0x3B,
835
            $sheetIndex - 1,
836
            $rangeBounds[0][1] - 1,
837
            $rangeBounds[1][1] - 1,
838
            $rangeBounds[0][0] - 1,
839
            $rangeBounds[1][0] - 1
840
        );
841
842
        // size of the formula (in bytes)
843
        $sz = strlen($extra);
844
845
        // combine the parts
846
        $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
847
            . $name . $extra;
848
        $length = strlen($data);
849
850
        $header = pack('vv', $record, $length);
851
852
        return $header . $data;
853
    }
854
855
    /**
856
     * Stores the CODEPAGE biff record.
857
     */
858
    private function writeCodepage()
859
    {
860
        $record = 0x0042; // Record identifier
861
        $length = 0x0002; // Number of bytes to follow
862
        $cv = $this->codepage; // The code page
863
864
        $header = pack('vv', $record, $length);
865
        $data = pack('v', $cv);
866
867
        $this->append($header . $data);
868
    }
869
870
    /**
871
     * Write Excel BIFF WINDOW1 record.
872
     */
873
    private function writeWindow1()
874
    {
875
        $record = 0x003D; // Record identifier
876
        $length = 0x0012; // Number of bytes to follow
877
878
        $xWn = 0x0000; // Horizontal position of window
879
        $yWn = 0x0000; // Vertical position of window
880
        $dxWn = 0x25BC; // Width of window
881
        $dyWn = 0x1572; // Height of window
882
883
        $grbit = 0x0038; // Option flags
884
885
        // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
886
        $ctabsel = 1; // Number of workbook tabs selected
887
888
        $wTabRatio = 0x0258; // Tab to scrollbar ratio
889
890
        // not supported by PhpSpreadsheet, set to 0
891
        $itabFirst = 0; // 1st displayed worksheet
892
        $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
893
894
        $header = pack('vv', $record, $length);
895
        $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
896
        $this->append($header . $data);
897
    }
898
899
    /**
900
     * Writes Excel BIFF BOUNDSHEET record.
901
     *
902
     * @param \PhpOffice\PhpSpreadsheet\Worksheet  $sheet Worksheet name
903
     * @param int $offset    Location of worksheet BOF
904
     */
905
    private function writeBoundSheet($sheet, $offset)
906
    {
907
        $sheetname = $sheet->getTitle();
908
        $record = 0x0085; // Record identifier
909
910
        // sheet state
911
        switch ($sheet->getSheetState()) {
912
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE:
913
                $ss = 0x00;
914
                break;
915
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_HIDDEN:
916
                $ss = 0x01;
917
                break;
918
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VERYHIDDEN:
919
                $ss = 0x02;
920
                break;
921
            default:
922
                $ss = 0x00;
923
                break;
924
        }
925
926
        // sheet type
927
        $st = 0x00;
928
929
        $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...
930
931
        $data = pack('VCC', $offset, $ss, $st);
932
        $data .= \PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
933
934
        $length = strlen($data);
935
        $header = pack('vv', $record, $length);
936
        $this->append($header . $data);
937
    }
938
939
    /**
940
     * Write Internal SUPBOOK record
941
     */
942 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...
943
    {
944
        $record = 0x01AE; // Record identifier
945
        $length = 0x0004; // Bytes to follow
946
947
        $header = pack('vv', $record, $length);
948
        $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
949
950
        return $this->writeData($header . $data);
951
    }
952
953
    /**
954
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
955
     * formulas.
956
     */
957
    private function writeExternalsheetBiff8()
958
    {
959
        $totalReferences = count($this->parser->references);
960
        $record = 0x0017; // Record identifier
961
        $length = 2 + 6 * $totalReferences; // Number of bytes to follow
962
963
        $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...
964
        $header = pack('vv', $record, $length);
965
        $data = pack('v', $totalReferences);
966
        for ($i = 0; $i < $totalReferences; ++$i) {
967
            $data .= $this->parser->references[$i];
968
        }
969
970
        return $this->writeData($header . $data);
971
    }
972
973
    /**
974
     * Write Excel BIFF STYLE records.
975
     */
976
    private function writeStyle()
977
    {
978
        $record = 0x0293; // Record identifier
979
        $length = 0x0004; // Bytes to follow
980
981
        $ixfe = 0x8000; // Index to cell style XF
982
        $BuiltIn = 0x00; // Built-in style
983
        $iLevel = 0xff; // Outline style level
984
985
        $header = pack('vv', $record, $length);
986
        $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
987
        $this->append($header . $data);
988
    }
989
990
    /**
991
     * Writes Excel FORMAT record for non "built-in" numerical formats.
992
     *
993
     * @param string  $format Custom format string
994
     * @param int $ifmt   Format index code
995
     */
996 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...
997
    {
998
        $record = 0x041E; // Record identifier
999
1000
        $numberFormatString = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeLong($format);
1001
        $length = 2 + strlen($numberFormatString); // Number of bytes to follow
1002
1003
        $header = pack('vv', $record, $length);
1004
        $data = pack('v', $ifmt) . $numberFormatString;
1005
        $this->append($header . $data);
1006
    }
1007
1008
    /**
1009
     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
1010
     */
1011
    private function writeDateMode()
1012
    {
1013
        $record = 0x0022; // Record identifier
1014
        $length = 0x0002; // Bytes to follow
1015
1016
        $f1904 = (\PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar() == \PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904)
1017
            ? 1
1018
            : 0; // Flag for 1904 date system
1019
1020
        $header = pack('vv', $record, $length);
1021
        $data = pack('v', $f1904);
1022
        $this->append($header . $data);
1023
    }
1024
1025
    /**
1026
     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
1027
     * references in the workbook.
1028
     *
1029
     * Excel only stores references to external sheets that are used in NAME.
1030
     * The workbook NAME record is required to define the print area and the repeat
1031
     * rows and columns.
1032
     *
1033
     * A similar method is used in Worksheet.php for a slightly different purpose.
1034
     *
1035
     * @param int $cxals Number of external references
1036
     */
1037
    private function writeExternalCount($cxals)
1038
    {
1039
        $record = 0x0016; // Record identifier
1040
        $length = 0x0002; // Number of bytes to follow
1041
1042
        $header = pack('vv', $record, $length);
1043
        $data = pack('v', $cxals);
1044
        $this->append($header . $data);
1045
    }
1046
1047
    /**
1048
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
1049
     * formulas. NAME record is required to define the print area and the repeat
1050
     * rows and columns.
1051
     *
1052
     * A similar method is used in Worksheet.php for a slightly different purpose.
1053
     *
1054
     * @param string $sheetname Worksheet name
1055
     */
1056 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...
1057
    {
1058
        $record = 0x0017; // Record identifier
1059
        $length = 0x02 + strlen($sheetname); // Number of bytes to follow
1060
1061
        $cch = strlen($sheetname); // Length of sheet name
1062
        $rgch = 0x03; // Filename encoding
1063
1064
        $header = pack('vv', $record, $length);
1065
        $data = pack('CC', $cch, $rgch);
1066
        $this->append($header . $data . $sheetname);
1067
    }
1068
1069
    /**
1070
     * Store the NAME record in the short format that is used for storing the print
1071
     * area, repeat rows only and repeat columns only.
1072
     *
1073
     * @param int $index  Sheet index
1074
     * @param int $type   Built-in name type
1075
     * @param int $rowmin Start row
1076
     * @param int $rowmax End row
1077
     * @param int $colmin Start colum
1078
     * @param int $colmax End column
1079
     */
1080
    private function writeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
1081
    {
1082
        $record = 0x0018; // Record identifier
1083
        $length = 0x0024; // Number of bytes to follow
1084
1085
        $grbit = 0x0020; // Option flags
1086
        $chKey = 0x00; // Keyboard shortcut
1087
        $cch = 0x01; // Length of text name
1088
        $cce = 0x0015; // Length of text definition
1089
        $ixals = $index + 1; // Sheet index
1090
        $itab = $ixals; // Equal to ixals
1091
        $cchCustMenu = 0x00; // Length of cust menu text
1092
        $cchDescription = 0x00; // Length of description text
1093
        $cchHelptopic = 0x00; // Length of help topic text
1094
        $cchStatustext = 0x00; // Length of status bar text
1095
        $rgch = $type; // Built-in name type
1096
1097
        $unknown03 = 0x3b;
1098
        $unknown04 = 0xffff - $index;
1099
        $unknown05 = 0x0000;
1100
        $unknown06 = 0x0000;
1101
        $unknown07 = 0x1087;
1102
        $unknown08 = 0x8005;
1103
1104
        $header = pack('vv', $record, $length);
1105
        $data = pack('v', $grbit);
1106
        $data .= pack('C', $chKey);
1107
        $data .= pack('C', $cch);
1108
        $data .= pack('v', $cce);
1109
        $data .= pack('v', $ixals);
1110
        $data .= pack('v', $itab);
1111
        $data .= pack('C', $cchCustMenu);
1112
        $data .= pack('C', $cchDescription);
1113
        $data .= pack('C', $cchHelptopic);
1114
        $data .= pack('C', $cchStatustext);
1115
        $data .= pack('C', $rgch);
1116
        $data .= pack('C', $unknown03);
1117
        $data .= pack('v', $unknown04);
1118
        $data .= pack('v', $unknown05);
1119
        $data .= pack('v', $unknown06);
1120
        $data .= pack('v', $unknown07);
1121
        $data .= pack('v', $unknown08);
1122
        $data .= pack('v', $index);
1123
        $data .= pack('v', $index);
1124
        $data .= pack('v', $rowmin);
1125
        $data .= pack('v', $rowmax);
1126
        $data .= pack('C', $colmin);
1127
        $data .= pack('C', $colmax);
1128
        $this->append($header . $data);
1129
    }
1130
1131
    /**
1132
     * Store the NAME record in the long format that is used for storing the repeat
1133
     * rows and columns when both are specified. This shares a lot of code with
1134
     * writeNameShort() but we use a separate method to keep the code clean.
1135
     * Code abstraction for reuse can be carried too far, and I should know. ;-)
1136
     *
1137
     * @param int $index Sheet index
1138
     * @param int $type  Built-in name type
1139
     * @param int $rowmin Start row
1140
     * @param int $rowmax End row
1141
     * @param int $colmin Start colum
1142
     * @param int $colmax End column
1143
     */
1144
    private function writeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
1145
    {
1146
        $record = 0x0018; // Record identifier
1147
        $length = 0x003d; // Number of bytes to follow
1148
        $grbit = 0x0020; // Option flags
1149
        $chKey = 0x00; // Keyboard shortcut
1150
        $cch = 0x01; // Length of text name
1151
        $cce = 0x002e; // Length of text definition
1152
        $ixals = $index + 1; // Sheet index
1153
        $itab = $ixals; // Equal to ixals
1154
        $cchCustMenu = 0x00; // Length of cust menu text
1155
        $cchDescription = 0x00; // Length of description text
1156
        $cchHelptopic = 0x00; // Length of help topic text
1157
        $cchStatustext = 0x00; // Length of status bar text
1158
        $rgch = $type; // Built-in name type
1159
1160
        $unknown01 = 0x29;
1161
        $unknown02 = 0x002b;
1162
        $unknown03 = 0x3b;
1163
        $unknown04 = 0xffff - $index;
1164
        $unknown05 = 0x0000;
1165
        $unknown06 = 0x0000;
1166
        $unknown07 = 0x1087;
1167
        $unknown08 = 0x8008;
1168
1169
        $header = pack('vv', $record, $length);
1170
        $data = pack('v', $grbit);
1171
        $data .= pack('C', $chKey);
1172
        $data .= pack('C', $cch);
1173
        $data .= pack('v', $cce);
1174
        $data .= pack('v', $ixals);
1175
        $data .= pack('v', $itab);
1176
        $data .= pack('C', $cchCustMenu);
1177
        $data .= pack('C', $cchDescription);
1178
        $data .= pack('C', $cchHelptopic);
1179
        $data .= pack('C', $cchStatustext);
1180
        $data .= pack('C', $rgch);
1181
        $data .= pack('C', $unknown01);
1182
        $data .= pack('v', $unknown02);
1183
        // Column definition
1184
        $data .= pack('C', $unknown03);
1185
        $data .= pack('v', $unknown04);
1186
        $data .= pack('v', $unknown05);
1187
        $data .= pack('v', $unknown06);
1188
        $data .= pack('v', $unknown07);
1189
        $data .= pack('v', $unknown08);
1190
        $data .= pack('v', $index);
1191
        $data .= pack('v', $index);
1192
        $data .= pack('v', 0x0000);
1193
        $data .= pack('v', 0x3fff);
1194
        $data .= pack('C', $colmin);
1195
        $data .= pack('C', $colmax);
1196
        // Row definition
1197
        $data .= pack('C', $unknown03);
1198
        $data .= pack('v', $unknown04);
1199
        $data .= pack('v', $unknown05);
1200
        $data .= pack('v', $unknown06);
1201
        $data .= pack('v', $unknown07);
1202
        $data .= pack('v', $unknown08);
1203
        $data .= pack('v', $index);
1204
        $data .= pack('v', $index);
1205
        $data .= pack('v', $rowmin);
1206
        $data .= pack('v', $rowmax);
1207
        $data .= pack('C', 0x00);
1208
        $data .= pack('C', 0xff);
1209
        // End of data
1210
        $data .= pack('C', 0x10);
1211
        $this->append($header . $data);
1212
    }
1213
1214
    /**
1215
     * Stores the COUNTRY record for localization
1216
     *
1217
     * @return string
1218
     */
1219 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...
1220
    {
1221
        $record = 0x008C; // Record identifier
1222
        $length = 4; // Number of bytes to follow
1223
1224
        $header = pack('vv', $record, $length);
1225
        /* using the same country code always for simplicity */
1226
        $data = pack('vv', $this->countryCode, $this->countryCode);
1227
1228
        return $this->writeData($header . $data);
1229
    }
1230
1231
    /**
1232
     * Write the RECALCID record
1233
     *
1234
     * @return string
1235
     */
1236 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...
1237
    {
1238
        $record = 0x01C1; // Record identifier
1239
        $length = 8; // Number of bytes to follow
1240
1241
        $header = pack('vv', $record, $length);
1242
1243
        // by inspection of real Excel files, MS Office Excel 2007 writes this
1244
        $data = pack('VV', 0x000001C1, 0x00001E667);
1245
1246
        return $this->writeData($header . $data);
1247
    }
1248
1249
    /**
1250
     * Stores the PALETTE biff record.
1251
     */
1252
    private function writePalette()
1253
    {
1254
        $aref = $this->palette;
1255
1256
        $record = 0x0092; // Record identifier
1257
        $length = 2 + 4 * count($aref); // Number of bytes to follow
1258
        $ccv = count($aref); // Number of RGB values to follow
1259
        $data = ''; // The RGB data
1260
1261
        // Pack the RGB data
1262
        foreach ($aref as $color) {
1263
            foreach ($color as $byte) {
1264
                $data .= pack('C', $byte);
1265
            }
1266
        }
1267
1268
        $header = pack('vvv', $record, $length, $ccv);
1269
        $this->append($header . $data);
1270
    }
1271
1272
    /**
1273
     * Handling of the SST continue blocks is complicated by the need to include an
1274
     * additional continuation byte depending on whether the string is split between
1275
     * blocks or whether it starts at the beginning of the block. (There are also
1276
     * additional complications that will arise later when/if Rich Strings are
1277
     * supported).
1278
     *
1279
     * The Excel documentation says that the SST record should be followed by an
1280
     * EXTSST record. The EXTSST record is a hash table that is used to optimise
1281
     * access to SST. However, despite the documentation it doesn't seem to be
1282
     * required so we will ignore it.
1283
     *
1284
     * @return string Binary data
1285
     */
1286
    private function writeSharedStringsTable()
1287
    {
1288
        // maximum size of record data (excluding record header)
1289
        $continue_limit = 8224;
1290
1291
        // initialize array of record data blocks
1292
        $recordDatas = [];
1293
1294
        // start SST record data block with total number of strings, total number of unique strings
1295
        $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
1296
1297
        // loop through all (unique) strings in shared strings table
1298
        foreach (array_keys($this->stringTable) as $string) {
1299
            // here $string is a BIFF8 encoded string
1300
1301
            // length = character count
1302
            $headerinfo = unpack('vlength/Cencoding', $string);
1303
1304
            // currently, this is always 1 = uncompressed
1305
            $encoding = $headerinfo['encoding'];
1306
1307
            // initialize finished writing current $string
1308
            $finished = false;
1309
1310
            while ($finished === false) {
1311
                // normally, there will be only one cycle, but if string cannot immediately be written as is
1312
                // there will be need for more than one cylcle, if string longer than one record data block, there
1313
                // may be need for even more cycles
1314
1315
                if (strlen($recordData) + strlen($string) <= $continue_limit) {
1316
                    // then we can write the string (or remainder of string) without any problems
1317
                    $recordData .= $string;
1318
1319
                    if (strlen($recordData) + strlen($string) == $continue_limit) {
1320
                        // we close the record data block, and initialize a new one
1321
                        $recordDatas[] = $recordData;
1322
                        $recordData = '';
1323
                    }
1324
1325
                    // we are finished writing this string
1326
                    $finished = true;
1327
                } else {
1328
                    // special treatment writing the string (or remainder of the string)
1329
                    // If the string is very long it may need to be written in more than one CONTINUE record.
1330
1331
                    // check how many bytes more there is room for in the current record
1332
                    $space_remaining = $continue_limit - strlen($recordData);
1333
1334
                    // minimum space needed
1335
                    // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
1336
                    // compressed:   2 byte string length length field + 1 byte option flags + 1 byte character
1337
                    $min_space_needed = ($encoding == 1) ? 5 : 4;
1338
1339
                    // We have two cases
1340
                    // 1. space remaining is less than minimum space needed
1341
                    //        here we must waste the space remaining and move to next record data block
1342
                    // 2. space remaining is greater than or equal to minimum space needed
1343
                    //        here we write as much as we can in the current block, then move to next record data block
1344
1345
                    // 1. space remaining is less than minimum space needed
1346
                    if ($space_remaining < $min_space_needed) {
1347
                        // we close the block, store the block data
1348
                        $recordDatas[] = $recordData;
1349
1350
                        // and start new record data block where we start writing the string
1351
                        $recordData = '';
1352
1353
                    // 2. space remaining is greater than or equal to minimum space needed
1354
                    } else {
1355
                        // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
1356
                        $effective_space_remaining = $space_remaining;
1357
1358
                        // for uncompressed strings, sometimes effective space remaining is reduced by 1
1359
                        if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
1360
                            --$effective_space_remaining;
1361
                        }
1362
1363
                        // one block fininshed, store the block data
1364
                        $recordData .= substr($string, 0, $effective_space_remaining);
1365
1366
                        $string = substr($string, $effective_space_remaining); // for next cycle in while loop
1367
                        $recordDatas[] = $recordData;
1368
1369
                        // start new record data block with the repeated option flags
1370
                        $recordData = pack('C', $encoding);
1371
                    }
1372
                }
1373
            }
1374
        }
1375
1376
        // Store the last record data block unless it is empty
1377
        // if there was no need for any continue records, this will be the for SST record data block itself
1378
        if (strlen($recordData) > 0) {
1379
            $recordDatas[] = $recordData;
1380
        }
1381
1382
        // combine into one chunk with all the blocks SST, CONTINUE,...
1383
        $chunk = '';
1384
        foreach ($recordDatas as $i => $recordData) {
1385
            // first block should have the SST record header, remaing should have CONTINUE header
1386
            $record = ($i == 0) ? 0x00FC : 0x003C;
1387
1388
            $header = pack('vv', $record, strlen($recordData));
1389
            $data = $header . $recordData;
1390
1391
            $chunk .= $this->writeData($data);
1392
        }
1393
1394
        return $chunk;
1395
    }
1396
1397
    /**
1398
     * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
1399
     */
1400
    private function writeMsoDrawingGroup()
1401
    {
1402
        // write the Escher stream if necessary
1403
        if (isset($this->escher)) {
1404
            $writer = new \PhpOffice\PhpSpreadsheet\Writer\Excel5\Escher($this->escher);
1405
            $data = $writer->close();
1406
1407
            $record = 0x00EB;
1408
            $length = strlen($data);
1409
            $header = pack('vv', $record, $length);
1410
1411
            return $this->writeData($header . $data);
1412
        } else {
1413
            return '';
1414
        }
1415
    }
1416
1417
    /**
1418
     * Get Escher object
1419
     *
1420
     * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
1421
     */
1422
    public function getEscher()
1423
    {
1424
        return $this->escher;
1425
    }
1426
1427
    /**
1428
     * Set Escher object
1429
     *
1430
     * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
1431
     */
1432
    public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null)
1433
    {
1434
        $this->escher = $pValue;
1435
    }
1436
}
1437