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 ( 7edaa8...d5ac9b )
by Denis
03:08
created

ExcelWriter::writeToFile()   C

Complexity

Conditions 8
Paths 26

Size

Total Lines 62
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 62
rs 6.943
cc 8
eloc 42
nc 26
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Ellumilel;
3
4
use Ellumilel\DocProps\App;
5
use Ellumilel\DocProps\Core;
6
use Ellumilel\Rels\Relationships;
7
use Ellumilel\Xl\SharedStrings;
8
9
/**
10
 * Class ExcelWriter
11
 * @package Ellumilel
12
 * @author Denis Tikhonov <[email protected]>
13
 */
14
class ExcelWriter
15
{
16
    /**
17
     * @link http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP010073849.aspx
18
     */
19
    const EXCEL_MAX_ROW = 1048576;
20
    const EXCEL_MAX_RANGE = 2147483647;
21
    const EXCEL_MAX_COL = 16384;
22
23
    /** @var string */
24
    private $urlSchemaFormat = 'http://schemas.openxmlformats.org/officeDocument/2006';
25
26
    /** @var string */
27
    protected $author ='Unknown Author';
28
    /** @var array */
29
    protected $sheets = [];
30
    /** @var array */
31
    protected $sharedStrings = [];//unique set
32
    /** @var int */
33
    protected $sharedStringCount = 0;//count of non-unique references to the unique set
34
    /** @var array */
35
    protected $tempFiles = [];
36
    /** @var array */
37
    protected $cellFormats = [];//contains excel format like YYYY-MM-DD HH:MM:SS
38
    /** @var array */
39
    protected $cellTypes = [];//contains friendly format like datetime
40
    /** @var string  */
41
    protected $currentSheet = '';
42
    /** @var null */
43
    protected $tmpDir = null;
44
45
    /**
46
     * ExcelWriter constructor.
47
     * @throws \Exception
48
     */
49
    public function __construct()
50
    {
51
        if (!class_exists('ZipArchive')) {
52
            throw new \Exception('ZipArchive not found');
53
        }
54
55
        if (!ini_get('date.timezone')) {
56
            //using date functions can kick out warning if this isn't set
57
            date_default_timezone_set('UTC');
58
        }
59
        $this->addCellFormat($cell_format = 'GENERAL');
60
    }
61
62
    /**
63
     * @param string $author
64
     */
65
    public function setAuthor($author = '')
66
    {
67
        $this->author = $author;
68
    }
69
70
    public function __destruct()
71
    {
72
        if (!empty($this->tempFiles)) {
73
            foreach ($this->tempFiles as $tempFile) {
74
                if (file_exists($tempFile)) {
75
                    unlink($tempFile);
76
                }
77
            }
78
        }
79
    }
80
81
    /**
82
     * @param $dir
83
     */
84
    public function setTmpDir($dir)
85
    {
86
        $this->tmpDir = $dir;
87
    }
88
89
    /**
90
     * Return tmpFileName
91
     * @return string
92
     */
93
    protected function tempFilename()
94
    {
95
        $tmpDir = is_null($this->tmpDir) ? sys_get_temp_dir() : $this->tmpDir;
96
        $filename = tempnam($tmpDir, "exlsWriter_");
97
        $this->tempFiles[] = $filename;
98
99
        return $filename;
100
    }
101
102
    public function writeToStdOut()
103
    {
104
        $tempFile = $this->tempFilename();
105
        $this->writeToFile($tempFile);
106
        readfile($tempFile);
107
    }
108
109
    /**
110
     * @return string
111
     */
112
    public function writeToString()
113
    {
114
        $tempFile = $this->tempFilename();
115
        $this->writeToFile($tempFile);
116
        $string = file_get_contents($tempFile);
117
118
        return $string;
119
    }
120
121
    /**
122
     * @param string $filename
123
     */
124
    public function writeToFile($filename)
125
    {
126
        foreach ($this->sheets as $sheetName => $sheet) {
127
            $this->finalizeSheet($sheetName);//making sure all footers have been written
128
        }
129
        if (file_exists($filename)) {
130
            if (is_writable($filename)) {
131
                //remove it if already exists
132
                unlink($filename);
133
            } else {
134
                self::log("Error in ".__CLASS__."::".__FUNCTION__.", file is not writable.");
135
136
                return;
137
            }
138
        }
139
        $zip = new \ZipArchive();
140
        if (empty($this->sheets)) {
141
            self::log("Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined.");
142
143
            return;
144
        }
145
        if (!$zip->open($filename, \ZipArchive::CREATE)) {
146
            self::log("Error in ".__CLASS__."::".__FUNCTION__.", unable to create zip.");
147
148
            return;
149
        }
150
151
        $app = new App();
152
        $core = new Core();
153
154
        $contentTypes = new ContentTypes(!empty($this->sharedStrings));
155
        $contentTypes->setSheet($this->sheets);
156
157
        $rels = new Relationships(!empty($this->sharedStrings));
158
        $rels->setSheet($this->sheets);
159
160
        $zip->addEmptyDir("docProps/");
161
        $zip->addFromString("docProps/app.xml", $app->buildAppXML());
162
        $zip->addFromString("docProps/core.xml", $core->buildCoreXML());
163
        $zip->addEmptyDir("_rels/");
164
        $zip->addFromString("_rels/.rels", $rels->buildRelationshipsXML());
165
        $zip->addEmptyDir("xl/worksheets/");
166
        foreach ($this->sheets as $sheet) {
167
            /** @var Sheet $sheet */
168
            $zip->addFile($sheet->getFilename(), "xl/worksheets/".$sheet->getXmlName());
169
        }
170
        if (!empty($this->sharedStrings)) {
171
            $zip->addFile(
172
                $this->writeSharedStringsXML(),
173
                "xl/sharedStrings.xml"
174
            );
175
        }
176
        $zip->addFromString("xl/workbook.xml", self::buildWorkbookXML());
177
        $zip->addFile(
178
            $this->writeStylesXML(),
179
            "xl/styles.xml"
180
        );
181
        $zip->addFromString("[Content_Types].xml", $contentTypes->buildContentTypesXML());
182
        $zip->addEmptyDir("xl/_rels/");
183
        $zip->addFromString("xl/_rels/workbook.xml.rels", $rels->buildWorkbookRelationshipsXML());
184
        $zip->close();
185
    }
186
187
    /**
188
     * @param string $sheetName
189
     */
190
    protected function initializeSheet($sheetName)
191
    {
192
        if ($this->currentSheet == $sheetName || isset($this->sheets[$sheetName])) {
193
            return;
194
        }
195
        $sheetFilename = $this->tempFilename();
196
        $sheetXmlName = 'sheet' . (count($this->sheets) + 1).".xml";
197
        $sheetObj = new Sheet();
198
        $sheetObj
199
            ->setFilename($sheetFilename)
200
            ->setSheetName($sheetName)
201
            ->setXmlName($sheetXmlName)
202
            ->setWriter(new Writer($sheetFilename))
203
        ;
204
        $this->sheets[$sheetName] = $sheetObj;
205
        /** @var Sheet $sheet */
206
        $sheet = &$this->sheets[$sheetName];
207
        $selectedTab = count($this->sheets) == 1 ? 'true' : 'false';//only first sheet is selected
208
        $maxCell = ExcelWriter::xlsCell(self::EXCEL_MAX_ROW, self::EXCEL_MAX_COL);//XFE1048577
209
        $sheet->getWriter()->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
210
        $sheet->getWriter()->write(
211
            '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" 
212
                xmlns:r="'.$this->urlSchemaFormat.'/relationships">'
213
        );
214
        $sheet->getWriter()->write('<sheetPr filterMode="false">');
215
        $sheet->getWriter()->write('<pageSetUpPr fitToPage="false"/>');
216
        $sheet->getWriter()->write('</sheetPr>');
217
        $sheet->setMaxCellTagStart($sheet->getWriter()->fTell());
218
        $sheet->getWriter()->write('<dimension ref="A1:'.$maxCell.'"/>');
219
        $sheet->setMaxCellTagEnd($sheet->getWriter()->fTell());
220
        $sheet->getWriter()->write('<sheetViews>');
221
        $sheet->getWriter()->write(
222
            '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" 
223
            showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" 
224
            tabSelected="'.$selectedTab.'" topLeftCell="A1" view="normal" windowProtection="false" 
225
            workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">'
226
        );
227
        $sheet->getWriter()->write('<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
228
        $sheet->getWriter()->write('</sheetView>');
229
        $sheet->getWriter()->write('</sheetViews>');
230
        $sheet->getWriter()->write('<cols>');
231
        $sheet->getWriter()->write('<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="11.5"/>');
232
        $sheet->getWriter()->write('</cols>');
233
        $sheet->getWriter()->write('<sheetData>');
234
    }
235
236
    /**
237
     * @param $cellFormat
238
     *
239
     * @return string
240
     */
241
    private function determineCellType($cellFormat)
242
    {
243
        $cellFormat = str_replace("[RED]", "", $cellFormat);
244
        if ($cellFormat == 'GENERAL') {
245
            return 'string';
246
        }
247
        if ($cellFormat == '0') {
248
            return 'numeric';
249
        }
250
        if (preg_match("/[H]{1,2}:[M]{1,2}/", $cellFormat)) {
251
            return 'datetime';
252
        }
253
        if (preg_match("/[M]{1,2}:[S]{1,2}/", $cellFormat)) {
254
            return 'datetime';
255
        }
256
        if (preg_match("/[YY]{2,4}/", $cellFormat)) {
257
            return 'date';
258
        }
259
        if (preg_match("/[D]{1,2}/", $cellFormat)) {
260
            return 'date';
261
        }
262
        if (preg_match("/[M]{1,2}/", $cellFormat)) {
263
            return 'date';
264
        }
265
        if (preg_match("/$/", $cellFormat)) {
266
            return 'currency';
267
        }
268
        if (preg_match("/%/", $cellFormat)) {
269
            return 'percent';
270
        }
271
        if (preg_match("/0/", $cellFormat)) {
272
            return 'numeric';
273
        }
274
275
        return 'string';
276
    }
277
278
    /**
279
     * @todo  check escaping
280
     *
281
     * @param $cellFormat
282
     *
283
     * @return string
284
     */
285
    private function escapeCellFormat($cellFormat)
286
    {
287
        $ignoreUntil = '';
288
        $escaped = '';
289
        for ($i = 0, $ix = strlen($cellFormat); $i < $ix; $i++) {
290
            $c = $cellFormat[$i];
291
            if ($ignoreUntil == '' && $c == '[') {
292
                $ignoreUntil = ']';
293
            } else {
294
                if ($ignoreUntil == '' && $c == '"') {
295
                    $ignoreUntil = '"';
296
                } else {
297
                    if ($ignoreUntil == $c) {
298
                        $ignoreUntil = '';
299
                    }
300
                }
301
            }
302
            if ($ignoreUntil == '' &&
303
                ($c == ' ' || $c == '-' || $c == '(' || $c == ')') &&
304
                ($i == 0 || $cellFormat[$i - 1] != '_')
305
            ) {
306
                $escaped .= "\\".$c;
307
            } else {
308
                $escaped .= $c;
309
            }
310
        }
311
312
        return $escaped;
313
    }
314
315
    /**
316
     * backwards compatibility
317
     *
318
     * @param $cellFormat
319
     *
320
     * @return int|mixed
321
     */
322
    private function addCellFormat($cellFormat)
323
    {
324
        switch ($cellFormat) {
325
            case 'string':
326
                $cellFormat = 'GENERAL';
327
                break;
328
            case 'integer':
329
                $cellFormat = '0';
330
                break;
331
            case 'date':
332
                $cellFormat = 'YYYY-MM-DD';
333
                break;
334
            case 'datetime':
335
                $cellFormat = 'YYYY-MM-DD HH:MM:SS';
336
                break;
337
            case 'dollar':
338
                $cellFormat = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
339
                break;
340
            case 'money':
341
                $cellFormat = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
342
                break;
343
            case 'euro':
344
                $cellFormat = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]';
345
                break;
346
            case 'NN':
347
                $cellFormat = 'DDD';
348
                break;
349
            case 'NNN':
350
                $cellFormat = 'DDDD';
351
                break;
352
            case 'NNNN':
353
                $cellFormat = 'DDDD", "';
354
                break;
355
        }
356
357
        $cellFormat = strtoupper($cellFormat);
358
        $position = array_search($cellFormat, $this->cellFormats, $strict = true);
359
        if ($position === false) {
360
            $position = count($this->cellFormats);
361
            $this->cellFormats[] = $this->escapeCellFormat($cellFormat);
362
            $this->cellTypes[] = $this->determineCellType($cellFormat);
363
        }
364
365
        return $position;
366
    }
367
368
    /**
369
     * @param string $sheetName
370
     * @param array $headerTypes
371
     * @param bool $suppressRow
372
     */
373
    public function writeSheetHeader($sheetName, array $headerTypes, $suppressRow = false)
374
    {
375
        if (empty($sheetName) || empty($headerTypes) || !empty($this->sheets[$sheetName])) {
376
            return;
377
        }
378
        $this->initializeSheet($sheetName);
379
        /** @var Sheet $sheet */
380
        $sheet = &$this->sheets[$sheetName];
381
        $sheet->setColumns([]);
382
        foreach ($headerTypes as $v) {
383
            $sheet->setColumn($this->addCellFormat($v));
384
        }
385
386
        if (!$suppressRow) {
387
            $header_row = array_keys($headerTypes);
388
            $sheet->getWriter()->write(
389
                '<row collapsed="false" customFormat="false" 
390
                customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">'
391
            );
392
            foreach ($header_row as $k => $v) {
393
                $this->writeCell($sheet->getWriter(), 0, $k, $v, $cell_format_index = '0');
394
            }
395
            $sheet->getWriter()->write('</row>');
396
            $sheet->increaseRowCount();
397
        }
398
        $this->currentSheet = $sheetName;
399
    }
400
401
    /**
402
     * @param string $sheetName
403
     * @param array $row
404
     */
405
    public function writeSheetRow($sheetName, array $row)
406
    {
407
        if (empty($sheetName) || empty($row)) {
408
            return;
409
        }
410
        $this->initializeSheet($sheetName);
411
        /** @var Sheet $sheet */
412
        $sheet = &$this->sheets[$sheetName];
413
        $columns = $sheet->getColumns();
414
        if (empty($columns)) {
415
            $sheet->setColumns(array_fill($from = 0, $until = count($row), '0'));//'0'=>'string'
416
        }
417
        $sheet->getWriter()->write(
418
            '<row collapsed="false" customFormat="false" customHeight="false" 
419
            hidden="false" ht="12.1" outlineLevel="0" r="'.($sheet->getRowCount() + 1).'">'
420
        );
421
        $column_count = 0;
422
        $sheetColumns = $sheet->getColumns();
423
        foreach ($row as $k => $v) {
424
            $this->writeCell(
425
                $sheet->getWriter(),
426
                $sheet->getRowCount(),
427
                $column_count,
428
                $v,
429
                $sheetColumns[$column_count]
430
            );
431
            $column_count++;
432
        }
433
        $sheet->getWriter()->write('</row>');
434
        $sheet->increaseRowCount();
435
        $this->currentSheet = $sheetName;
436
    }
437
438
    /**
439
     * @param string $sheetName
440
     */
441
    protected function finalizeSheet($sheetName)
442
    {
443
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
444
            return;
445
        }
446
        /** @var Sheet $sheet */
447
        $sheet = &$this->sheets[$sheetName];
448
        $sheet->getWriter()->write('</sheetData>');
449
        $mergeCells = $sheet->getMergeCells();
450
        if (!empty($mergeCells)) {
451
            $sheet->getWriter()->write('<mergeCells>');
452
            foreach ($mergeCells as $range) {
453
                $sheet->getWriter()->write('<mergeCell ref="'.$range.'"/>');
454
            }
455
            $sheet->getWriter()->write('</mergeCells>');
456
        }
457
        $sheet->getWriter()->write(
458
            '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false"
459
                verticalCentered="false"/>'
460
        );
461
        $sheet->getWriter()->write(
462
            '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>'
463
        );
464
        $sheet->getWriter()->write(
465
            '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" 
466
                fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" 
467
                paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>'
468
        );
469
        $sheet->getWriter()->write('<headerFooter differentFirst="false" differentOddEven="false">');
470
        $sheet->getWriter()->write(
471
            '<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>'
472
        );
473
        $sheet->getWriter()->write(
474
            '<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>'
475
        );
476
        $sheet->getWriter()->write('</headerFooter>');
477
        $sheet->getWriter()->write('</worksheet>');
478
        $maxCell = self::xlsCell($sheet->getRowCount() - 1, count($sheet->getColumns()) - 1);
479
        $maxCellTag = '<dimension ref="A1:'.$maxCell.'"/>';
480
        $padding_length = $sheet->getMaxCellTagEnd() - $sheet->getMaxCellTagStart() - strlen($maxCellTag);
481
        $sheet->getWriter()->fSeek($sheet->getMaxCellTagStart());
482
        $sheet->getWriter()->write($maxCellTag.str_repeat(" ", $padding_length));
483
        $sheet->getWriter()->close();
484
        $sheet->setFinalized(true);
485
    }
