Completed
Push — develop ( 39b55d...e2e982 )
by Adrien
19:14
created

Workbook::writeCodepage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
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\Xls\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\Xls\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\Xls\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 38
    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 38
        parent::__construct();
197
198 38
        $this->parser = $parser;
199 38
        $this->biffSize = 0;
200 38
        $this->palette = [];
201 38
        $this->countryCode = -1;
202
203 38
        $this->stringTotal = &$str_total;
204 38
        $this->stringUnique = &$str_unique;
205 38
        $this->stringTable = &$str_table;
206 38
        $this->colors = &$colors;
207 38
        $this->setPaletteXl97();
208
209 38
        $this->spreadsheet = $spreadsheet;
210
211 38
        $this->codepage = 0x04B0;
212
213
        // Add empty sheets and Build color cache
214 38
        $countSheets = $spreadsheet->getSheetCount();
215 38
        for ($i = 0; $i < $countSheets; ++$i) {
216 38
            $phpSheet = $spreadsheet->getSheet($i);
217
218 38
            $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
219
220 38
            $supbook_index = 0x00;
221 38
            $ref = pack('vvv', $supbook_index, $i, $i);
222 38
            $this->parser->references[] = $ref; // Register reference with parser
223
224
            // Sheet tab colors?
225 38
            if ($phpSheet->isTabColorSet()) {
226 5
                $this->addColor($phpSheet->getTabColor()->getRGB());
227
            }
228
        }
229 38
    }
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 38
    public function addXfWriter($style, $isStyleXf = false)
239
    {
240 38
        $xfWriter = new Xf($style);
241 38
        $xfWriter->setIsStyleXf($isStyleXf);
242
243
        // Add the font if not already added
244 38
        $fontIndex = $this->addFont($style->getFont());
245
246
        // Assign the font index to the xf record
247 38
        $xfWriter->setFontIndex($fontIndex);
248
249
        // Background colors, best to treat these after the font so black will come after white in custom palette
250 38
        $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
251 38
        $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
252 38
        $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
253 38
        $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
254 38
        $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
255 38
        $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
256 38
        $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 38
        if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
260 14
            $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
261
262 14
            if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
263 6
                $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
264
            } else {
265 14
                $numberFormatIndex = 164 + count($this->numberFormats);
266 14
                $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
267 14
                $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
268
            }
269
        } else {
270 38
            $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
271
        }
272
273
        // Assign the number format index to xf record
274 38
        $xfWriter->setNumberFormatIndex($numberFormatIndex);
275
276 38
        $this->xfWriters[] = $xfWriter;
277
278 38
        $xfIndex = count($this->xfWriters) - 1;
279
280 38
        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 38
    public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
