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 ( 44810d...9c6bac )
by Denis
03:46
created

ExcelWriter::setTmpDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
namespace Ellumilel;
3
4
use Ellumilel\DocProps\App;
5
use Ellumilel\DocProps\Core;
6
use Ellumilel\Helpers\ExcelHelper;
7
use Ellumilel\Rels\Relationships;
8
use Ellumilel\Xl\SharedStrings;
9
use Ellumilel\Xl\Styles;
10
use Ellumilel\Xl\Workbook;
11
use Ellumilel\Xl\Worksheets\SheetXml;
12
13
/**
14
 * Class ExcelWriter
15
 * @package Ellumilel
16
 * @author Denis Tikhonov <[email protected]>
17
 */
18
class ExcelWriter
19
{
20
    /** @var array */
21
    protected $sheets = [];
22
    /** @var array */
23
    protected $sharedStrings = [];
24
    /** @var int */
25
    protected $sharedStringCount = 0;
26
    /** @var array */
27
    protected $tempFiles = [];
28
    /** @var array */
29
    protected $cellFormats = [];
30
    /** @var array */
31
    protected $cellTypes = [];
32
    /** @var string  */
33
    protected $currentSheet = '';
34
    /** @var null */
35
    protected $tmpDir = null;
36
    /** @var Core */
37
    protected $core;
38
    /** @var App */
39
    protected $app;
40
    /** @var Workbook */
41
    protected $workbook;
42
    /** @var SheetXml */
43
    protected $sheetXml;
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
        if (!ini_get('date.timezone')) {
55
            date_default_timezone_set('UTC');
56
        }
57
        $this->addCellFormat($cell_format = 'GENERAL');
58
        $this->core = new Core();
59
        $this->app = new App();
60
        $this->workbook = new Workbook();
61
        $this->sheetXml = new SheetXml();
62
    }
63
64
    /**
65
     * @param string $author
66
     */
67
    public function setAuthor($author)
68
    {
69
        $this->core->setAuthor($author);
70
    }
71
72
    public function __destruct()
73
    {
74
        if (!empty($this->tempFiles)) {
75
            foreach ($this->tempFiles as $tempFile) {
76
                if (file_exists($tempFile)) {
77
                    unlink($tempFile);
78
                }
79
            }
80
        }
81
    }
82
83
    /**
84
     * @param $dir
85
     */
86
    public function setTmpDir($dir)
87
    {
88
        $this->tmpDir = $dir;
89
    }
90
91
    /**
92
     * Return tmpFileName
93
     * @return string
94
     */
95
    protected function tempFilename()
96
    {
97
        $tmpDir = is_null($this->tmpDir) ? sys_get_temp_dir() : $this->tmpDir;
98
        $filename = tempnam($tmpDir, "excelWriter_");
99
        $this->tempFiles[] = $filename;
100
101
        return $filename;
102
    }
103
104
    public function writeToStdOut()
105
    {
106
        $tempFile = $this->tempFilename();
107
        $this->writeToFile($tempFile);
108
        readfile($tempFile);
109
    }
110
111
    /**
112
     * @return string
113
     */
114
    public function writeToString()
115
    {
116
        $tempFile = $this->tempFilename();
117
        $this->writeToFile($tempFile);
118
        $string = file_get_contents($tempFile);
119
120
        return $string;
121
    }
122
123
    /**
124
     * @param string $filename
125
     */
126
    public function writeToFile($filename)
127
    {
128
        $zip = new \ZipArchive();
129
        foreach ($this->sheets as $sheetName => $sheet) {
130
            $this->finalizeSheet($sheetName);
131
        }
132
        $this->checkAndUnlink($zip, $filename);
133
        $this->workbook->setSheet($this->sheets);
134
135
        $contentTypes = new ContentTypes(!empty($this->sharedStrings));
136
        $contentTypes->setSheet($this->sheets);
137
138
        $rel = new Relationships(!empty($this->sharedStrings));
139
        $rel->setSheet($this->sheets);
140
        $zip->addEmptyDir("docProps/");
141
        $zip->addFromString("docProps/app.xml", $this->app->buildAppXML());
142
        $zip->addFromString("docProps/core.xml", $this->core->buildCoreXML());
143
        $zip->addEmptyDir("_rels/");
144
        $zip->addFromString("_rels/.rels", $rel->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
            );
155
        }