486
487
    /**
488
     * @param string $sheetName
489
     * @param int $startCellRow
490
     * @param int $startCellColumn
491
     * @param int $endCellRow
492
     * @param int $endCellColumn
493
     */
494
    public function markMergedCell($sheetName, $startCellRow, $startCellColumn, $endCellRow, $endCellColumn)
495
    {
496
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
497
            return;
498
        }
499
        $this->initializeSheet($sheetName);
500
        /** @var Sheet $sheet */
501
        $sheet = &$this->sheets[$sheetName];
502
        $startCell = self::xlsCell($startCellRow, $startCellColumn);
503
        $endCell = self::xlsCell($endCellRow, $endCellColumn);
504
        $sheet->setMergeCells($startCell.":".$endCell);
505
    }
506
507
    /**
508
     * @param array $data
509
     * @param string $sheetName
510
     * @param array $headerTypes
511
     */
512
    public function writeSheet(array $data, $sheetName = '', array $headerTypes = [])
513
    {
514
        $sheetName = empty($sheetName) ? 'Sheet1' : $sheetName;
515
        $data = empty($data) ? [['']] : $data;
516
        if (!empty($headerTypes)) {
517
            $this->writeSheetHeader($sheetName, $headerTypes);
518
        }
519
        foreach ($data as $i => $row) {
520
            $this->writeSheetRow($sheetName, $row);
521
        }
522
        $this->finalizeSheet($sheetName);
523
    }