290
    {
291 38
        $fontHashCode = $font->getHashCode();
292 38
        if (isset($this->addedFonts[$fontHashCode])) {
293 38
            $fontIndex = $this->addedFonts[$fontHashCode];
294
        } else {
295 38
            $countFonts = count($this->fontWriters);
296 38
            $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
297
298 38
            $fontWriter = new \PhpOffice\PhpSpreadsheet\Writer\Xls\Font($font);
299 38
            $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
300 38
            $this->fontWriters[] = $fontWriter;
301
302 38
            $this->addedFonts[$fontHashCode] = $fontIndex;
303
        }
304
305 38
        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 38
    private function addColor($rgb)
315
    {
316 38
        if (!isset($this->colors[$rgb])) {
317 38
            if (count($this->colors) < 57) {
318
                // then we add a custom color altering the palette
319 38
                $colorIndex = 8 + count($this->colors);
320 38
                $this->palette[$colorIndex] =
321
                    [
322 38
                        hexdec(substr($rgb, 0, 2)),
323 38
                        hexdec(substr($rgb, 2, 2)),
324 38
                        hexdec(substr($rgb, 4)),
325 38
                        0,
326
                    ];
327 38
                $this->colors[$rgb] = $colorIndex;
328
            } else {
329
                // no room for more custom colors, just map to black
330 38
                $colorIndex = 0;
331
            }
332
        } else {
333
            // fetch already added custom color
334 38
            $colorIndex = $this->colors[$rgb];
335
        }
336
337 38
        return $colorIndex;
338
    }
339
340
    /**
341
     * Sets the colour palette to the Excel 97+ default.
342
     */
343 38
    private function setPaletteXl97()
344
    {
345 38
        $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 38
    }
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 38
    public function writeWorkbook($pWorksheetSizes = null)
413
    {
414 38
        $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 38
        $total_worksheets = $this->spreadsheet->getSheetCount();
419
420
        // Add part 1 of the Workbook globals, what goes before the SHEET records
421 38
        $this->storeBof(0x0005);
422 38
        $this->writeCodepage();
423 38
        $this->writeWindow1();
424
425 38
        $this->writeDateMode();
426 38
        $this->writeAllFonts();
427 38
        $this->writeAllNumberFormats();
428 38
        $this->writeAllXfs();
429 38
        $this->writeAllStyles();
430 38
        $this->writePalette();
431
432
        // Prepare part 3 of the workbook global stream, what goes after the SHEET records
433 38
        $part3 = '';
434 38
        if ($this->countryCode != -1) {
435
            $part3 .= $this->writeCountry();
436
        }
437 38
        $part3 .= $this->writeRecalcId();
438
439 38
        $part3 .= $this->writeSupbookInternal();
440
        /* TODO: store external SUPBOOK records and XCT and CRN records
441
        in case of external references for BIFF8 */
442 38
        $part3 .= $this->writeExternalsheetBiff8();
443 38
        $part3 .= $this->writeAllDefinedNamesBiff8();
444 38
        $part3 .= $this->writeMsoDrawingGroup();
445 38
        $part3 .= $this->writeSharedStringsTable();
446
447 38
        $part3 .= $this->writeEof();
448
449
        // Add part 2 of the Workbook globals, the SHEET records
450 38
        $this->calcSheetOffsets();
451 38
        for ($i = 0; $i < $total_worksheets; ++$i) {
452 38
            $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
453
        }
454
455
        // Add part 3 of the Workbook globals
456 38
        $this->_data .= $part3;
457
458 38
        return $this->_data;
459
    }
460
461
    /**
462
     * Calculate offsets for Worksheet BOF records.
463
     */
464 38
    private function calcSheetOffsets()
465
    {
466 38
        $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
467
468
        // size of Workbook globals part 1 + 3
469 38
        $offset = $this->_datasize;
470
471
        // add size of Workbook globals part 2, the length of the SHEET records
472 38
        $total_worksheets = count($this->spreadsheet->getAllSheets());
473 38
        foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
474 38
            $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 38
        for ($i = 0; $i < $total_worksheets; ++$i) {
479 38
            $this->worksheetOffsets[$i] = $offset;
480 38
            $offset += $this->worksheetSizes[$i];
481
        }
482 38
        $this->biffSize = $offset;
483 38
    }
484
485
    /**
486
     * Store the Excel FONT records.
487
     */
488 38
    private function writeAllFonts()
489
    {
490 38
        foreach ($this->fontWriters as $fontWriter) {
491 38
            $this->append($fontWriter->writeFont());
492
        }
493 38
    }
494
495
    /**
496
     * Store user defined numerical formats i.e. FORMAT records
497
     */
498 38
    private function writeAllNumberFormats()
499
    {
500 38
        foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
501 14
            $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
502
        }
503 38
    }
504
505
    /**
506
     * Write all XF records.
507
     */
508 38
    private function writeAllXfs()
509
    {
510 38
        foreach ($this->xfWriters as $xfWriter) {
511 38
            $this->append($xfWriter->writeXf());
512
        }
513 38
    }
514
515
    /**
516
     * Write all STYLE records.
517
     */
518 38
    private function writeAllStyles()
519
    {
520 38
        $this->writeStyle();
521 38
    }
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 38
    private function writeAllDefinedNamesBiff8()