156
        $zip->addFromString("xl/workbook.xml", $this->workbook->buildWorkbookXML());
157
        $zip->addFile(
158
            $this->writeStylesXML(),
159
            "xl/styles.xml"
160
        );
161
        $zip->addFromString("[Content_Types].xml", $contentTypes->buildContentTypesXML());
162
        $zip->addEmptyDir("xl/_rels/");
163
        $zip->addFromString("xl/_rels/workbook.xml.rels", $rel->buildWorkbookRelationshipsXML());
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';
188
        $maxCell = ExcelHelper::xlsCell(ExcelHelper::EXCEL_MAX_ROW, ExcelHelper::EXCEL_MAX_COL);
189
        $sheet->getWriter()->write($this->sheetXml->getXml());
190
        $sheet->getWriter()->write($this->sheetXml->getWorksheet());
191
        $sheet->getWriter()->write($this->sheetXml->getSheetPr());
192
        $sheet->setMaxCellTagStart($sheet->getWriter()->fTell());
193
        $sheet->getWriter()->write($this->sheetXml->getDimension($maxCell));
194
        $sheet->setMaxCellTagEnd($sheet->getWriter()->fTell());
195
        $sheet->getWriter()->write($this->sheetXml->getSheetViews($selectedTab));
196
        $sheet->getWriter()->write($this->sheetXml->getCools());
197
        $sheet->getWriter()->write('<sheetData>');
198
    }
199
200
    /**
201
     * @param $cellFormat
202
     *
203
     * @return string
204
     */
205
    private function determineCellType($cellFormat)
206
    {
207
        $cellFormat = str_replace("[RED]", "", $cellFormat);
208
        if ($cellFormat == 'GENERAL') {
209
            return 'string';
210
        }
211
        if ($cellFormat == '0') {
212
            return 'numeric';
213
        }
214
        $checkArray = [
215
            'datetime' => [
216
                "/[H]{1,2}:[M]{1,2}/",
217
                "/[M]{1,2}:[S]{1,2}/",
218
            ],
219
            'numeric' => [
220
                "/0/",
221
            ],
222
            'date' => [
223
                "/[YY]{2,4}/",
224
                "/[D]{1,2}/",
225
                "/[M]{1,2}/",
226
            ],
227
            'currency' => [
228
                "/$/",
229
            ],
230
            'percent' => [
231
                "/%/",
232
            ],
233
        ];
234
        foreach ($checkArray as $type => $item) {
235
            foreach ($item as $prMatch) {
236
                if (preg_match($prMatch, $cellFormat)) {
237
                    return $type;
238
                }
239
            }
240
        }
241
242
        return 'string';
243
    }
244
245
    /**
246
     * @todo  check escaping
247
     *
248
     * @param $cellFormat
249
     *
250
     * @return string
251
     */
252
    private function escapeCellFormat($cellFormat)
253
    {
254
        $ignoreUntil = '';
255
        $escaped = '';
256
        for ($i = 0, $ix = strlen($cellFormat); $i < $ix; $i++) {
257
            $c = $cellFormat[$i];
258
            if ($ignoreUntil == '' && $c == '[') {
259
                $ignoreUntil = ']';
260
            } else {
261
                if ($ignoreUntil == '' && $c == '"') {
262
                    $ignoreUntil = '"';
263
                } else {
264
                    if ($ignoreUntil == $c) {
265
                        $ignoreUntil = '';
266
                    }
267
                }
268
            }
269
            if ($ignoreUntil == '' &&
270
                ($c == ' ' || $c == '-' || $c == '(' || $c == ')') &&
271
                ($i == 0 || $cellFormat[$i - 1] != '_')
272
            ) {
273
                $escaped .= "\\".$c;
274
            } else {
275
                $escaped .= $c;
276
            }
277
        }
278
279
        return $escaped;
280
    }
