Completed
Pull Request — master (#1761)
by Martin
06:48
created

XLSXWriter::numberFormatStandardized()   D

Complexity

Conditions 23
Paths 288

Size

Total Lines 59
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 59
rs 4.6579
c 0
b 0
f 0
cc 23
eloc 42
nc 288
nop 1

How to fix   Long Method    Complexity   

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
3
namespace luya\helpers;
4
5
use ZipArchive;
6
7
/*
8
 * @license MIT License
9
 *
10
 * @see https://raw.githubusercontent.com/mk-j/PHP_XLSXWriter/master/xlsxwriter.class.php
11
 * */
12
13
class XLSXWriter
14
{
15
    //http://www.ecma-international.org/publications/standards/Ecma-376.htm
16
    //http://officeopenxml.com/SSstyles.php
17
    //------------------------------------------------------------------
18
    //http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP010073849.aspx
19
    const EXCEL_2007_MAX_ROW = 1048576;
20
    const EXCEL_2007_MAX_COL = 16384;
21
    //------------------------------------------------------------------
22
    protected $title;
23
    protected $subject;
24
    protected $author;
25
    protected $company;
26
    protected $description;
27
    protected $keywords = array();
28
29
    protected $current_sheet;
30
    protected $sheets = array();
31
    protected $temp_files = array();
32
    protected $cell_styles = array();
33
    protected $number_formats = array();
34
35
    public function __construct()
36
    {
37
        if (!ini_get('date.timezone')) {
38
            //using date functions can kick out warning if this isn't set
39
            date_default_timezone_set('UTC');
40
        }
41
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
42
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
43
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
44
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
45
    }
46
47
    public function setTitle($title = '')
48
    {
49
        $this->title = $title;
50
    }
51
52
    public function setSubject($subject = '')
53
    {
54
        $this->subject = $subject;
55
    }
56
57
    public function setAuthor($author = '')
58
    {
59
        $this->author = $author;
60
    }
61
62
    public function setCompany($company = '')
63
    {
64
        $this->company = $company;
65
    }
66
67
    public function setKeywords($keywords = '')
68
    {
69
        $this->keywords = $keywords;
0 ignored issues
show
Documentation Bug introduced by
It seems like $keywords of type string is incompatible with the declared type array of property $keywords.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
70
    }
71
72
    public function setDescription($description = '')
73
    {
74
        $this->description = $description;
75
    }
76
77
    public function setTempDir($tempdir = '')
78
    {
79
        $this->tempdir = $tempdir;
0 ignored issues
show
Bug introduced by
The property tempdir does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
80
    }
81
82
    public function __destruct()
83
    {
84
        if (!empty($this->temp_files)) {
85
            foreach ($this->temp_files as $temp_file) {
86
                @unlink($temp_file);
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...
87
            }
88
        }
89
    }
90
91
    protected function tempFilename()
92
    {
93
        $tempdir = !empty($this->tempdir) ? $this->tempdir : sys_get_temp_dir();
94
        $filename = tempnam($tempdir, "xlsx_writer_");
95
        $this->temp_files[] = $filename;
96
        return $filename;
97
    }
98
99
    public function writeToStdOut()
100
    {
101
        $temp_file = $this->tempFilename();
102
        self::writeToFile($temp_file);
103
        readfile($temp_file);
104
    }
105
106
    public function writeToString()
107
    {
108
        $temp_file = $this->tempFilename();
109
        self::writeToFile($temp_file);
110
        $string = file_get_contents($temp_file);
111
        return $string;
112
    }
113
114
    public function writeToFile($filename)
115
    {
116
        foreach ($this->sheets as $sheet_name => $sheet) {
117
            self::finalizeSheet($sheet_name);//making sure all footers have been written
118
        }
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
                return;
126
            }
127
        }
128
        $zip = new ZipArchive();
129
        if (empty($this->sheets)) {
130
            self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", no worksheets defined.");
131
            return;
132
        }
133
        if (!$zip->open($filename, ZipArchive::CREATE)) {
134
            self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", unable to create zip.");
135
            return;
136
        }
137
138
        $zip->addEmptyDir("docProps/");
139
        $zip->addFromString("docProps/app.xml", self::buildAppXML());
140
        $zip->addFromString("docProps/core.xml", self::buildCoreXML());
141
142
        $zip->addEmptyDir("_rels/");
143
        $zip->addFromString("_rels/.rels", self::buildRelationshipsXML());
144
145
        $zip->addEmptyDir("xl/worksheets/");
146
        foreach ($this->sheets as $sheet) {
147
            $zip->addFile($sheet->filename, "xl/worksheets/" . $sheet->xmlname);
148
        }
149
        $zip->addFromString("xl/workbook.xml", self::buildWorkbookXML());
150
        $zip->addFile($this->writeStylesXML(),
151
            "xl/styles.xml");  //$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...
152
        $zip->addFromString("[Content_Types].xml", self::buildContentTypesXML());
153
154
        $zip->addEmptyDir("xl/_rels/");
155
        $zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelsXML());
156
        $zip->close();
157
    }