636
    {
637 38
        $chunk = '';
638
639
        // Named ranges
640 38
        if (count($this->spreadsheet->getNamedRanges()) > 0) {
641
            // Loop named ranges
642 4
            $namedRanges = $this->spreadsheet->getNamedRanges();
643 4
            foreach ($namedRanges as $namedRange) {
644
                // Create absolute coordinate
645 4
                $range = \PhpOffice\PhpSpreadsheet\Cell::splitRange($namedRange->getRange());
646 4 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 4
                    $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . \PhpOffice\PhpSpreadsheet\Cell::absoluteCoordinate($range[$i][0]);
648 4
                    if (isset($range[$i][1])) {
649 3
                        $range[$i][1] = \PhpOffice\PhpSpreadsheet\Cell::absoluteCoordinate($range[$i][1]);
650
                    }
651
                }
652 4
                $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 4
                    $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 4
                    $formulaData = $this->parser->toReversePolish();
658
659
                    // make sure tRef3d is of type tRef3dR (0x3A)
660 4
                    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 1
                        $formulaData = "\x3A" . substr($formulaData, 1);
662
                    }
663
664 4
                    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 4
                        $scope = 0;
670
                    }
671 4
                    $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
672 4
                } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
673
                    // do nothing
674
                }
675
            }
676
        }
677
678
        // total number of sheets
679 38
        $total_worksheets = $this->spreadsheet->getSheetCount();
680
681
        // write the print titles (repeating rows, columns), if any
682 38
        for ($i = 0; $i < $total_worksheets; ++$i) {
683 38
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
684
            // simultaneous repeatColumns repeatRows
685 38
            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 38
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
705
                // Columns to repeat
706 1 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 1
                    $colmin = 0;
712 1
                    $colmax = 255;
713
                }
714
                // Rows to repeat
715 1 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 1
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
717 1
                    $rowmin = $repeat[0] - 1;
718 1
                    $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 1
                $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
726
727
                // store the DEFINEDNAME record
728 1
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
729
            }
730
        }
731
732
        // write the print areas, if any
