GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 6d0cfa...78d09e )
by Denis
04:52
created

ExcelWriter   D

Complexity

Total Complexity 141

Size/Duplication

Total Lines 931
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 141
lcom 1
cbo 2
dl 0
loc 931
rs 4.4444
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A setAuthor() 0 4 1
A __destruct() 0 8 3
A setTmpDir() 0 4 1
A tempFilename() 0 8 2
A writeToStdOut() 0 6 1
A writeToString() 0 8 1
C writeToFile() 0 51 8
B initializeSheet() 0 45 4
C determineCellType() 0 36 11
C escapeCellFormat() 0 30 14
C addCellFormat() 0 46 12
C writeSheetHeader() 0 27 7
B writeSheetRow() 0 32 5
B finalizeSheet() 0 45 5
A markMergedCell() 0 12 3
B writeSheet() 0 12 5
C writeCell() 0 47 14
A writeStylesXML() 0 72 3
A setSharedString() 0 12 2
A writeSharedStringsXML() 0 18 2
A buildAppXML() 0 10 1
A buildCoreXML() 0 18 1
A buildRelationshipsXML() 0 15 1
B buildWorkbookXML() 0 24 2
A buildWorkbookRelXML() 0 23 3
B buildContentTypesXML() 0 30 3
A xlsCell() 0 9 2
A log() 0 7 2
A checkFilename() 0 9 1
A xmlspecialchars() 0 4 1
D convertDateTime() 0 61 17

How to fix   Complexity   

Complex Class

Complex classes like ExcelWriter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExcelWriter, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Ellumilel;
3
4
/**
5
 * Class ExcelWriter
6
 * @package xlsxWriter
7
 */