281
282
    /**
283
     * backwards compatibility
284
     *
285
     * @param $cellFormat
286
     *
287
     * @return int|mixed
288
     */
289
    private function addCellFormat($cellFormat)
290
    {
291
292
293
        $cellFormat = strtoupper($this->getCellFormat($cellFormat));
294
        $position = array_search($cellFormat, $this->cellFormats, $strict = true);
295
        if ($position === false) {
296
            $position = count($this->cellFormats);
297
            $this->cellFormats[] = $this->escapeCellFormat($cellFormat);
298
            $this->cellTypes[] = $this->determineCellType($cellFormat);
299
        }
300
301
        return $position;
302
    }
303
304
    /**
305
     * @param string $cellFormat
306
     *
307
     * @return string
308
     */
309
    private function getCellFormat($cellFormat)
310
    {
311
        $formatArray = [
312
            'string' => 'GENERAL',
313
            'integer' => '0',
314
            'date' => 'YYYY-MM-DD',
315
            'datetime' => 'YYYY-MM-DD HH:MM:SS',
316
            'dollar' => '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00',
317
            'money' => '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00',
318
            'euro' => '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]',
319
            'NN' => 'DDD',
320
            'NNN' => 'DDDD',
321
            'NNNN' => 'DDDD", "',
322
        ];
323
324
        if (array_key_exists($cellFormat, $formatArray)) {
325
            return $formatArray[$cellFormat];
326
        }
327
        return $cellFormat;
328
    }
329
330
    /**
331
     * @param string $sheetName
332
     * @param array $headerTypes
333
     * @param bool $suppressRow
334
     */
335
    public function writeSheetHeader($sheetName, array $headerTypes, $suppressRow = false)
336
    {
337
        if (empty($sheetName) || empty($headerTypes) || !empty($this->sheets[$sheetName])) {
338
            return;
339
        }
340
        $this->initializeSheet($sheetName);
341
        /** @var Sheet $sheet */
342
        $sheet = &$this->sheets[$sheetName];
343
        $sheet->setColumns([]);
344
        foreach ($headerTypes as $val) {
345
            $sheet->setColumn($this->addCellFormat($val));
346
        }
347
        if (!$suppressRow) {
348
            $this->writeRowHeader($sheet, array_keys($headerTypes));
349
            $sheet->increaseRowCount();
350
        }
351
        $this->currentSheet = $sheetName;
352
    }
353
354
    /**
355
     * @param Sheet $sheet
356
     * @param array $headerRow
357
     */
358
    private function writeRowHeader(Sheet $sheet, $headerRow)
359
    {
360
        $sheet->getWriter()->write(
361
            '<row collapsed="false" customFormat="false" 
362
                customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">'
363
        );
364
        foreach ($headerRow as $k => $v) {
365
            $this->writeCell($sheet->getWriter(), 0, $k, $v, $cell_format_index = '0');
366
        }
367
        $sheet->getWriter()->write('</row>');
368
    }
369
370
    /**
371
     * @param string $sheetName
372
     * @param array $row
373
     */
374
    public function writeSheetRow($sheetName, array $row)
375
    {
376
        if (empty($sheetName) || empty($row)) {
377
            return;
378
        }
379
        $this->initializeSheet($sheetName);
380
        /** @var Sheet $sheet */
381
        $sheet = &$this->sheets[$sheetName];
382
        $columns = $sheet->getColumns();
383
        if (empty($columns)) {
384
            $sheet->setColumns(array_fill($from = 0, $until = count($row), '0'));
385
        }
386
        $sheet->getWriter()->write(
387
            '<row collapsed="false" customFormat="false" customHeight="false" 
388
            hidden="false" ht="12.1" outlineLevel="0" r="'.($sheet->getRowCount() + 1).'">'
389
        );
390
        $column_count = 0;
391
        $sheetColumns = $sheet->getColumns();
392
        foreach ($row as $k => $v) {
393
            $this->writeCell(
394
                $sheet->getWriter(),
395
                $sheet->getRowCount(),
396
                $column_count,
397
                $v,
398
                $sheetColumns[$column_count]
399
            );
400
            $column_count++;
401
        }
402
        $sheet->getWriter()->write('</row>');
403
        $sheet->increaseRowCount();
404
        $this->currentSheet = $sheetName;
405
    }