733 38
        for ($i = 0; $i < $total_worksheets; ++$i) {
734 38
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
735 38
            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 38
        for ($i = 0; $i < $total_worksheets; ++$i) {
766 38
            $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
767 38
            $autoFilterRange = $sheetAutoFilter->getRange();
768 38
            if (!empty($autoFilterRange)) {
769 3
                $rangeBounds = \PhpOffice\PhpSpreadsheet\Cell::rangeBoundaries($autoFilterRange);
770
771
                //Autofilter built in name
772 3
                $name = pack('C', 0x0D);
773
774 3
                $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
775
            }
776
        }
777
778 38
        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 5
    private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
791
    {
792 5
        $record = 0x0018;
793
794
        // option flags
795 5
        $options = $isBuiltIn ? 0x20 : 0x00;
796
797
        // length of the name, character count
798 5
        $nlen = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::countCharacters($name);
799
800
        // name with stripped length field
801 5
        $name = substr(\PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
802
803
        // size of the formula (in bytes)
804 5
        $sz = strlen($formulaData);
805
806
        // combine the parts
807 5
        $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
808 5
            . $name . $formulaData;
809 5
        $length = strlen($data);
810
811 5
        $header = pack('vv', $record, $length);
812
813 5
        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 3
    private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
826
    {
827 3
        $record = 0x0018;
828
829
        // option flags
830 3
        $options = ($isHidden ? 0x21 : 0x00);
831
832 3
        $extra = pack(
833 3
            'Cvvvvv',
834 3
            0x3B,
835 3
            $sheetIndex - 1,
836 3
            $rangeBounds[0][1] - 1,
837 3
            $rangeBounds[1][1] - 1,
838 3
            $rangeBounds[0][0] - 1,
839 3
            $rangeBounds[1][0] - 1
840
        );
841
842
        // size of the formula (in bytes)
843 3
        $sz = strlen($extra);
844
845
        // combine the parts
846 3
        $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
847 3
            . $name . $extra;
848 3
        $length = strlen($data);
849
850 3
        $header = pack('vv', $record, $length);
851
852 3
        return $header . $data;
853
    }
854
855
    /**
856
     * Stores the CODEPAGE biff record.
857
     */
858 38
    private function writeCodepage()
859
    {
860 38
        $record = 0x0042; // Record identifier
861 38
        $length = 0x0002; // Number of bytes to follow
862 38
        $cv = $this->codepage; // The code page
863
864 38
        $header = pack('vv', $record, $length);
865 38
        $data = pack('v', $cv);
866
867 38
        $this->append($header . $data);
868 38
    }
869
870
    /**
871
     * Write Excel BIFF WINDOW1 record.
872
     */
873 38
    private function writeWindow1()
874
    {
875 38
        $record = 0x003D; // Record identifier
876 38
        $length = 0x0012; // Number of bytes to follow
877
878 38
        $xWn = 0x0000; // Horizontal position of window
879 38
        $yWn = 0x0000; // Vertical position of window
880 38
        $dxWn = 0x25BC; // Width of window
881 38
        $dyWn = 0x1572; // Height of window
882
883 38
        $grbit = 0x0038; // Option flags
884
885
        // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
886 38
        $ctabsel = 1; // Number of workbook tabs selected
887
888 38
        $wTabRatio = 0x0258; // Tab to scrollbar ratio
889
890
        // not supported by PhpSpreadsheet, set to 0
891 38
        $itabFirst = 0; // 1st displayed worksheet
892 38
        $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
893
894 38
        $header = pack('vv', $record, $length);
895 38
        $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
896 38
        $this->append($header . $data);
897 38
    }
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 38
    private function writeBoundSheet($sheet, $offset)
906
    {
907 38
        $sheetname = $sheet->getTitle();
908 38
        $record = 0x0085; // Record identifier
909
910
        // sheet state
911 38
        switch ($sheet->getSheetState()) {
912 38
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE:
913 38
                $ss = 0x00;
914 38
                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 38
        $st = 0x00;
928
929 38
        $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 38
        $data = pack('VCC', $offset, $ss, $st);
932 38
        $data .= \PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
933
934 38
        $length = strlen($data);
935 38
        $header = pack('vv', $record, $length);
936 38
        $this->append($header . $data);
937 38
    }
938
939
    /**
940
     * Write Internal SUPBOOK record
941
     */
942 38 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 38
        $record = 0x01AE; // Record identifier
945 38
        $length = 0x0004; // Bytes to follow
946
947 38
        $header = pack('vv', $record, $length);
948 38
        $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
949
950 38
        return $this->writeData($header . $data);
951
    }
952
953
    /**
954
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
955
     * formulas.
956
     */
957 38
    private function writeExternalsheetBiff8()
958
    {
959 38
        $totalReferences = count($this->parser->references);
960 38
        $record = 0x0017; // Record identifier
961 38
        $length = 2 + 6 * $totalReferences; // Number of bytes to follow
962
963 38
        $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 38
        $header = pack('vv', $record, $length);
965 38
        $data = pack('v', $totalReferences);
966 38
        for ($i = 0; $i < $totalReferences; ++$i) {
967 38
            $data .= $this->parser->references[$i];
968
        }
969
970 38
        return $this->writeData($header . $data);
971
    }
972
973
    /**
974
     * Write Excel BIFF STYLE records.
975
     */
976 38
    private function writeStyle()
977
    {
978 38
        $record = 0x0293; // Record identifier
979 38
        $length = 0x0004; // Bytes to follow
980
981 38
        $ixfe = 0x8000; // Index to cell style XF
982 38
        $BuiltIn = 0x00; // Built-in style
983 38
        $iLevel = 0xff; // Outline style level
984
985 38
        $header = pack('vv', $record, $length);
986 38
        $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
987 38
        $this->append($header . $data);
988 38
    }
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 14 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 14
        $record = 0x041E; // Record identifier
999
1000 14
        $numberFormatString = \PhpOffice\PhpSpreadsheet\Shared\StringHelper::UTF8toBIFF8UnicodeLong($format);
1001 14
        $length = 2 + strlen($numberFormatString); // Number of bytes to follow
1002
1003 14
        $header = pack('vv', $record, $length);
1004 14
        $data = pack('v', $ifmt) . $numberFormatString;
1005 14
        $this->append($header . $data);
1006 14
    }
1007
1008
    /**
1009
     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
1010
     */
1011 38
    private function writeDateMode()
1012
    {
1013 38
        $record = 0x0022; // Record identifier
1014 38
        $length = 0x0002; // Bytes to follow
1015
1016 38
        $f1904 = (\PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar() == \PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904)
1017
            ? 1
1018 38
            : 0; // Flag for 1904 date system
1019
1020 38
        $header = pack('vv', $record, $length);
1021 38
        $data = pack('v', $f1904);
1022 38
        $this->append($header . $data);
1023 38
    }
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 38 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 38
        $record = 0x01C1; // Record identifier
1239 38
        $length = 8; // Number of bytes to follow
1240
1241 38
        $header = pack('vv', $record, $length);
1242
1243
        // by inspection of real Excel files, MS Office Excel 2007 writes this
1244 38
        $data = pack('VV', 0x000001C1, 0x00001E667);
1245
1246 38
        return $this->writeData($header . $data);
1247
    }
1248
1249
    /**
1250
     * Stores the PALETTE biff record.
1251
     */
1252 38
    private function writePalette()
1253
    {
1254 38
        $aref = $this->palette;
1255
1256 38
        $record = 0x0092; // Record identifier
1257 38
        $length = 2 + 4 * count($aref); // Number of bytes to follow
1258 38
        $ccv = count($aref); // Number of RGB values to follow
1259 38
        $data = ''; // The RGB data
1260
1261
        // Pack the RGB data
1262 38
        foreach ($aref as $color) {
1263 38
            foreach ($color as $byte) {
1264 38
                $data .= pack('C', $byte);
1265
            }
1266
        }
1267
1268 38
        $header = pack('vvv', $record, $length, $ccv);
1269 38
        $this->append($header . $data);
1270 38
    }
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 38
    private function writeSharedStringsTable()
1287
    {
1288
        // maximum size of record data (excluding record header)
1289 38
        $continue_limit = 8224;
1290
1291
        // initialize array of record data blocks
1292 38
        $recordDatas = [];
1293
1294
        // start SST record data block with total number of strings, total number of unique strings
1295 38
        $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
1296
1297
        // loop through all (unique) strings in shared strings table
1298 38
        foreach (array_keys($this->stringTable) as $string) {
1299
            // here $string is a BIFF8 encoded string
1300
1301
            // length = character count
1302 34
            $headerinfo = unpack('vlength/Cencoding', $string);
1303
1304
            // currently, this is always 1 = uncompressed
1305 34
            $encoding = $headerinfo['encoding'];
1306
1307
            // initialize finished writing current $string
1308 34
            $finished = false;
1309
1310 34
            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 34
                if (strlen($recordData) + strlen($string) <= $continue_limit) {
1316
                    // then we can write the string (or remainder of string) without any problems
1317 34
                    $recordData .= $string;
1318
1319 34
                    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 34
                    $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 38
        if (strlen($recordData) > 0) {
1379 38
            $recordDatas[] = $recordData;
1380
        }
1381
1382
        // combine into one chunk with all the blocks SST, CONTINUE,...
1383 38
        $chunk = '';
1384 38
        foreach ($recordDatas as $i => $recordData) {
1385
            // first block should have the SST record header, remaing should have CONTINUE header
1386 38
            $record = ($i == 0) ? 0x00FC : 0x003C;
1387
1388 38
            $header = pack('vv', $record, strlen($recordData));
1389 38
            $data = $header . $recordData;
1390
1391 38
            $chunk .= $this->writeData($data);
1392
        }
1393
1394 38
        return $chunk;
1395
    }
1396
1397
    /**
1398
     * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
1399
     */
1400 38
    private function writeMsoDrawingGroup()
1401
    {
1402
        // write the Escher stream if necessary
1403 38
        if (isset($this->escher)) {
1404 10
            $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xls\Escher($this->escher);
1405 10
            $data = $writer->close();
1406
1407 10
            $record = 0x00EB;
1408 10
            $length = strlen($data);
1409 10
            $header = pack('vv', $record, $length);
1410
1411 10
            return $this->writeData($header . $data);
1412
        } else {
1413 29
            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 10
    public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null)
1433
    {
1434 10
        $this->escher = $pValue;
1435 10
    }
1436
}
1437