8
class ExcelWriter
9
{
10
    /**
11
     * @link http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP010073849.aspx
12
     */
13
    const EXCEL_MAX_ROW = 1048576;
14
    const EXCEL_MAX_COL = 16384;
15
16
    /** @var string */
17
    private $urlSchemaFormat = 'http://schemas.openxmlformats.org/officeDocument/2006';
18
19
    /** @var string */
20
    protected $author ='Unknown Author';
21
    /** @var array */
22
    protected $sheets = [];
23
    /** @var array */
24
    protected $sharedStrings = [];//unique set
25
    /** @var int */
26
    protected $shared_string_count = 0;//count of non-unique references to the unique set
27
    /** @var array */
28
    protected $tempFiles = [];
29
    /** @var array */
30
    protected $cellFormats = [];//contains excel format like YYYY-MM-DD HH:MM:SS
31
    /** @var array */
32
    protected $cellTypes = [];//contains friendly format like datetime
33
    /** @var string  */
34
    protected $currentSheet = '';
35
    /** @var null */
36
    protected $tmpDir = null;
37
38
    /**
39
     * ExcelWriter constructor.
40
     * @throws \Exception
41
     */
42
    public function __construct()
43
    {
44
        if (!class_exists('ZipArchive')) {
45
            throw new \Exception('ZipArchive not found');
46
        }
47
48
        if (!ini_get('date.timezone')) {
49
            //using date functions can kick out warning if this isn't set
50
            date_default_timezone_set('UTC');
51
        }
52
        $this->addCellFormat($cell_format = 'GENERAL');
53
    }
54
55
    /**
56
     * @param string $author
57
     */
58
    public function setAuthor($author = '')
59
    {
60
        $this->author = $author;
61
    }
62
63
    public function __destruct()
64
    {
65
        if (!empty($this->tempFiles)) {
66
            foreach ($this->tempFiles as $tempFile) {
67
                @unlink($tempFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
68
            }
69
        }
70
    }
71
72
    /**
73
     * @param $dir
74
     */
75
    public function setTmpDir($dir)
76
    {
77
        $this->tmpDir = $dir;
78
    }
79
80
    /**
81
     * Return tmpFileName
82
     * @return string
83
     */
84
    protected function tempFilename()
85
    {
86
        $tmpDir = is_null($this->tmpDir) ? sys_get_temp_dir() : $this->tmpDir;
87
        $filename = tempnam($tmpDir, "exlsWriter_");
88
        $this->tempFiles[] = $filename;
89
90
        return $filename;
91
    }
92
93
    public function writeToStdOut()
94
    {
95
        $tempFile = $this->tempFilename();
96
        $this->writeToFile($tempFile);
97
        readfile($tempFile);
98
    }
99
100
    /**
101
     * @return string
102
     */
103
    public function writeToString()
104
    {
105
        $tempFile = $this->tempFilename();
106
        $this->writeToFile($tempFile);
107
        $string = file_get_contents($tempFile);
108
109
        return $string;
110
    }
111
112
    /**
113
     * @param string $filename
114
     */
115
    public function writeToFile($filename)
116
    {
117
        foreach ($this->sheets as $sheetName => $sheet) {
118
            self::finalizeSheet($sheetName);//making sure all footers have been written
119
        }
120
        if (file_exists($filename)) {
121
            if (is_writable($filename)) {
122
                @unlink($filename); //if the zip already exists, remove it
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
123
            } else {
124
                self::log("Error in ".__CLASS__."::".__FUNCTION__.", file is not writeable.");
125
126
                return;
127
            }
128
        }
129
        $zip = new \ZipArchive();
130
        if (empty($this->sheets)) {
131
            self::log("Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined.");
132
133
            return;
134
        }
135
        if (!$zip->open($filename, \ZipArchive::CREATE)) {
136
            self::log("Error in ".__CLASS__."::".__FUNCTION__.", unable to create zip.");
137
138
            return;
139
        }
140
        $zip->addEmptyDir("docProps/");
141
        $zip->addFromString("docProps/app.xml", self::buildAppXML());
142
        $zip->addFromString("docProps/core.xml", self::buildCoreXML());
143
        $zip->addEmptyDir("_rels/");
144
        $zip->addFromString("_rels/.rels", self::buildRelationshipsXML());
145
        $zip->addEmptyDir("xl/worksheets/");
146
        foreach ($this->sheets as $sheet) {
147
            /** @var Sheet $sheet */
148
            $zip->addFile($sheet->getFilename(), "xl/worksheets/".$sheet->getXmlName());
149
        }
150
        if (!empty($this->sharedStrings)) {
151
            $zip->addFile(
152
                $this->writeSharedStringsXML(),
153
                "xl/sharedStrings.xml"
154
            );  //$zip->addFromString("xl/sharedStrings.xml",     self::buildSharedStringsXML() );
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% 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...
155
        }
156
        $zip->addFromString("xl/workbook.xml", self::buildWorkbookXML());
157
        $zip->addFile(
158
            $this->writeStylesXML(),
159
            "xl/styles.xml"
160
        );  //$zip->addFromString("xl/styles.xml"           , self::buildStylesXML() );
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% 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...
161
        $zip->addFromString("[Content_Types].xml", self::buildContentTypesXML());
162
        $zip->addEmptyDir("xl/_rels/");
163
        $zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelXML());
164
        $zip->close();
165
    }
166
167
    /**
168
     * @param string $sheetName
169
     */
170
    protected function initializeSheet($sheetName)
171
    {
172
        if ($this->currentSheet == $sheetName || isset($this->sheets[$sheetName])) {
173
            return;
174
        }
175
        $sheetFilename = $this->tempFilename();
176
        $sheetXmlName = 'sheet' . (count($this->sheets) + 1).".xml";
177
        $sheetObj = new Sheet();
178
        $sheetObj
179
            ->setFilename($sheetFilename)
180
            ->setSheetName($sheetName)
181
            ->setXmlName($sheetXmlName)
182
            ->setWriter(new Writer($sheetFilename))
183
        ;
184
        $this->sheets[$sheetName] = $sheetObj;
185
        /** @var Sheet $sheet */
186
        $sheet = &$this->sheets[$sheetName];
187
        $selectedTab = count($this->sheets) == 1 ? 'true' : 'false';//only first sheet is selected
188
        $maxCell = ExcelWriter::xlsCell(self::EXCEL_MAX_ROW, self::EXCEL_MAX_COL);//XFE1048577
189
        $sheet->getWriter()->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
190
        $sheet->getWriter()->write(
191
            '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" 
192
                xmlns:r="'.$this->urlSchemaFormat.'/relationships">'
193
        );
194
        $sheet->getWriter()->write('<sheetPr filterMode="false">');
195
        $sheet->getWriter()->write('<pageSetUpPr fitToPage="false"/>');
196
        $sheet->getWriter()->write('</sheetPr>');
197
        $sheet->setMaxCellTagStart($sheet->getWriter()->fTell());
198
        $sheet->getWriter()->write('<dimension ref="A1:'.$maxCell.'"/>');
199
        $sheet->setMaxCellTagEnd($sheet->getWriter()->fTell());
200
        $sheet->getWriter()->write('<sheetViews>');
201
        $sheet->getWriter()->write(
202
            '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" 
203
            showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" 
204
            tabSelected="'.$selectedTab.'" topLeftCell="A1" view="normal" windowProtection="false" 
205
            workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">'
206
        );
207
        $sheet->getWriter()->write('<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
208
        $sheet->getWriter()->write('</sheetView>');
209
        $sheet->getWriter()->write('</sheetViews>');
210
        $sheet->getWriter()->write('<cols>');
211
        $sheet->getWriter()->write('<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="11.5"/>');
212
        $sheet->getWriter()->write('</cols>');
213
        $sheet->getWriter()->write('<sheetData>');
214
    }
215
216
    /**
217
     * @param $cellFormat
218
     *
219
     * @return string
220
     */
221
    private function determineCellType($cellFormat)
222
    {
223
        $cellFormat = str_replace("[RED]", "", $cellFormat);
224
        if ($cellFormat == 'GENERAL') {
225
            return 'string';
226
        }
227
        if ($cellFormat == '0') {
228
            return 'numeric';
229
        }
230
        if (preg_match("/[H]{1,2}:[M]{1,2}/", $cellFormat)) {
231
            return 'datetime';
232
        }
233
        if (preg_match("/[M]{1,2}:[S]{1,2}/", $cellFormat)) {
234
            return 'datetime';
235
        }
236
        if (preg_match("/[YY]{2,4}/", $cellFormat)) {
237
            return 'date';
238
        }
239
        if (preg_match("/[D]{1,2}/", $cellFormat)) {
240
            return 'date';
241
        }
242
        if (preg_match("/[M]{1,2}/", $cellFormat)) {
243
            return 'date';
244
        }
245
        if (preg_match("/$/", $cellFormat)) {
246
            return 'currency';
247
        }
248
        if (preg_match("/%/", $cellFormat)) {
249
            return 'percent';
250
        }
251
        if (preg_match("/0/", $cellFormat)) {
252
            return 'numeric';
253
        }
254
255
        return 'string';
256
    }
257
258
    /**
259
     * @param $cellFormat
260
     *
261
     * @return string
262
     */
263
    private function escapeCellFormat($cellFormat)
264
    {
265
        $ignoreUntil = '';
266
        $escaped = '';
267
        for ($i = 0, $ix = strlen($cellFormat); $i < $ix; $i++) {
268
            $c = $cellFormat[$i];
269
            if ($ignoreUntil == '' && $c == '[') {
270
                $ignoreUntil = ']';
271
            } else {
272
                if ($ignoreUntil == '' && $c == '"') {
273
                    $ignoreUntil = '"';
274
                } else {
275
                    if ($ignoreUntil == $c) {
276
                        $ignoreUntil = '';
277
                    }
278
                }
279
            }
280
            if ($ignoreUntil == '' &&
281
                ($c == ' ' || $c == '-' || $c == '(' || $c == ')') &&
282
                ($i == 0 || $cellFormat[$i - 1] != '_')
283
            ) {
284
                $escaped .= "\\".$c;
285
            } else {
286
                $escaped .= $c;
287
            }
288
        }
289
290
        return $escaped;
291
        //return str_replace( array(" ","-", "(", ")"), array("\ ","\-", "\(", "\)"), $cell_format);//TODO, needs more escaping
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
292
    }
293
294
    /**
295
     * @param $cellFormat
296
     *
297
     * @return int|mixed
298
     */
299
    private function addCellFormat($cellFormat)
300
    {
301
        //for backwards compatibility, to handle older versions
302
        switch ($cellFormat) {
303
            case 'string':
304
                $cellFormat = 'GENERAL';
305
                break;
306
            case 'integer':
307
                $cellFormat = '0';
308
                break;
309
            case 'date':
310
                $cellFormat = 'YYYY-MM-DD';
311
                break;
312
            case 'datetime':
313
                $cellFormat = 'YYYY-MM-DD HH:MM:SS';
314
                break;
315
            case 'dollar':
316
                $cellFormat = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
317
                break;
318
            case 'money':
319
                $cellFormat = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
320
                break;
321
            case 'euro':
322
                $cellFormat = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]';
323
                break;
324
            case 'NN':
325
                $cellFormat = 'DDD';
326
                break;
327
            case 'NNN':
328
                $cellFormat = 'DDDD';
329
                break;
330
            case 'NNNN':
331
                $cellFormat = 'DDDD", "';
332
                break;
333
        }
334
335
        $cellFormat = strtoupper($cellFormat);
336
        $position = array_search($cellFormat, $this->cellFormats, $strict = true);
337
        if ($position === false) {
338
            $position = count($this->cellFormats);
339
            $this->cellFormats[] = $this->escapeCellFormat($cellFormat);
340
            $this->cellTypes[] = $this->determineCellType($cellFormat);
341
        }
342
343
        return $position;
344
    }
345
346
    /**
347
     * @param string $sheetName
348
     * @param array $headerTypes
349
     * @param bool $suppressRow
350
     */
351
    public function writeSheetHeader($sheetName, array $headerTypes, $suppressRow = false)
352
    {
353
        if (empty($sheetName) || empty($headerTypes) || !empty($this->sheets[$sheetName])) {
354
            return;
355
        }
356
        self::initializeSheet($sheetName);
357
        /** @var Sheet $sheet */
358
        $sheet = &$this->sheets[$sheetName];
359
        $sheet->setColumns([]);
360
        foreach ($headerTypes as $v) {
361
            $sheet->setColumn($this->addCellFormat($v));
0 ignored issues
show
Documentation introduced by
$this->addCellFormat($v) is of type integer|string, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
362
        }
363
364
        if (!$suppressRow) {
365
            $header_row = array_keys($headerTypes);
366
            $sheet->getWriter()->write(
367
                '<row collapsed="false" customFormat="false" 
368
                customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">'
369
            );
370
            foreach ($header_row as $k => $v) {
371
                $this->writeCell($sheet->getWriter(), 0, $k, $v, $cell_format_index = '0');
372
            }
373
            $sheet->getWriter()->write('</row>');
374
            $sheet->increaseRowCount();
375
        }
376
        $this->currentSheet = $sheetName;
377
    }
378
379
    /**
380
     * @param string $sheetName
381
     * @param array $row
382
     */
383
    public function writeSheetRow($sheetName, array $row)
384
    {
385
        if (empty($sheetName) || empty($row)) {
386
            return;
387
        }
388
        self::initializeSheet($sheetName);
389
        /** @var Sheet $sheet */
390
        $sheet = &$this->sheets[$sheetName];
391
        $columns = $sheet->getColumns();
392
        if (empty($columns)) {
393
            $sheet->setColumns(array_fill($from = 0, $until = count($row), '0'));//'0'=>'string'
394
        }
395
        $sheet->getWriter()->write(
396
            '<row collapsed="false" customFormat="false" customHeight="false" 
397
            hidden="false" ht="12.1" outlineLevel="0" r="'.($sheet->getRowCount() + 1).'">'
398
        );
399
        $column_count = 0;
400
        $sheetColumns = $sheet->getColumns();
401
        foreach ($row as $k => $v) {
402
            $this->writeCell(
403
                $sheet->getWriter(),
404
                $sheet->getRowCount(),
405
                $column_count,
406
                $v,
407
                $sheetColumns[$column_count]
408
            );
409
            $column_count++;
410
        }
411
        $sheet->getWriter()->write('</row>');
412
        $sheet->increaseRowCount();
413
        $this->currentSheet = $sheetName;
414
    }
415
416
    /**
417
     * @param string $sheetName
418
     */
419
    protected function finalizeSheet($sheetName)
420
    {
421
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
422
            return;
423
        }
424
        /** @var Sheet $sheet */
425
        $sheet = &$this->sheets[$sheetName];
426
        $sheet->getWriter()->write('</sheetData>');
427
        $mergeCells = $sheet->getMergeCells();
428
        if (!empty($mergeCells)) {
429
            $sheet->getWriter()->write('<mergeCells>');
430
            foreach ($mergeCells as $range) {
431
                $sheet->getWriter()->write('<mergeCell ref="'.$range.'"/>');
432
            }
433
            $sheet->getWriter()->write('</mergeCells>');
434
        }
435
        $sheet->getWriter()->write(
436
            '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false"
437
                verticalCentered="false"/>'
438
        );
439
        $sheet->getWriter()->write(
440
            '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>'
441
        );
442
        $sheet->getWriter()->write(
443
            '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" 
444
                fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" 
445
                paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>'
446
        );
447
        $sheet->getWriter()->write('<headerFooter differentFirst="false" differentOddEven="false">');
448
        $sheet->getWriter()->write(
449
            '<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>'
450
        );
451
        $sheet->getWriter()->write(
452
            '<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>'
453
        );
454
        $sheet->getWriter()->write('</headerFooter>');
455
        $sheet->getWriter()->write('</worksheet>');
456
        $maxCell = self::xlsCell($sheet->getRowCount() - 1, count($sheet->getColumns()) - 1);
457
        $maxCellTag = '<dimension ref="A1:'.$maxCell.'"/>';
458
        $padding_length = $sheet->getMaxCellTagEnd() - $sheet->getMaxCellTagStart() - strlen($maxCellTag);
459
        $sheet->getWriter()->fSeek($sheet->getMaxCellTagStart());
460
        $sheet->getWriter()->write($maxCellTag.str_repeat(" ", $padding_length));
461
        $sheet->getWriter()->close();
462
        $sheet->setFinalized(true);
463
    }