158
159
    protected function initializeSheet(
160
        $sheet_name,
161
        $col_widths = array(),
162
        $auto_filter = false,
163
        $freeze_rows = false,
164
        $freeze_columns = false
165
    ) {
166
        //if already initialized
167
        if ($this->current_sheet == $sheet_name || isset($this->sheets[$sheet_name])) {
168
            return;
169
        }
170
171
        $sheet_filename = $this->tempFilename();
172
        $sheet_xmlname = 'sheet' . (count($this->sheets) + 1) . ".xml";
173
        $this->sheets[$sheet_name] = (object)array(
174
            'filename' => $sheet_filename,
175
            'sheetname' => $sheet_name,
176
            'xmlname' => $sheet_xmlname,
177
            'row_count' => 0,
178
            'file_writer' => new XLSXWriter_BuffererWriter($sheet_filename),
179
            'columns' => array(),
180
            'merge_cells' => array(),
181
            'max_cell_tag_start' => 0,
182
            'max_cell_tag_end' => 0,
183
            'auto_filter' => $auto_filter,
184
            'freeze_rows' => $freeze_rows,
185
            'freeze_columns' => $freeze_columns,
186
            'finalized' => false,
187
        );
188
        $sheet = &$this->sheets[$sheet_name];
189
        $tabselected = count($this->sheets) == 1 ? 'true' : 'false';//only first sheet is selected
190
        $max_cell = XLSXWriter::xlsCell(self::EXCEL_2007_MAX_ROW, self::EXCEL_2007_MAX_COL);//XFE1048577
191
        $sheet->file_writer->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n");
192
        $sheet->file_writer->write('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
193
        $sheet->file_writer->write('<sheetPr filterMode="false">');
194
        $sheet->file_writer->write('<pageSetUpPr fitToPage="false"/>');
195
        $sheet->file_writer->write('</sheetPr>');
196
        $sheet->max_cell_tag_start = $sheet->file_writer->ftell();
197
        $sheet->file_writer->write('<dimension ref="A1:' . $max_cell . '"/>');
198
        $sheet->max_cell_tag_end = $sheet->file_writer->ftell();
199
        $sheet->file_writer->write('<sheetViews>');
200
        $sheet->file_writer->write('<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" tabSelected="' . $tabselected . '" topLeftCell="A1" view="normal" windowProtection="false" workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">');
201
        if ($sheet->freeze_rows && $sheet->freeze_columns) {
202
            $sheet->file_writer->write('<pane ySplit="' . $sheet->freeze_rows . '" xSplit="' . $sheet->freeze_columns . '" topLeftCell="' . self::xlsCell($sheet->freeze_rows,
203
                    $sheet->freeze_columns) . '" activePane="bottomRight" state="frozen"/>');
204
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell($sheet->freeze_rows,
205
                    0) . '" activeCellId="0" pane="topRight" sqref="' . self::xlsCell($sheet->freeze_rows, 0) . '"/>');
206
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell(0,
207
                    $sheet->freeze_columns) . '" activeCellId="0" pane="bottomLeft" sqref="' . self::xlsCell(0,
208
                    $sheet->freeze_columns) . '"/>');
209
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell($sheet->freeze_rows,
210
                    $sheet->freeze_columns) . '" activeCellId="0" pane="bottomRight" sqref="' . self::xlsCell($sheet->freeze_rows,
211
                    $sheet->freeze_columns) . '"/>');
212
        } elseif ($sheet->freeze_rows) {
213
            $sheet->file_writer->write('<pane ySplit="' . $sheet->freeze_rows . '" topLeftCell="' . self::xlsCell($sheet->freeze_rows,
214
                    0) . '" activePane="bottomLeft" state="frozen"/>');
215
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell($sheet->freeze_rows,
216
                    0) . '" activeCellId="0" pane="bottomLeft" sqref="' . self::xlsCell($sheet->freeze_rows,
217
                    0) . '"/>');
218
        } elseif ($sheet->freeze_columns) {
219
            $sheet->file_writer->write('<pane xSplit="' . $sheet->freeze_columns . '" topLeftCell="' . self::xlsCell(0,
220
                    $sheet->freeze_columns) . '" activePane="topRight" state="frozen"/>');
221
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell(0,
222
                    $sheet->freeze_columns) . '" activeCellId="0" pane="topRight" sqref="' . self::xlsCell(0,
223
                    $sheet->freeze_columns) . '"/>');
224
        } else { // not frozen
225
            $sheet->file_writer->write('<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
226
        }
227
        $sheet->file_writer->write('</sheetView>');
228
        $sheet->file_writer->write('</sheetViews>');
229
        $sheet->file_writer->write('<cols>');
230
        $i = 0;
231
        if (!empty($col_widths)) {
232
            foreach ($col_widths as $column_width) {
233
                $sheet->file_writer->write('<col collapsed="false" hidden="false" max="' . ($i + 1) . '" min="' . ($i + 1) . '" style="0" customWidth="true" width="' . floatval($column_width) . '"/>');
234
                $i++;
235
            }
236
        }
237
        $sheet->file_writer->write('<col collapsed="false" hidden="false" max="1024" min="' . ($i + 1) . '" style="0" customWidth="false" width="11.5"/>');
238
        $sheet->file_writer->write('</cols>');
239
        $sheet->file_writer->write('<sheetData>');
240
    }
241
242
    private function addCellStyle($number_format, $cell_style_string)
243
    {
244
        $number_format_idx = self::add_to_list_get_index($this->number_formats, $number_format);
245
        $lookup_string = $number_format_idx . ";" . $cell_style_string;
246
        $cell_style_idx = self::add_to_list_get_index($this->cell_styles, $lookup_string);
247
        return $cell_style_idx;
248
    }
249
250
    private function initializeColumnTypes($header_types)
251
    {
252
        $column_types = array();
253
        foreach ($header_types as $v) {
254
            $number_format = self::numberFormatStandardized($v);
255
            $number_format_type = self::determineNumberFormatType($number_format);
256
            $cell_style_idx = $this->addCellStyle($number_format, $style_string = null);
257
            $column_types[] = array(
258
                'number_format' => $number_format,//contains excel format like 'YYYY-MM-DD HH:MM:SS'
259
                'number_format_type' => $number_format_type, //contains friendly format like 'datetime'
260
                'default_cell_style' => $cell_style_idx,
261
            );
262
        }
263
        return $column_types;
264
    }
265
266
    public function writeSheetHeader($sheet_name, array $header_types, $col_options = null)