524
525
    /**
526
     * @param Writer $file
527
     * @param $rowNumber
528
     * @param $columnNumber
529
     * @param $value
530
     * @param $cellIndex
531
     */
532
    protected function writeCell(
533
        Writer $file,
534
        $rowNumber,
535
        $columnNumber,
536
        $value,
537
        $cellIndex
538
    ) {
539
        $cellType = $this->cellTypes[$cellIndex];
540
        $cellName = self::xlsCell($rowNumber, $columnNumber);
541
        if (!is_scalar($value) || $value === '') {
542
            $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'"/>');
543
        } elseif (is_string($value) && $value{0} == '=') {
544
            $file->write(
545
                sprintf('<c r="%s" s="%s" t="s"><f>%s</f></c>', $cellName, $cellIndex, self::xmlspecialchars($value))
546
            );
547
        } elseif ($cellType == 'date') {
548
            $file->write(
549
                sprintf('<c r="%s" s="%s" t="n"><v>%s</v></c>', $cellName, $cellIndex, self::convertDateTime($value))
550
            );
551
        } elseif ($cellType == 'datetime') {
552
            $file->write(
553
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.self::convertDateTime($value).'</v></c>'
554
            );
555
        } elseif ($cellType == 'currency' || $cellType == 'percent' || $cellType == 'numeric') {
556
            $file->write(
557
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.self::xmlspecialchars($value).'</v></c>'
558
            );
559
        } else {
560
            if (!is_string($value)) {
561
                $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
562
            } else {
563
                if ($value{0} != '0' && $value{0} != '+' && filter_var(
564
                    $value,
565
                    FILTER_VALIDATE_INT,
566
                    ['options' => ['max_range' => self::EXCEL_MAX_RANGE]]
567
                )) {
568
                    $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
569
                } else {
570
                    $file->write(
571
                        '<c r="'.$cellName.'" s="'.$cellIndex.'" t="s"><v>'.self::xmlspecialchars(
572
                            $this->setSharedString($value)
573
                        ).'</v></c>'
574
                    );
575
                }
576
            }
577
        }