464
465
    /**
466
     * @param string $sheetName
467
     * @param int $startCellRow
468
     * @param int $startCellColumn
469
     * @param int $endCellRow
470
     * @param int $endCellColumn
471
     */
472
    public function markMergedCell($sheetName, $startCellRow, $startCellColumn, $endCellRow, $endCellColumn)
473
    {
474
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
475
            return;
476
        }
477
        self::initializeSheet($sheetName);
478
        /** @var Sheet $sheet */
479
        $sheet = &$this->sheets[$sheetName];
480
        $startCell = self::xlsCell($startCellRow, $startCellColumn);
481
        $endCell = self::xlsCell($endCellRow, $endCellColumn);
482
        $sheet->setMergeCells($startCell.":".$endCell);
0 ignored issues
show
Documentation introduced by
$startCell . ':' . $endCell is of type string, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
483
    }
484
485
    /**
486
     * @param array $data
487
     * @param string $sheetName
488
     * @param array $headerTypes
489
     */
490
    public function writeSheet(array $data, $sheetName = '', array $headerTypes = [])
491
    {
492
        $sheetName = empty($sheetName) ? 'Sheet1' : $sheetName;
493
        $data = empty($data) ? [['']] : $data;
494
        if (!empty($headerTypes)) {
495
            $this->writeSheetHeader($sheetName, $headerTypes);
496
        }
497
        foreach ($data as $i => $row) {
498
            $this->writeSheetRow($sheetName, $row);
499
        }
500
        $this->finalizeSheet($sheetName);
501
    }