267
    {
268
        if (empty($sheet_name) || empty($header_types) || !empty($this->sheets[$sheet_name])) {
269
            return;
270
        }
271
272
        $suppress_row = isset($col_options['suppress_row']) ? intval($col_options['suppress_row']) : false;
273
        if (is_bool($col_options)) {
274
            self::log("Warning! passing $suppress_row=false|true to writeSheetHeader() is deprecated, this will be removed in a future version.");
275
            $suppress_row = intval($col_options);
276
        }
277
        $style = &$col_options;
278
279
        $col_widths = isset($col_options['widths']) ? (array)$col_options['widths'] : array();
280
        $auto_filter = isset($col_options['auto_filter']) ? intval($col_options['auto_filter']) : false;
281
        $freeze_rows = isset($col_options['freeze_rows']) ? intval($col_options['freeze_rows']) : false;
282
        $freeze_columns = isset($col_options['freeze_columns']) ? intval($col_options['freeze_columns']) : false;
283
        self::initializeSheet($sheet_name, $col_widths, $auto_filter, $freeze_rows, $freeze_columns);
0 ignored issues
show
Bug introduced by
It seems like $auto_filter defined by isset($col_options['auto...'auto_filter']) : false on line 280 can also be of type integer; however, luya\helpers\XLSXWriter::initializeSheet() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $freeze_rows defined by isset($col_options['free...'freeze_rows']) : false on line 281 can also be of type integer; however, luya\helpers\XLSXWriter::initializeSheet() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $freeze_columns defined by isset($col_options['free...eeze_columns']) : false on line 282 can also be of type integer; however, luya\helpers\XLSXWriter::initializeSheet() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
284
        $sheet = &$this->sheets[$sheet_name];
285
        $sheet->columns = $this->initializeColumnTypes($header_types);
286
        if (!$suppress_row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $suppress_row of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
287
            $header_row = array_keys($header_types);
288
289
            $sheet->file_writer->write('<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="' . (1) . '">');
290
            foreach ($header_row as $c => $v) {
291
                $cell_style_idx = empty($style) ? $sheet->columns[$c]['default_cell_style'] : $this->addCellStyle('GENERAL',
292
                    json_encode(isset($style[0]) ? $style[$c] : $style));
293
                $this->writeCell($sheet->file_writer, 0, $c, $v, $number_format_type = 'n_string', $cell_style_idx);
294
            }
295
            $sheet->file_writer->write('</row>');
296
            $sheet->row_count++;
297
        }
298
        $this->current_sheet = $sheet_name;
299
    }
300
301
    public function writeSheetRow($sheet_name, array $row, $row_options = null)
302
    {
303
        if (empty($sheet_name)) {
304
            return;
305
        }
306
307
        self::initializeSheet($sheet_name);
308
        $sheet = &$this->sheets[$sheet_name];
309
        if (count($sheet->columns) < count($row)) {
310
            $default_column_types = $this->initializeColumnTypes(array_fill($from = 0, $until = count($row),
311
                'GENERAL'));//will map to n_auto
312
            $sheet->columns = array_merge((array)$sheet->columns, $default_column_types);
313
        }
314
315
        if (!empty($row_options)) {
316
            $ht = isset($row_options['height']) ? floatval($row_options['height']) : 12.1;
317
            $customHt = isset($row_options['height']) ? true : false;
318
            $hidden = isset($row_options['hidden']) ? (bool)($row_options['hidden']) : false;
319
            $collapsed = isset($row_options['collapsed']) ? (bool)($row_options['collapsed']) : false;
320
            $sheet->file_writer->write('<row collapsed="' . ($collapsed) . '" customFormat="false" customHeight="' . ($customHt) . '" hidden="' . ($hidden) . '" ht="' . ($ht) . '" outlineLevel="0" r="' . ($sheet->row_count + 1) . '">');
321
        } else {
322
            $sheet->file_writer->write('<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="' . ($sheet->row_count + 1) . '">');
323
        }
324
325
        $style = &$row_options;
326
        $c = 0;
327
        foreach ($row as $v) {
328
            $number_format = $sheet->columns[$c]['number_format'];
329
            $number_format_type = $sheet->columns[$c]['number_format_type'];
330
            $cell_style_idx = empty($style) ? $sheet->columns[$c]['default_cell_style'] : $this->addCellStyle($number_format,
331
                json_encode(isset($style[0]) ? $style[$c] : $style));
332
            $this->writeCell($sheet->file_writer, $sheet->row_count, $c, $v, $number_format_type, $cell_style_idx);
333
            $c++;
334
        }
335
        $sheet->file_writer->write('</row>');
336
        $sheet->row_count++;
337
        $this->current_sheet = $sheet_name;
338
    }
339
340
    public function countSheetRows($sheet_name = '')
341
    {
342
        $sheet_name = $sheet_name ?: $this->current_sheet;
343
        return array_key_exists($sheet_name, $this->sheets) ? $this->sheets[$sheet_name]->row_count : 0;
344
    }
345
346
    protected function finalizeSheet($sheet_name)
347
    {
348
        if (empty($sheet_name) || $this->sheets[$sheet_name]->finalized) {
349
            return;
350
        }
351
352
        $sheet = &$this->sheets[$sheet_name];
353
354
        $sheet->file_writer->write('</sheetData>');
355
356
        if (!empty($sheet->merge_cells)) {
357
            $sheet->file_writer->write('<mergeCells>');
358
            foreach ($sheet->merge_cells as $range) {
359
                $sheet->file_writer->write('<mergeCell ref="' . $range . '"/>');
360
            }
361
            $sheet->file_writer->write('</mergeCells>');
362
        }
363
364
        $max_cell = self::xlsCell($sheet->row_count - 1, count($sheet->columns) - 1);
365
366
        if ($sheet->auto_filter) {
367
            $sheet->file_writer->write('<autoFilter ref="A1:' . $max_cell . '"/>');
368
        }
369
370
        $sheet->file_writer->write('<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"/>');
371
        $sheet->file_writer->write('<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>');
372
        $sheet->file_writer->write('<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>');
373
        $sheet->file_writer->write('<headerFooter differentFirst="false" differentOddEven="false">');
374
        $sheet->file_writer->write('<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>');
375
        $sheet->file_writer->write('<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>');
376
        $sheet->file_writer->write('</headerFooter>');
377
        $sheet->file_writer->write('</worksheet>');
378
379
        $max_cell_tag = '<dimension ref="A1:' . $max_cell . '"/>';
380
        $padding_length = $sheet->max_cell_tag_end - $sheet->max_cell_tag_start - strlen($max_cell_tag);
381
        $sheet->file_writer->fseek($sheet->max_cell_tag_start);
382
        $sheet->file_writer->write($max_cell_tag . str_repeat(" ", $padding_length));
383
        $sheet->file_writer->close();
384
        $sheet->finalized = true;
385
    }
386
387
    public function markMergedCell($sheet_name, $start_cell_row, $start_cell_column, $end_cell_row, $end_cell_column)