406
407
    /**
408
     * @param string $sheetName
409
     */
410
    protected function finalizeSheet($sheetName)
411
    {
412
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
413
            return;
414
        }
415
        /** @var Sheet $sheet */
416
        $sheet = &$this->sheets[$sheetName];
417
        $sheet->getWriter()->write('</sheetData>');
418
        $mergeCells = $sheet->getMergeCells();
419
        if (!empty($mergeCells)) {
420
            $sheet->getWriter()->write($this->sheetXml->getMergeCells($mergeCells));
421
        }
422
        $sheet->getWriter()->write($this->sheetXml->getPrintOptions());
423
        $sheet->getWriter()->write($this->sheetXml->getPageMargins());
424
        $sheet->getWriter()->write($this->sheetXml->getPageSetup());
425
        $sheet->getWriter()->write($this->sheetXml->getHeaderFooter());
426
        $sheet->getWriter()->write('</worksheet>');
427
        $maxCell = ExcelHelper::xlsCell($sheet->getRowCount() - 1, count($sheet->getColumns()) - 1);
428
        $maxCellTag = $this->sheetXml->getDimension($maxCell);
429
        $paddingLength = $sheet->getMaxCellTagEnd() - $sheet->getMaxCellTagStart() - strlen($maxCellTag);
430
        $sheet->getWriter()->fSeek($sheet->getMaxCellTagStart());
431
        $sheet->getWriter()->write($maxCellTag.str_repeat(" ", $paddingLength));
432
        $sheet->getWriter()->close();
433
        $sheet->setFinalized(true);
434
    }
435
436
    /**
437
     * @param string $sheetName
438
     * @param int $startCellRow
439
     * @param int $startCellColumn
440
     * @param int $endCellRow
441
     * @param int $endCellColumn
442
     */
443
    public function markMergedCell($sheetName, $startCellRow, $startCellColumn, $endCellRow, $endCellColumn)
444
    {
445
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
446
            return;
447
        }
448
        $this->initializeSheet($sheetName);
449
        /** @var Sheet $sheet */
450
        $sheet = &$this->sheets[$sheetName];
451
        $startCell = ExcelHelper::xlsCell($startCellRow, $startCellColumn);
452
        $endCell = ExcelHelper::xlsCell($endCellRow, $endCellColumn);
453
        $sheet->setMergeCells($startCell.":".$endCell);
454
    }
455
456
    /**
457
     * @param array $data
458
     * @param string $sheetName
459
     * @param array $headerTypes
460
     */
461
    public function writeSheet(array $data, $sheetName = '', array $headerTypes = [])
462
    {
463
        $sheetName = empty($sheetName) ? 'Sheet1' : $sheetName;
464
        $data = empty($data) ? [['']] : $data;
465
        if (!empty($headerTypes)) {
466
            $this->writeSheetHeader($sheetName, $headerTypes);
467
        }
468
        foreach ($data as $i => $row) {
469
            $this->writeSheetRow($sheetName, $row);
470
        }
471
        $this->finalizeSheet($sheetName);
472
    }
473
474
    /**
475
     * @param Writer $file
476
     * @param $rowNumber
477
     * @param $columnNumber
478
     * @param $value
479
     * @param $cellIndex
480
     */