502
503
    /**
504
     * @param Writer $file
505
     * @param $rowNumber
506
     * @param $columnNumber
507
     * @param $value
508
     * @param $cellIndex
509
     */
510
    protected function writeCell(
511
        Writer $file,
512
        $rowNumber,
513
        $columnNumber,
514
        $value,
515
        $cellIndex
516
    ) {
517
        $cellType = $this->cellTypes[$cellIndex];
518
        $cellName = self::xlsCell($rowNumber, $columnNumber);
519
        if (!is_scalar($value) || $value === '') { //objects, array, empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
520
            $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'"/>');
521
        } elseif (is_string($value) && $value{0} == '=') {
522
            $file->write(
523
                sprintf('<c r="%s" s="%s" t="s"><f>%s</f></c>', $cellName, $cellIndex, self::xmlspecialchars($value))
524
            );
525
        } elseif ($cellType == 'date') {
526
            $file->write(
527
                sprintf('<c r="%s" s="%s" t="n"><v>%s</v></c>', $cellName, $cellIndex, self::convertDateTime($value))
528
            );
529
        } elseif ($cellType == 'datetime') {
530
            $file->write(
531
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.self::convertDateTime($value).'</v></c>'
532
            );
533
        } elseif ($cellType == 'currency' || $cellType == 'percent' || $cellType == 'numeric') {
534
            $file->write(
535
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.self::xmlspecialchars($value).'</v></c>'
536
            );//int,float,currency
537
        } else {
538
            if (!is_string($value)) {
539
                $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
540
            } else {
541
                if ($value{0} != '0' && $value{0} != '+' && filter_var(
542
                    $value,
543
                    FILTER_VALIDATE_INT,
544
                    ['options' => ['max_range' => 2147483647]]
545
                )) {
546
                    $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
547
                } else { //implied: ($cell_format=='string')
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
548
                    $file->write(
549
                        '<c r="'.$cellName.'" s="'.$cellIndex.'" t="s"><v>'.self::xmlspecialchars(
550
                            $this->setSharedString($value)
551
                        ).'</v></c>'
552
                    );
553
                }
554
            }
555
        }