388
    {
389
        if (empty($sheet_name) || $this->sheets[$sheet_name]->finalized) {
390
            return;
391
        }
392
393
        self::initializeSheet($sheet_name);
394
        $sheet = &$this->sheets[$sheet_name];
395
396
        $startCell = self::xlsCell($start_cell_row, $start_cell_column);
397
        $endCell = self::xlsCell($end_cell_row, $end_cell_column);
398
        $sheet->merge_cells[] = $startCell . ":" . $endCell;
399
    }
400
401
    public function writeSheet(array $data, $sheet_name = '', array $header_types = array())
402
    {
403
        $sheet_name = empty($sheet_name) ? 'Sheet1' : $sheet_name;
404
        $data = empty($data) ? array(array('')) : $data;
405
        if (!empty($header_types)) {
406
            $this->writeSheetHeader($sheet_name, $header_types);
407
        }
408
        foreach ($data as $i => $row) {
409
            $this->writeSheetRow($sheet_name, $row);
410
        }
411
        $this->finalizeSheet($sheet_name);
412
    }
413
414
    protected function writeCell(
415
        XLSXWriter_BuffererWriter &$file,
416
        $row_number,
417
        $column_number,
418
        $value,
419
        $num_format_type,
420
        $cell_style_idx
421
    ) {
422
        $cell_name = self::xlsCell($row_number, $column_number);
423
424
        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...
425
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '"/>');
426
        } elseif (is_string($value) && $value{0} == '=') {
427
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="s"><f>' . self::xmlspecialchars($value) . '</f></c>');
428
        } elseif ($num_format_type == 'n_date') {
429
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . intval(self::convert_date_time($value)) . '</v></c>');
430
        } elseif ($num_format_type == 'n_datetime') {
431
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . self::convert_date_time($value) . '</v></c>');
432 View Code Duplication
        } elseif ($num_format_type == 'n_numeric') {
433
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . self::xmlspecialchars($value) . '</v></c>');//int,float,currency
434
        } elseif ($num_format_type == 'n_string') {
435
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="inlineStr"><is><t>' . self::xmlspecialchars($value) . '</t></is></c>');
436
        } elseif ($num_format_type == 'n_auto' || 1) { //auto-detect unknown column types
437
            if (!is_string($value) || $value == '0' || ($value[0] != '0' && ctype_digit($value)) || preg_match("/^\-?(0|[1-9][0-9]*)(\.[0-9]+)?$/",
438
                    $value)
439
            ) {
440
                $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . self::xmlspecialchars($value) . '</v></c>');//int,float,currency
441 View Code Duplication
            } 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...
442
                $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="inlineStr"><is><t>' . self::xmlspecialchars($value) . '</t></is></c>');
443
            }
444
        }
445
    }
446
447
    protected function styleFontIndexes()
448
    {
449
        static $border_allowed = array('left', 'right', 'top', 'bottom');
450
        static $border_style_allowed = array(
451
            'thin',
452
            'medium',
453
            'thick',
454
            'dashDot',
455
            'dashDotDot',
456
            'dashed',
457
            'dotted',
458
            'double',
459
            'hair',
460
            'mediumDashDot',
461
            'mediumDashDotDot',
462
            'mediumDashed',
463
            'slantDashDot'
464
        );
465
        static $horizontal_allowed = array('general', 'left', 'right', 'justify', 'center');
466
        static $vertical_allowed = array('bottom', 'center', 'distributed', 'top');
467
        $default_font = array('size' => '10', 'name' => 'Arial', 'family' => '2');
468
        $fills = array('', '');//2 placeholders for static xml later
469
        $fonts = array('', '', '', '');//4 placeholders for static xml later
470
        $borders = array('');//1 placeholder for static xml later
471
        $style_indexes = array();
472
        foreach ($this->cell_styles as $i => $cell_style_string) {
473
            $semi_colon_pos = strpos($cell_style_string, ";");
474
            $number_format_idx = substr($cell_style_string, 0, $semi_colon_pos);
475
            $style_json_string = substr($cell_style_string, $semi_colon_pos + 1);
476
            $style = @json_decode($style_json_string, $as_assoc = true);
477
478
            $style_indexes[$i] = array('num_fmt_idx' => $number_format_idx);//initialize entry
479
            if (isset($style['border']) && is_string($style['border']))//border is a comma delimited str
480
            {
481
                $border_value['side'] = array_intersect(explode(",", $style['border']), $border_allowed);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$border_value was never initialized. Although not strictly required by PHP, it is generally a good practice to add $border_value = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
482
                if (isset($style['border-style']) && in_array($style['border-style'], $border_style_allowed)) {
483
                    $border_value['style'] = $style['border-style'];
0 ignored issues
show
Bug introduced by
The variable $border_value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
484
                }
485 View Code Duplication
                if (isset($style['border-color']) && is_string($style['border-color']) && $style['border-color'][0] == '#') {
486
                    $v = substr($style['border-color'], 1, 6);
487
                    $v = strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v;// expand cf0 => ccff00
488
                    $border_value['color'] = "FF" . strtoupper($v);
489
                }
490
                $style_indexes[$i]['border_idx'] = self::add_to_list_get_index($borders, json_encode($border_value));
491
            }
492 View Code Duplication
            if (isset($style['fill']) && is_string($style['fill']) && $style['fill'][0] == '#') {
493
                $v = substr($style['fill'], 1, 6);
494
                $v = strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v;// expand cf0 => ccff00
495
                $style_indexes[$i]['fill_idx'] = self::add_to_list_get_index($fills, "FF" . strtoupper($v));
496
            }
497 View Code Duplication
            if (isset($style['halign']) && in_array($style['halign'], $horizontal_allowed)) {
498
                $style_indexes[$i]['alignment'] = true;
499
                $style_indexes[$i]['halign'] = $style['halign'];
500
            }
501 View Code Duplication
            if (isset($style['valign']) && in_array($style['valign'], $vertical_allowed)) {
502
                $style_indexes[$i]['alignment'] = true;
503
                $style_indexes[$i]['valign'] = $style['valign'];
504
            }
505
            if (isset($style['wrap_text'])) {
506
                $style_indexes[$i]['alignment'] = true;
507
                $style_indexes[$i]['wrap_text'] = (bool)$style['wrap_text'];
508
            }
509
510
            $font = $default_font;
511
            if (isset($style['font-size'])) {
512
                $font['size'] = floatval($style['font-size']);//floatval to allow "10.5" etc
513
            }
514
            if (isset($style['font']) && is_string($style['font'])) {
515
                if ($style['font'] == 'Comic Sans MS') {
516
                    $font['family'] = 4;
517
                }
518
                if ($style['font'] == 'Times New Roman') {
519
                    $font['family'] = 1;
520
                }
521
                if ($style['font'] == 'Courier New') {
522
                    $font['family'] = 3;
523
                }
524
                $font['name'] = strval($style['font']);
525
            }
526
            if (isset($style['font-style']) && is_string($style['font-style'])) {
527 View Code Duplication
                if (strpos($style['font-style'], 'bold') !== false) {
528
                    $font['bold'] = true;
529
                }
530 View Code Duplication
                if (strpos($style['font-style'], 'italic') !== false) {
531
                    $font['italic'] = true;
532
                }
533 View Code Duplication
                if (strpos($style['font-style'], 'strike') !== false) {
534
                    $font['strike'] = true;
535
                }
536 View Code Duplication
                if (strpos($style['font-style'], 'underline') !== false) {
537
                    $font['underline'] = true;
538
                }
539
            }
540 View Code Duplication
            if (isset($style['color']) && is_string($style['color']) && $style['color'][0] == '#') {
541
                $v = substr($style['color'], 1, 6);
542
                $v = strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v;// expand cf0 => ccff00
543
                $font['color'] = "FF" . strtoupper($v);
544
            }
545
            if ($font != $default_font) {
546
                $style_indexes[$i]['font_idx'] = self::add_to_list_get_index($fonts, json_encode($font));
547
            }
548
        }
549
        return array('fills' => $fills, 'fonts' => $fonts, 'borders' => $borders, 'styles' => $style_indexes);
550
    }