481
    protected function writeCell(
482
        Writer $file,
483
        $rowNumber,
484
        $columnNumber,
485
        $value,
486
        $cellIndex
487
    ) {
488
        $cellType = $this->cellTypes[$cellIndex];
489
        $cellName = ExcelHelper::xlsCell($rowNumber, $columnNumber);
490
        if (!is_scalar($value) || $value === '') {
491
            $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'"/>');
492
        } elseif (is_string($value) && $value{0} == '=') {
493
            $file->write(
494
                sprintf(
495
                    '<c r="%s" s="%s" t="s"><f>%s</f></c>',
496
                    $cellName,
497
                    $cellIndex,
498
                    ExcelHelper::xmlspecialchars($value)
499
                )
500
            );
501
        } elseif ($cellType == 'date') {
502
            $file->write(
503
                sprintf(
504
                    '<c r="%s" s="%s" t="n"><v>%s</v></c>',
505
                    $cellName,
506
                    $cellIndex,
507
                    ExcelHelper::convertDateTime($value)
508
                )
509
            );
510
        } elseif ($cellType == 'datetime') {
511
            $file->write(
512
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.ExcelHelper::convertDateTime($value).'</v></c>'
513
            );
514
        } elseif ($cellType == 'currency' || $cellType == 'percent' || $cellType == 'numeric') {
515
            $file->write(
516
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.ExcelHelper::xmlspecialchars($value).'</v></c>'
517
            );
518
        } else {
519
            if (!is_string($value)) {
520
                $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
521
            } else {
522
                if ($value{0} != '0' && $value{0} != '+' && filter_var(
523
                    $value,
524
                    FILTER_VALIDATE_INT,
525
                    ['options' => ['max_range' => ExcelHelper::EXCEL_MAX_RANGE]]
526
                )) {
527
                    $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
528
                } else {
529
                    $file->write(
530
                        '<c r="'.$cellName.'" s="'.$cellIndex.'" t="s"><v>'.ExcelHelper::xmlspecialchars(
531
                            $this->setSharedString($value)
532
                        ).'</v></c>'
533
                    );
534
                }
535
            }
536
        }
537
    }
538
539
    /**
540
     * @return string
541
     */
542
    protected function writeStylesXML()
543
    {
544
        $temporaryFilename = $this->tempFilename();
545
        $file = new Writer($temporaryFilename);
546
        $styles = new Styles();
547
        $styles->setCellFormats($this->cellFormats);
548
        $file->write($styles->buildStylesXML());
549
550
        return $temporaryFilename;
551
    }
552
553
    /**
554
     * @param $v
555
     *
556
     * @return int|mixed
557
     */
558
    protected function setSharedString($v)
559
    {
560
        if (isset($this->sharedStrings[$v])) {
561
            $stringValue = $this->sharedStrings[$v];
562
        } else {
563
            $stringValue = count($this->sharedStrings);
564
            $this->sharedStrings[$v] = $stringValue;
565
        }
566
        $this->sharedStringCount++;
567
568
        return $stringValue;
569
    }
570
571
    /**
572
     * @return string
573
     */
574
    protected function writeSharedStringsXML()
575
    {
576
        $tempFilename = $this->tempFilename();
577
        $file = new Writer($tempFilename);
578
        $sharedStrings = new SharedStrings($this->sharedStringCount, $this->sharedStrings);
579
        $file->write($sharedStrings->buildSharedStringsXML());
580
        $file->close();
581
582
        return $tempFilename;
583
    }
584
585
    /**
586
     * @param $string
587
     */
588
    public static function log($string)
589
    {
590
        file_put_contents(
591
            "php://stderr",
592
            date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n"
593
        );
594
    }
595
596
    /**
597
     * @link https://msdn.microsoft.com/ru-RU/library/aa365247%28VS.85%29.aspx
598
     *
599
     * @param string $filename
600
     *
601
     * @return mixed
602
     */
603 View Code Duplication
    public static function checkFilename($filename)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
604
    {
605
        $invalidCharacter = array_merge(
606
            array_map('chr', range(0, 31)),
607
            ['<', '>', '?', '"', ':', '|', '\\', '/', '*', '&']
608
        );
609
610
        return str_replace($invalidCharacter, '', $filename);
611
    }
612
613
    /**
614
     * @param \ZipArchive $zip
615
     * @param string $filename
616
     */
617
    private function checkAndUnlink(\ZipArchive $zip, $filename)
618
    {
619
        if (file_exists($filename) && is_writable($filename)) {
620
            unlink($filename);
621
        }
622
        if (empty($this->sheets) || !$zip->open($filename, \ZipArchive::CREATE)) {
623
            throw new \RuntimeException(
624
                "Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined or unable to create zip."
625
            );
626
        }
627
    }
628
}
629