556
    }
557
558
    /**
559
     * @return string
560
     */
561
    protected function writeStylesXML()
562
    {
563
        $temporaryFilename = $this->tempFilename();
564
        $file = new Writer($temporaryFilename);
565
        $file->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
566
        $file->write('<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
567
        $file->write('<numFmts count="'.count($this->cellFormats).'">');
568
        foreach ($this->cellFormats as $i => $v) {
569
            $file->write('<numFmt numFmtId="'.(164 + $i).'" formatCode="'.self::xmlspecialchars($v).'" />');
570
        }
571
        //$file->write(		'<numFmt formatCode="GENERAL" numFmtId="164"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
572
        //$file->write(		'<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
573
        //$file->write(		'<numFmt formatCode="YYYY-MM-DD\ HH:MM:SS" numFmtId="166"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
574
        //$file->write(		'<numFmt formatCode="YYYY-MM-DD" numFmtId="167"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
575
        $file->write('</numFmts>');
576
        $file->write('<fonts count="4">');
577
        $file->write('<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
578
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
579
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
580
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
581
        $file->write('</fonts>');
582
        $file->write('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
583
        $file->write('<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>');
584
        $file->write('<cellStyleXfs count="20">');
585
        $file->write('<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
586
        $file->write('<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
587
        $file->write('<protection hidden="false" locked="true"/>');
588
        $file->write('</xf>');
589
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
590
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
591
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
592
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
593
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
594
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
595
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
596
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
597
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
598
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
599
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
600
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
601
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
602
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
603
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
604
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
605
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
606
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
607
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
608
        $file->write('</cellStyleXfs>');
609
        $file->write('<cellXfs count="'.count($this->cellFormats).'">');
610
        foreach ($this->cellFormats as $i => $v) {
611
            $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="false" 
612
            applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="'.(164+$i).'" xfId="0"/>');
613
        }
614
        $file->write('</cellXfs>');
615
        //$file->write(	'<cellXfs count="4">');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
616
        //$file->write('<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="164" xfId="0"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
617
        //$file->write('<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="165" xfId="0"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
618
        //$file->write('<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="166" xfId="0"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
619
        //$file->write('<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="167" xfId="0"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
620
        //$file->write(	'</cellXfs>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
621
        $file->write('<cellStyles count="6">');
622
        $file->write('<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
623
        $file->write('<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
624
        $file->write('<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
625
        $file->write('<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
626
        $file->write('<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
627
        $file->write('<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
628
        $file->write('</cellStyles>');
629
        $file->write('</styleSheet>');
630
        $file->close();
631
        return $temporaryFilename;
632
    }
633
634
    /**
635
     * @param $v
636
     *
637
     * @return int|mixed
638
     */
639
    protected function setSharedString($v)
640
    {
641
        if (isset($this->sharedStrings[$v])) {
642
            $stringValue = $this->sharedStrings[$v];
643
        } else {
644
            $stringValue = count($this->sharedStrings);
645
            $this->sharedStrings[$v] = $stringValue;
646
        }
647
        $this->shared_string_count++;//non-unique count
648
649
        return $stringValue;
650
    }
651
652
    /**
653
     * @return string
654
     */
655
    protected function writeSharedStringsXML()
656
    {
657
        $temporaryFilename = $this->tempFilename();
658
        $file = new Writer($temporaryFilename, $fd_flags = 'w', $check_utf8 = true);
0 ignored issues
show
Documentation introduced by
$check_utf8 = true is of type boolean, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
659
        $file->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
660
        $file->write(
661
            '<sst count="'.($this->shared_string_count).'" uniqueCount="'.count(
662
                $this->sharedStrings
663
            ).'" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
664
        );
665
        foreach ($this->sharedStrings as $s => $c) {
666
            $file->write('<si><t>'.self::xmlspecialchars($s).'</t></si>');
667
        }
668
        $file->write('</sst>');
669
        $file->close();
670
671
        return $temporaryFilename;
672
    }
673
674
    /**
675
     * @return string
676
     */
677
    protected function buildAppXML()
678
    {
679
        $appXml = '';
680
        $appXml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
681
        $appXml .= '<Properties xmlns="'.$this->urlSchemaFormat.'/extended-properties"';
682
        $appXml .= ' xmlns:vt="'.$this->urlSchemaFormat.'/docPropsVTypes">';
683
        $appXml .= '<TotalTime>0</TotalTime></Properties>';
684
685
        return $appXml;
686
    }
687
688
    /**
689
     * @return string
690
     */
691
    protected function buildCoreXML()
692
    {
693
        $coreXml = '';
694
        $coreXml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
695
        $coreXml .= '<cp:coreProperties';
696
        $coreXml .= ' xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"';
697
        $coreXml .= ' xmlns:dc="http://purl.org/dc/elements/1.1/"';
698
        $coreXml .= ' xmlns:dcmitype="http://purl.org/dc/dcmitype/"';
699
        $coreXml .= ' xmlns:dcterms="http://purl.org/dc/terms/"';
700
        $coreXml .= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
701
        //$date_time = '2016-08-30T15:52:19.00Z';
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
702
        $coreXml .= '<dcterms:created xsi:type="dcterms:W3CDTF">'.date("Y-m-d\TH:i:s.00\Z").'</dcterms:created>';
703
        $coreXml .= '<dc:creator>'.self::xmlspecialchars($this->author).'</dc:creator>';
704
        $coreXml .= '<cp:revision>0</cp:revision>';
705
        $coreXml .= '</cp:coreProperties>';
706
707
        return $coreXml;
708
    }
709
710
    /**
711
     * @return string
712
     */
713
    protected function buildRelationshipsXML()
714
    {
715
        $relXml = "";
716
        $relXml .= '<?xml version="1.0" encoding="UTF-8"?>'."\n";
717
        $relXml .= '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
718
        $relXml .= '<Relationship Id="rId1" Type="'.$this->urlSchemaFormat.'/relationships/officeDocument"';
719
        $relXml .= ' Target="xl/workbook.xml"/>';
720
        $relXml .= '<Relationship Id="rId2" Type="'.$this->urlSchemaFormat.'/relationships/metadata/core-properties"';
721
        $relXml .= ' Target="docProps/core.xml"/>';
722
        $relXml .= '<Relationship Id="rId3" Type="'.$this->urlSchemaFormat.'/relationships/extended-properties"';
723
        $relXml .= ' Target="docProps/app.xml"/>'."\n";
724
        $relXml .= '</Relationships>';
725
726
        return $relXml;
727
    }
728
729
    /**
730
     * @return string
731
     */
732
    protected function buildWorkbookXML()
733
    {
734
        $i = 0;
735
        $workbookXml = '';
736
        $workbookXml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
737
        $workbookXml .= '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"';
738
        $workbookXml .= ' xmlns:r="'.$this->urlSchemaFormat.'/relationships">';
739
        $workbookXml .= '<fileVersion appName="Calc"/><workbookPr backupFile="false"';
740
        $workbookXml .= ' showObjects="all" date1904="false"/><workbookProtection/>';
741
        $workbookXml .= '<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true"';
742
        $workbookXml .= ' showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192"';
743
        $workbookXml .= ' windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>';
744
        $workbookXml .= '<sheets>';
745
        foreach ($this->sheets as $sheet_name => $sheet) {
746
            /** @var Sheet $sheet */
747
            $workbookXml .= '<sheet name="'.self::xmlspecialchars($sheet->getSheetName()).'"';
748
            $workbookXml .= ' sheetId="'.($i + 1).'" state="visible" r:id="rId'.($i + 2).'"/>';
749
            $i++;
750
        }
751
        $workbookXml .= '</sheets>';
752
        $workbookXml .= '<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
753
754
        return $workbookXml;
755
    }
756
757
    /**
758
     * @return string
759
     */
760
    protected function buildWorkbookRelXML()
761
    {
762
        $i = 0;
763
        $relXml = '';
764
        $relXml .= '<?xml version="1.0" encoding="UTF-8"?>'."\n";
765
        $relXml .= '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
766
        $relXml .= '<Relationship Id="rId1" Type="'.$this->urlSchemaFormat.'/relationships/styles"';
767
        $relXml .= ' Target="styles.xml"/>';
768
        foreach ($this->sheets as $sheet_name => $sheet) {
769
            /** @var Sheet $sheet */
770
            $relXml .= '<Relationship Id="rId'.($i + 2).'" 
771
            Type="'.$this->urlSchemaFormat.'/relationships/worksheet" Target="worksheets/'.($sheet->getXmlName()).'"/>';
772
            $i++;
773
        }
774
        if (!empty($this->sharedStrings)) {
775
            $relXml .= '<Relationship Id="rId'.(count($this->sheets) + 2).'" 
776
            Type="'.$this->urlSchemaFormat.'/relationships/sharedStrings" Target="sharedStrings.xml"/>';
777
        }
778
        $relXml .= "\n";
779
        $relXml .= '</Relationships>';
780
781
        return $relXml;
782
    }
783
784
    /**
785
     * @return string
786
     */
787
    protected function buildContentTypesXML()
788
    {
789
        $xml = '';
790
        $xml .= '<?xml version="1.0" encoding="UTF-8"?>'."\n";
791
        $xml .= '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
792
        $xml .= '<Override PartName="/_rels/.rels"';
793
        $xml .= ' ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
794
        $xml .= '<Override PartName="/xl/_rels/workbook.xml.rels"';
795
        $xml .= ' ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
796
        foreach ($this->sheets as $sheet_name => $sheet) {
797
            /** @var Sheet $sheet */
798
            $xml .= '<Override PartName="/xl/worksheets/'.($sheet->getXmlName()).'"';
799
            $xml .= ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
800
        }
801
        if (!empty($this->sharedStrings)) {
802
            $xml .= '<Override PartName="/xl/sharedStrings.xml"';
803
            $xml .= ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>';
804
        }
805
        $xml .= '<Override PartName="/xl/workbook.xml"';
806
        $xml .= ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
807
        $xml .= '<Override PartName="/xl/styles.xml"';
808
        $xml .= ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
809
        $xml .= '<Override PartName="/docProps/app.xml"';
810
        $xml .= ' ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>';
811
        $xml .= '<Override PartName="/docProps/core.xml"';
812
        $xml .= ' ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'."\n";
813
        $xml .= '</Types>';
814
815
        return $xml;
816
    }
817
818
    /**
819
     * @param int $rowNumber
820
     * @param int $columnNumber
821
     *
822
     * @return string Cell label/coordinates (A1, C3, AA42)
823
     */
824
    public static function xlsCell($rowNumber, $columnNumber)
825
    {
826
        $n = $columnNumber;
827
        for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
828
            $r = chr($n % 26 + 0x41).$r;
829
        }
830
831
        return $r.($rowNumber + 1);
832
    }
833
834
    /**
835
     * @param $string
836
     */
837
    public static function log($string)
838
    {
839
        file_put_contents(
840
            "php://stderr",
841
            date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n"
842
        );
843
    }
844
845
    /**
846
     * @link https://msdn.microsoft.com/ru-RU/library/aa365247%28VS.85%29.aspx
847
     *
848
     * @param string $filename
849
     *
850
     * @return mixed
851
     */
852
    public static function checkFilename($filename)
853
    {
854
        $invalidCharacter = array_merge(
855
            array_map('chr', range(0, 31)),
856
            ['<', '>', '?', '"', ':', '|', '\\', '/', '*', '&']
857
        );
858
859
        return str_replace($invalidCharacter, '', $filename);
860
    }
861
862
    /**
863
     * @param $val
864
     *
865
     * @return mixed
866
     */
867
    public static function xmlspecialchars($val)
868
    {
869
        return str_replace("'", "&#39;", htmlspecialchars($val));
870
    }
871
872
    /**
873
     * @param string $dateInput
874
     *
875
     * @return int
876
     */
877
    public static function convertDateTime($dateInput) //thanks to Excel::Writer::XLSX::Worksheet.pm (perl)
878
    {
879
        $days = 0;    # Number of days since epoch
0 ignored issues
show
Unused Code introduced by
$days 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...
880
        $seconds = 0;    # Time expressed as fraction of 24h hours in seconds
881
        $year = $month = $day = 0;
882
        $hour = $min = $sec = 0;
883
        $date_time = $dateInput;
884
        if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $date_time, $matches)) {
885
            list($junk, $year, $month, $day) = $matches;
0 ignored issues
show
Unused Code introduced by
The assignment to $junk is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
886
        }
887
        if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $date_time, $matches)) {
888
            list($junk, $hour, $min, $sec) = $matches;
0 ignored issues
show
Unused Code introduced by
The assignment to $junk is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
889
            $seconds = ($hour * 60 * 60 + $min * 60 + $sec) / (24 * 60 * 60);
890
        }
891
        //using 1900 as epoch, not 1904, ignoring 1904 special case
892
        # Special cases for Excel.
893
        if ("$year-$month-$day" == '1899-12-31') {
894
            return $seconds;
895
        }    # Excel 1900 epoch
896
        if ("$year-$month-$day" == '1900-01-00') {
897
            return $seconds;
898
        }    # Excel 1900 epoch
899
        if ("$year-$month-$day" == '1900-02-29') {
900
            return 60 + $seconds;
901
        }    # Excel false leapday
902
        # We calculate the date by calculating the number of days since the epoch
903
        # and adjust for the number of leap days. We calculate the number of leap
904
        # days by normalising the year in relation to the epoch. Thus the year 2000
905
        # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
906
        $epoch  = 1900;
907
        $offset = 0;
908
        $norm   = 300;
909
        $range  = $year - $epoch;
910
        # Set month days and check for leap year.
911
        $leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100)) ) ? 1 : 0;
912
        $mdays = array( 31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
913
        # Some boundary checks
914
        if ($year < $epoch || $year > 9999) {
915
            return 0;
916
        }
917
        if ($month < 1 || $month > 12) {
918
            return 0;
919
        }
920
        if ($day < 1 || $day > $mdays[$month - 1]) {
921
            return 0;
922
        }
923
        # Accumulate the number of days since the epoch.
924
        $days = $day; # Add days for current month
925
        $days += array_sum(array_slice($mdays, 0, $month - 1)); # Add days for past months
926
        $days += $range * 365; # Add days for past years
927
        $days += intval(($range) / 4); # Add leapdays
928
        $days -= intval(($range + $offset) / 100); # Subtract 100 year leapdays
929
        $days += intval(($range + $offset + $norm) / 400); # Add 400 year leapdays
930
        $days -= $leap; # Already counted above
931
        # Adjust for Excel erroneously treating 1900 as a leap year.
932
        if ($days > 59) {
933
            $days++;
934
        }
935
936
        return $days + $seconds;
937
    }
938
}
939