551
552
    protected function writeStylesXML()
553
    {
554
        $r = self::styleFontIndexes();
555
        $fills = $r['fills'];
556
        $fonts = $r['fonts'];
557
        $borders = $r['borders'];
558
        $style_indexes = $r['styles'];
559
560
        $temporary_filename = $this->tempFilename();
561
        $file = new XLSXWriter_BuffererWriter($temporary_filename);
562
        $file->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n");
563
        $file->write('<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
564
        $file->write('<numFmts count="' . count($this->number_formats) . '">');
565
        foreach ($this->number_formats as $i => $v) {
566
            $file->write('<numFmt numFmtId="' . (164 + $i) . '" formatCode="' . self::xmlspecialchars($v) . '" />');
567
        }
568
        //$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...
569
        //$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...
570
        //$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...
571
        //$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...
572
        $file->write('</numFmts>');
573
574
        $file->write('<fonts count="' . (count($fonts)) . '">');
575
        $file->write('<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
576
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
577
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
578
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
579
580
        foreach ($fonts as $font) {
581
            if (!empty($font)) { //fonts have 4 empty placeholders in array to offset the 4 static xml entries above
582
                $f = json_decode($font, true);
583
                $file->write('<font>');
584
                $file->write('<name val="' . htmlspecialchars($f['name']) . '"/><charset val="1"/><family val="' . intval($f['family']) . '"/>');
585
                $file->write('<sz val="' . intval($f['size']) . '"/>');
586
                if (!empty($f['color'])) {
587
                    $file->write('<color rgb="' . strval($f['color']) . '"/>');
588
                }
589
                if (!empty($f['bold'])) {
590
                    $file->write('<b val="true"/>');
591
                }
592
                if (!empty($f['italic'])) {
593
                    $file->write('<i val="true"/>');
594
                }
595
                if (!empty($f['underline'])) {
596
                    $file->write('<u val="single"/>');
597
                }
598
                if (!empty($f['strike'])) {
599
                    $file->write('<strike val="true"/>');
600
                }
601
                $file->write('</font>');
602
            }
603
        }
604
        $file->write('</fonts>');
605
606
        $file->write('<fills count="' . (count($fills)) . '">');
607
        $file->write('<fill><patternFill patternType="none"/></fill>');
608
        $file->write('<fill><patternFill patternType="gray125"/></fill>');
609
        foreach ($fills as $fill) {
610
            if (!empty($fill)) { //fills have 2 empty placeholders in array to offset the 2 static xml entries above
611
                $file->write('<fill><patternFill patternType="solid"><fgColor rgb="' . strval($fill) . '"/><bgColor indexed="64"/></patternFill></fill>');
612
            }
613
        }
614
        $file->write('</fills>');
615
616
        $file->write('<borders count="' . (count($borders)) . '">');
617
        $file->write('<border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border>');
618
        foreach ($borders as $border) {
619
            if (!empty($border)) { //fonts have an empty placeholder in the array to offset the static xml entry above
620
                $pieces = json_decode($border, true);
621
                $border_style = !empty($pieces['style']) ? $pieces['style'] : 'hair';
622
                $border_color = !empty($pieces['color']) ? '<color rgb="' . strval($pieces['color']) . '"/>' : '';
623
                $file->write('<border diagonalDown="false" diagonalUp="false">');
624
                foreach (array('left', 'right', 'top', 'bottom') as $side) {
625
                    $show_side = in_array($side, $pieces['side']) ? true : false;
626
                    $file->write($show_side ? "<$side style=\"$border_style\">$border_color</$side>" : "<$side/>");
627
                }
628
                $file->write('<diagonal/>');
629
                $file->write('</border>');
630
            }
631
        }
632
        $file->write('</borders>');
633
634
        $file->write('<cellStyleXfs count="20">');
635
        $file->write('<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
636
        $file->write('<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
637
        $file->write('<protection hidden="false" locked="true"/>');
638
        $file->write('</xf>');
639
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
640
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
641
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
642
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
643
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
644
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
645
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
646
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
647
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
648
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
649
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
650
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
651
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
652
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
653
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
654
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
655
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
656
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
657
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
658
        $file->write('</cellStyleXfs>');
659
660
        $file->write('<cellXfs count="' . (count($style_indexes)) . '">');
661
        //$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
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...
662
        //$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
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...
663
        //$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
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...
664
        //$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
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...
665
        foreach ($style_indexes as $v) {
666
            $applyAlignment = isset($v['alignment']) ? 'true' : 'false';
667
            $wrapText = !empty($v['wrap_text']) ? 'true' : 'false';
668
            $horizAlignment = isset($v['halign']) ? $v['halign'] : 'general';
669
            $vertAlignment = isset($v['valign']) ? $v['valign'] : 'bottom';
670
            $applyBorder = isset($v['border_idx']) ? 'true' : 'false';
671
            $applyFont = 'true';
672
            $borderIdx = isset($v['border_idx']) ? intval($v['border_idx']) : 0;
673
            $fillIdx = isset($v['fill_idx']) ? intval($v['fill_idx']) : 0;
674
            $fontIdx = isset($v['font_idx']) ? intval($v['font_idx']) : 0;
675
            //$file->write('<xf applyAlignment="'.$applyAlignment.'" applyBorder="'.$applyBorder.'" applyFont="'.$applyFont.'" applyProtection="false" borderId="'.($borderIdx).'" fillId="'.($fillIdx).'" fontId="'.($fontIdx).'" numFmtId="'.(164+$v['num_fmt_idx']).'" xfId="0"/>');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
676
            $file->write('<xf applyAlignment="' . $applyAlignment . '" applyBorder="' . $applyBorder . '" applyFont="' . $applyFont . '" applyProtection="false" borderId="' . ($borderIdx) . '" fillId="' . ($fillIdx) . '" fontId="' . ($fontIdx) . '" numFmtId="' . (164 + $v['num_fmt_idx']) . '" xfId="0">');
677
            $file->write('	<alignment horizontal="' . $horizAlignment . '" vertical="' . $vertAlignment . '" textRotation="0" wrapText="' . $wrapText . '" indent="0" shrinkToFit="false"/>');
678
            $file->write('	<protection locked="true" hidden="false"/>');
679
            $file->write('</xf>');
680
        }
681
        $file->write('</cellXfs>');
682
        $file->write('<cellStyles count="6">');
683
        $file->write('<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
684
        $file->write('<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
685
        $file->write('<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
686
        $file->write('<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
687
        $file->write('<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
688
        $file->write('<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
689
        $file->write('</cellStyles>');
690
        $file->write('</styleSheet>');
691
        $file->close();
692
        return $temporary_filename;
693
    }
694
695
    protected function buildAppXML()
696
    {
697
        $app_xml = "";
698
        $app_xml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
699
        $app_xml .= '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">';
700
        $app_xml .= '<TotalTime>0</TotalTime>';
701
        $app_xml .= '<Company>' . self::xmlspecialchars($this->company) . '</Company>';
702
        $app_xml .= '</Properties>';
703
        return $app_xml;
704
    }
705
706
    protected function buildCoreXML()
707
    {
708
        $core_xml = "";
709
        $core_xml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
710
        $core_xml .= '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
711
        $core_xml .= '<dcterms:created xsi:type="dcterms:W3CDTF">' . date("Y-m-d\TH:i:s.00\Z") . '</dcterms:created>';//$date_time = '2014-10-25T15:54:37.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...
712
        $core_xml .= '<dc:title>' . self::xmlspecialchars($this->title) . '</dc:title>';
713
        $core_xml .= '<dc:subject>' . self::xmlspecialchars($this->subject) . '</dc:subject>';
714
        $core_xml .= '<dc:creator>' . self::xmlspecialchars($this->author) . '</dc:creator>';
715
        if (!empty($this->keywords)) {
716
            $core_xml .= '<cp:keywords>' . self::xmlspecialchars(implode(", ",
717
                    (array)$this->keywords)) . '</cp:keywords>';
718
        }
719
        $core_xml .= '<dc:description>' . self::xmlspecialchars($this->description) . '</dc:description>';
720
        $core_xml .= '<cp:revision>0</cp:revision>';
721
        $core_xml .= '</cp:coreProperties>';
722
        return $core_xml;
723
    }
724
725
    protected function buildRelationshipsXML()
726
    {
727
        $rels_xml = "";
728
        $rels_xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
729
        $rels_xml .= '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
730
        $rels_xml .= '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>';
731
        $rels_xml .= '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>';
732
        $rels_xml .= '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>';
733
        $rels_xml .= "\n";
734
        $rels_xml .= '</Relationships>';
735
        return $rels_xml;
736
    }
737
738
    protected function buildWorkbookXML()
739
    {
740
        $i = 0;
741
        $workbook_xml = "";
742
        $workbook_xml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
743
        $workbook_xml .= '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
744
        $workbook_xml .= '<fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/>';
745
        $workbook_xml .= '<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true" showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>';
746
        $workbook_xml .= '<sheets>';
747
        foreach ($this->sheets as $sheet_name => $sheet) {
748
            $sheetname = self::sanitize_sheetname($sheet->sheetname);
749
            $workbook_xml .= '<sheet name="' . self::xmlspecialchars($sheetname) . '" sheetId="' . ($i + 1) . '" state="visible" r:id="rId' . ($i + 2) . '"/>';
750
            $i++;
751
        }
752
        $workbook_xml .= '</sheets>';
753
        $workbook_xml .= '<definedNames>';
754
        foreach ($this->sheets as $sheet_name => $sheet) {
755
            if ($sheet->auto_filter) {
756
                $sheetname = self::sanitize_sheetname($sheet->sheetname);
757
                $workbook_xml .= '<definedName name="_xlnm._FilterDatabase" localSheetId="0" hidden="1">\'' . self::xmlspecialchars($sheetname) . '\'!$A$1:' . self::xlsCell($sheet->row_count - 1,
758
                        count($sheet->columns) - 1, true) . '</definedName>';
759
                $i++;
760
            }
761
        }
762
        $workbook_xml .= '</definedNames>';
763
        $workbook_xml .= '<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
764
        return $workbook_xml;
765
    }
766
767
    protected function buildWorkbookRelsXML()
768
    {
769
        $i = 0;
770
        $wkbkrels_xml = "";
771
        $wkbkrels_xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
772
        $wkbkrels_xml .= '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
773
        $wkbkrels_xml .= '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>';
774
        foreach ($this->sheets as $sheet_name => $sheet) {
775
            $wkbkrels_xml .= '<Relationship Id="rId' . ($i + 2) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/' . ($sheet->xmlname) . '"/>';
776
            $i++;
777
        }
778
        $wkbkrels_xml .= "\n";
779
        $wkbkrels_xml .= '</Relationships>';
780
        return $wkbkrels_xml;
781
    }
782
783
    protected function buildContentTypesXML()
784
    {
785
        $content_types_xml = "";
786
        $content_types_xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
787
        $content_types_xml .= '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
788
        $content_types_xml .= '<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
789
        $content_types_xml .= '<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
790
        foreach ($this->sheets as $sheet_name => $sheet) {
791
            $content_types_xml .= '<Override PartName="/xl/worksheets/' . ($sheet->xmlname) . '" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
792
        }
793
        $content_types_xml .= '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
794
        $content_types_xml .= '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
795
        $content_types_xml .= '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>';
796
        $content_types_xml .= '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>';
797
        $content_types_xml .= "\n";
798
        $content_types_xml .= '</Types>';
799
        return $content_types_xml;
800
    }
801
802
    //------------------------------------------------------------------
803
    /*
804
     * @param $row_number int, zero based
805
     * @param $column_number int, zero based
806
     * @param $absolute bool
807
     * @return Cell label/coordinates, ex: A1, C3, AA42 (or if $absolute==true: $A$1, $C$3, $AA$42)
808
     * */
809
    public static function xlsCell($row_number, $column_number, $absolute = false)
810
    {
811
        $n = $column_number;
812
        for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
813
            $r = chr($n % 26 + 0x41) . $r;
814
        }
815
        if ($absolute) {
816
            return '$' . $r . '$' . ($row_number + 1);
817
        }
818
        return $r . ($row_number + 1);
819
    }
820
821
    //------------------------------------------------------------------
822
    public static function log($string)
823
    {
824
        file_put_contents("php://stderr",
825
            date("Y-m-d H:i:s:") . rtrim(is_array($string) ? json_encode($string) : $string) . "\n");
826
    }
827
828
    //------------------------------------------------------------------
829
    public static function sanitize_filename($filename
830
    ) //http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
831
    {
832
        $nonprinting = array_map('chr', range(0, 31));
833
        $invalid_chars = array('<', '>', '?', '"', ':', '|', '\\', '/', '*', '&');
834
        $all_invalids = array_merge($nonprinting, $invalid_chars);
835
        return str_replace($all_invalids, "", $filename);
836
    }
837
838
    //------------------------------------------------------------------
839
    public static function sanitize_sheetname($sheetname)
840
    {
841
        static $badchars = '\\/?*:[]';
842
        static $goodchars = '        ';
843
        $sheetname = strtr($sheetname, $badchars, $goodchars);
844
        $sheetname = substr($sheetname, 0, 31);
845
        $sheetname = trim(trim(trim($sheetname), "'"));//trim before and after trimming single quotes
846
        return !empty($sheetname) ? $sheetname : 'Sheet' . ((rand() % 900) + 100);
847
    }
848
849
    //------------------------------------------------------------------
850
    public static function xmlspecialchars($val)
851
    {
852
        //note, badchars does not include \t\n\r (\x09\x0a\x0d)
853
        static $badchars = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
854
        static $goodchars = "                              ";
855
        return strtr(htmlspecialchars($val, ENT_QUOTES | ENT_XML1), $badchars,
856
            $goodchars);//strtr appears to be faster than str_replace
857
    }
858
859
    //------------------------------------------------------------------
860
    public static function array_first_key(array $arr)
861
    {
862
        reset($arr);
863
        $first_key = key($arr);
864
        return $first_key;
865
    }
866
867
    //------------------------------------------------------------------
868
    private static function determineNumberFormatType($num_format)
869
    {
870
        $num_format = preg_replace("/\[(Black|Blue|Cyan|Green|Magenta|Red|White|Yellow)\]/i", "", $num_format);
871
        if ($num_format == 'GENERAL') {
872
            return 'n_auto';
873
        }
874
        if ($num_format == '@') {
875
            return 'n_string';
876
        }
877
        if ($num_format == '0') {
878
            return 'n_numeric';
879
        }
880
        if (preg_match("/[H]{1,2}:[M]{1,2}/i", $num_format)) {
881
            return 'n_datetime';
882
        }
883
        if (preg_match("/[M]{1,2}:[S]{1,2}/i", $num_format)) {
884
            return 'n_datetime';
885
        }
886
        if (preg_match("/[Y]{2,4}/i", $num_format)) {
887
            return 'n_date';
888
        }
889
        if (preg_match("/[D]{1,2}/i", $num_format)) {
890
            return 'n_date';
891
        }
892
        if (preg_match("/[M]{1,2}/i", $num_format)) {
893
            return 'n_date';
894
        }
895
        if (preg_match("/$/", $num_format)) {
896
            return 'n_numeric';
897
        }
898
        if (preg_match("/%/", $num_format)) {
899
            return 'n_numeric';
900
        }
901
        if (preg_match("/0/", $num_format)) {
902
            return 'n_numeric';
903
        }
904
        return 'n_auto';
905
    }
906
907
    //------------------------------------------------------------------
908
    private static function numberFormatStandardized($num_format)
909
    {
910
        if ($num_format == 'money') {
911
            $num_format = 'dollar';
912
        }
913
        if ($num_format == 'number') {
914
            $num_format = 'integer';
915
        }
916
917
        if ($num_format == 'string') {
918
            $num_format = '@';
919
        } else {
920
            if ($num_format == 'integer') {
921
                $num_format = '0';
922
            } else {
923
                if ($num_format == 'date') {
924
                    $num_format = 'YYYY-MM-DD';
925
                } else {
926
                    if ($num_format == 'datetime') {
927
                        $num_format = 'YYYY-MM-DD HH:MM:SS';
928
                    } else {
929
                        if ($num_format == 'price') {
930
                            $num_format = '#,##0.00';
931
                        } else {
932
                            if ($num_format == 'dollar') {
933
                                $num_format = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
934
                            } else {
935
                                if ($num_format == 'euro') {
936
                                    $num_format = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]';
937
                                }
938
                            }
939
                        }
940
                    }
941
                }
942
            }
943
        }
944
        $ignore_until = '';
945
        $escaped = '';
946
        for ($i = 0, $ix = strlen($num_format); $i < $ix; $i++) {
947
            $c = $num_format[$i];
948
            if ($ignore_until == '' && $c == '[') {
949
                $ignore_until = ']';
950
            } else {
951
                if ($ignore_until == '' && $c == '"') {
952
                    $ignore_until = '"';
953
                } else {
954
                    if ($ignore_until == $c) {
955
                        $ignore_until = '';
956
                    }
957
                }
958
            }
959
            if ($ignore_until == '' && ($c == ' ' || $c == '-' || $c == '(' || $c == ')') && ($i == 0 || $num_format[$i - 1] != '_')) {
960
                $escaped .= "\\" . $c;
961
            } else {
962
                $escaped .= $c;
963
            }
964
        }
965
        return $escaped;
966
    }
967
968
    //------------------------------------------------------------------
969
    public static function add_to_list_get_index(&$haystack, $needle)
970
    {
971
        $existing_idx = array_search($needle, $haystack, $strict = true);
972
        if ($existing_idx === false) {
973
            $existing_idx = count($haystack);
974
            $haystack[] = $needle;
975
        }
976
        return $existing_idx;
977
    }
978
979
    //------------------------------------------------------------------
980
    public static function convert_date_time($date_input) //thanks to Excel::Writer::XLSX::Worksheet.pm (perl)
981
    {
982
        $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...
983
        $seconds = 0;    # Time expressed as fraction of 24h hours in seconds
984
        $year = $month = $day = 0;
985
        $hour = $min = $sec = 0;
986
987
        $date_time = $date_input;
988
        if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $date_time, $matches)) {
989
            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...
990
        }
991
        if (preg_match("/(\d+):(\d{2}):(\d{2})/", $date_time, $matches)) {
992
            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...
993
            $seconds = ($hour * 60 * 60 + $min * 60 + $sec) / (24 * 60 * 60);
994
        }
995
996
        //using 1900 as epoch, not 1904, ignoring 1904 special case
997
998
        # Special cases for Excel.
999
        if ("$year-$month-$day" == '1899-12-31') {
1000
            return $seconds;
1001
        }    # Excel 1900 epoch
1002
        if ("$year-$month-$day" == '1900-01-00') {
1003
            return $seconds;
1004
        }    # Excel 1900 epoch
1005
        if ("$year-$month-$day" == '1900-02-29') {
1006
            return 60 + $seconds;
1007
        }    # Excel false leapday
1008
1009
        # We calculate the date by calculating the number of days since the epoch
1010
        # and adjust for the number of leap days. We calculate the number of leap
1011
        # days by normalising the year in relation to the epoch. Thus the year 2000
1012
        # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
1013
        $epoch = 1900;
1014
        $offset = 0;
1015
        $norm = 300;
1016
        $range = $year - $epoch;
1017
1018
        # Set month days and check for leap year.
1019
        $leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100))) ? 1 : 0;
1020
        $mdays = array(31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
1021
1022
        # Some boundary checks
1023
        if ($year < $epoch || $year > 9999) {
1024
            return 0;
1025
        }
1026
        if ($month < 1 || $month > 12) {
1027
            return 0;
1028
        }
1029
        if ($day < 1 || $day > $mdays[$month - 1]) {
1030
            return 0;
1031
        }
1032
1033
        # Accumulate the number of days since the epoch.
1034
        $days = $day;    # Add days for current month
1035
        $days += array_sum(array_slice($mdays, 0, $month - 1));    # Add days for past months
1036
        $days += $range * 365;                      # Add days for past years
1037
        $days += intval(($range) / 4);             # Add leapdays
1038
        $days -= intval(($range + $offset) / 100); # Subtract 100 year leapdays
1039
        $days += intval(($range + $offset + $norm) / 400);  # Add 400 year leapdays
1040
        $days -= $leap;                                      # Already counted above
1041
1042
        # Adjust for Excel erroneously treating 1900 as a leap year.
1043
        if ($days > 59) {
1044
            $days++;
1045
        }
1046
1047
        return $days + $seconds;
1048
    }
1049
    //------------------------------------------------------------------
1050
}
1051
1052
class XLSXWriter_BuffererWriter
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1053
{
1054
    protected $fd = null;
1055
    protected $buffer = '';
1056
    protected $check_utf8 = false;
1057
1058
    public function __construct($filename, $fd_fopen_flags = 'w', $check_utf8 = false)
1059
    {
1060
        $this->check_utf8 = $check_utf8;
1061
        $this->fd = fopen($filename, $fd_fopen_flags);
1062
        if ($this->fd === false) {
1063
            XLSXWriter::log("Unable to open $filename for writing.");
1064
        }
1065
    }
1066
1067
    public function write($string)
1068
    {
1069
        $this->buffer .= $string;
1070
        if (isset($this->buffer[8191])) {
1071
            $this->purge();
1072
        }
1073
    }
1074
1075
    protected function purge()
1076
    {
1077
        if ($this->fd) {
1078
            if ($this->check_utf8 && !self::isValidUTF8($this->buffer)) {
1079
                XLSXWriter::log("Error, invalid UTF8 encoding detected.");
1080
                $this->check_utf8 = false;
1081
            }
1082
            fwrite($this->fd, $this->buffer);
1083
            $this->buffer = '';
1084
        }
1085
    }
1086
1087
    public function close()
1088
    {
1089
        $this->purge();
1090
        if ($this->fd) {
1091
            fclose($this->fd);
1092
            $this->fd = null;
1093
        }
1094
    }
1095
1096
    public function __destruct()
1097
    {
1098
        $this->close();
1099
    }
1100
1101
    public function ftell()
1102
    {
1103
        if ($this->fd) {
1104
            $this->purge();
1105
            return ftell($this->fd);
1106
        }
1107
        return -1;
1108
    }
1109
1110
    public function fseek($pos)
1111
    {
1112
        if ($this->fd) {
1113
            $this->purge();
1114
            return fseek($this->fd, $pos);
1115
        }
1116
        return -1;
1117
    }
1118
1119
    protected static function isValidUTF8($string)
1120
    {
1121
        if (function_exists('mb_check_encoding')) {
1122
            return mb_check_encoding($string, 'UTF-8') ? true : false;
1123
        }
1124
        return preg_match("//u", $string) ? true : false;
1125
    }
1126
}
1127