578
    }
579
580
    /**
581
     * @return string
582
     */
583
    protected function writeStylesXML()
584
    {
585
        $temporaryFilename = $this->tempFilename();
586
        $file = new Writer($temporaryFilename);
587
        $file->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
588
        $file->write('<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
589
        $file->write('<numFmts count="'.count($this->cellFormats).'">');
590
        foreach ($this->cellFormats as $i => $v) {
591
            $file->write('<numFmt numFmtId="'.(164 + $i).'" formatCode="'.self::xmlspecialchars($v).'" />');
592
        }
593
        $file->write('</numFmts>');
594
        $file->write('<fonts count="4">');
595
        $file->write('<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
596
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
597
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
598
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
599
        $file->write('</fonts>');
600
        $file->write('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
601
        $file->write('<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>');
602
        $file->write('<cellStyleXfs count="20">');
603
        $file->write('<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
604
        $file->write('<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
605
        $file->write('<protection hidden="false" locked="true"/>');
606
        $file->write('</xf>');
607
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
608
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
609
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
610
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
611
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
612
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
613
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
614
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
615
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
616
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
617
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
618
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
619
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
620
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
621
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
622
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
623
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
624
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
625
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
626
        $file->write('</cellStyleXfs>');
627
        $file->write('<cellXfs count="'.count($this->cellFormats).'">');
628
        foreach ($this->cellFormats as $i => $v) {
629
            $file->write(
630
                '<xf applyAlignment="false" applyBorder="false" applyFont="false" 
631
            applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="'.(164 + $i).'" xfId="0"/>'
632
            );
633
        }
634
        $file->write('</cellXfs>');
635
        $file->write('<cellStyles count="6">');
636
        $file->write('<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
637
        $file->write('<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
638
        $file->write('<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
639
        $file->write('<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
640
        $file->write('<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
641
        $file->write('<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
642
        $file->write('</cellStyles>');
643
        $file->write('</styleSheet>');
644
        $file->close();
645
        return $temporaryFilename;
646
    }
647
648
    /**
649
     * @param $v
650
     *
651
     * @return int|mixed
652
     */
653
    protected function setSharedString($v)
654
    {
655
        if (isset($this->sharedStrings[$v])) {
656
            $stringValue = $this->sharedStrings[$v];
657
        } else {
658
            $stringValue = count($this->sharedStrings);
659
            $this->sharedStrings[$v] = $stringValue;
660
        }
661
        $this->sharedStringCount++;//non-unique count
662
663
        return $stringValue;
664
    }
665
666
    /**
667
     * @return string
668
     */
669
    protected function writeSharedStringsXML()
670
    {
671
        $tempFilename = $this->tempFilename();
672
        $file = new Writer($tempFilename);
673
        $sharedStrings = new SharedStrings($this->sharedStringCount, $this->sharedStrings);
674
        $file->write($sharedStrings->buildSharedStringsXML());
675
        $file->close();
676
677
        return $tempFilename;
678
    }
679
680
    /**
681
     * @return string
682
     */
683
    protected function buildWorkbookXML()
684
    {
685
        $i = 0;
686
        $workbookXml = '';
687
        $workbookXml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
688
        $workbookXml .= '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"';
689
        $workbookXml .= ' xmlns:r="'.$this->urlSchemaFormat.'/relationships">';
690
        $workbookXml .= '<fileVersion appName="Calc"/><workbookPr backupFile="false"';
691
        $workbookXml .= ' showObjects="all" date1904="false"/><workbookProtection/>';
692
        $workbookXml .= '<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true"';
693
        $workbookXml .= ' showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192"';
694
        $workbookXml .= ' windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>';
695
        $workbookXml .= '<sheets>';
696
        foreach ($this->sheets as $sheet_name => $sheet) {
697
            /** @var Sheet $sheet */
698
            $workbookXml .= '<sheet name="'.self::xmlspecialchars($sheet->getSheetName()).'"';
699
            $workbookXml .= ' sheetId="'.($i + 1).'" state="visible" r:id="rId'.($i + 2).'"/>';
700
            $i++;
701
        }
702
        $workbookXml .= '</sheets>';
703
        $workbookXml .= '<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
704
705
        return $workbookXml;
706
    }
707
708
    /**
709
     * @param int $rowNumber
710
     * @param int $columnNumber
711
     *
712
     * @return string Cell label/coordinates (A1, C3, AA42)
713
     */
714
    public static function xlsCell($rowNumber, $columnNumber)
715
    {
716
        $n = $columnNumber;
717
        for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
718
            $r = chr($n % 26 + 0x41).$r;
719
        }
720
721
        return $r.($rowNumber + 1);
722
    }
723
724
    /**
725
     * @param $string
726
     */
727
    public static function log($string)
728
    {
729
        file_put_contents(
730
            "php://stderr",
731
            date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n"
732
        );
733
    }
734
735
    /**
736
     * @link https://msdn.microsoft.com/ru-RU/library/aa365247%28VS.85%29.aspx
737
     *
738
     * @param string $filename
739
     *
740
     * @return mixed
741
     */
742
    public static function checkFilename($filename)
743
    {
744
        $invalidCharacter = array_merge(
745
            array_map('chr', range(0, 31)),
746
            ['<', '>', '?', '"', ':', '|', '\\', '/', '*', '&']
747
        );
748
749
        return str_replace($invalidCharacter, '', $filename);
750
    }
751
752
    /**
753
     * @param $val
754
     *
755
     * @return mixed
756
     */
757
    public static function xmlspecialchars($val)
758
    {
759
        return str_replace("'", "&#39;", htmlspecialchars($val));
760
    }
761
762
    /**
763
     * @param string $dateInput
764
     *
765
     * @return int
766
     */
767
    public static function convertDateTime($dateInput)
768
    {
769
        // Time expressed as fraction of 24h hours in seconds
770
        $seconds = 0;
771
        $year = $month = $day = 0;
772
        $dateTime = $dateInput;
773
        if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $dateTime, $matches)) {
774
            list($year, $month, $day) = $matches;
775
        }
776
        if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $dateTime, $matches)) {
777
            list($hour, $min, $sec) = $matches;
778
            $seconds = ($hour * 60 * 60 + $min * 60 + $sec) / (24 * 60 * 60);
779
        }
780
        //using 1900 as epoch, not 1904, ignoring 1904 special case
781
        // Special cases for Excel.
782
        if ("$year-$month-$day" == '1899-12-31') {
783
            return $seconds;
784
        }    // Excel 1900 epoch
785
        if ("$year-$month-$day" == '1900-01-00') {
786
            return $seconds;
787
        }    // Excel 1900 epoch
788
        if ("$year-$month-$day" == '1900-02-29') {
789
            return 60 + $seconds;
790
        }
791
        // Excel false leapday
792
        /*
793
         We calculate the date by calculating the number of days since the epoch
794
         and adjust for the number of leap days. We calculate the number of leap
795
         days by normalising the year in relation to the epoch. Thus the year 2000
796
         becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
797
        */
798
        $epoch  = 1900;
799
        $offset = 0;
800
        $norm   = 300;
801
        $range  = $year - $epoch;
802
        // Set month days and check for leap year.
803
        $leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100)) ) ? 1 : 0;
804
        $mdays = array( 31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
805
        // Some boundary checks
806
        if ($year < $epoch || $year > 9999) {
807
            return 0;
808
        }
809
        if ($month < 1 || $month > 12) {
810
            return 0;
811
        }
812
        if ($day < 1 || $day > $mdays[$month - 1]) {
813
            return 0;
814
        }
815
        // Accumulate the number of days since the epoch.
816
        // Add days for current month
817
        $days = $day;
818
        // Add days for past months
819
        $days += array_sum(array_slice($mdays, 0, $month - 1));
820
        // Add days for past years
821
        $days += $range * 365;
822
        // Add leapdays
823
        $days += intval(($range) / 4);
824
        // Subtract 100 year leapdays
825
        $days -= intval(($range + $offset) / 100);
826
        // Add 400 year leapdays
827
        $days += intval(($range + $offset + $norm) / 400);
828
        // Already counted above
829
        $days -= $leap;
830
        // Adjust for Excel erroneously treating 1900 as a leap year.
831
        if ($days > 59) {
832
            $days++;
833
        }
834
835
        return $days + $seconds;
836
    }
837
}
838