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

XLSXWriter::writeStylesXML()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 142
Code Lines 110

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 142
rs 2
c 0
b 0
f 0
cc 27
eloc 110
nc 733992
nop 0

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
/*
6
 * @license MIT License
7
 *
8
 * @see https://raw.githubusercontent.com/mk-j/PHP_XLSXWriter/master/xlsxwriter.class.php
9
 * */
10
11
class XLSXWriter
12
{
13
    //http://www.ecma-international.org/publications/standards/Ecma-376.htm
14
    //http://officeopenxml.com/SSstyles.php
15
    //------------------------------------------------------------------
16
    //http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP010073849.aspx
17
    const EXCEL_2007_MAX_ROW = 1048576;
18
    const EXCEL_2007_MAX_COL = 16384;
19
    //------------------------------------------------------------------
20
    protected $title;
21
    protected $subject;
22
    protected $author;
23
    protected $company;
24
    protected $description;
25
    protected $keywords = array();
26
27
    protected $current_sheet;
28
    protected $sheets = array();
29
    protected $temp_files = array();
30
    protected $cell_styles = array();
31
    protected $number_formats = array();
32
33
    public function __construct()
34
    {
35
        if (!ini_get('date.timezone')) {
36
            //using date functions can kick out warning if this isn't set
37
            date_default_timezone_set('UTC');
38
        }
39
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
40
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
41
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
42
        $this->addCellStyle($number_format = 'GENERAL', $style_string = null);
43
    }
44
45
    public function setTitle($title = '')
46
    {
47
        $this->title = $title;
48
    }
49
50
    public function setSubject($subject = '')
51
    {
52
        $this->subject = $subject;
53
    }
54
55
    public function setAuthor($author = '')
56
    {
57
        $this->author = $author;
58
    }
59
60
    public function setCompany($company = '')
61
    {
62
        $this->company = $company;
63
    }
64
65
    public function setKeywords($keywords = '')
66
    {
67
        $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...
68
    }
69
70
    public function setDescription($description = '')
71
    {
72
        $this->description = $description;
73
    }
74
75
    public function setTempDir($tempdir = '')
76
    {
77
        $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...
78
    }
79
80
    public function __destruct()
81
    {
82
        if (!empty($this->temp_files)) {
83
            foreach ($this->temp_files as $temp_file) {
84
                @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...
85
            }
86
        }
87
    }
88
89
    protected function tempFilename()
90
    {
91
        $tempdir = !empty($this->tempdir) ? $this->tempdir : sys_get_temp_dir();
92
        $filename = tempnam($tempdir, "xlsx_writer_");
93
        $this->temp_files[] = $filename;
94
        return $filename;
95
    }
96
97
    public function writeToStdOut()
98
    {
99
        $temp_file = $this->tempFilename();
100
        self::writeToFile($temp_file);
101
        readfile($temp_file);
102
    }
103
104
    public function writeToString()
105
    {
106
        $temp_file = $this->tempFilename();
107
        self::writeToFile($temp_file);
108
        $string = file_get_contents($temp_file);
109
        return $string;
110
    }
111
112
    public function writeToFile($filename)
113
    {
114
        foreach ($this->sheets as $sheet_name => $sheet) {
115
            self::finalizeSheet($sheet_name);//making sure all footers have been written
116
        }
117
118
        if (file_exists($filename)) {
119
            if (is_writable($filename)) {
120
                @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...
121
            } else {
122
                self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", file is not writeable.");
123
                return;
124
            }
125
        }
126
        $zip = new ZipArchive();
127
        if (empty($this->sheets)) {
128
            self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", no worksheets defined.");
129
            return;
130
        }
131
        if (!$zip->open($filename, ZipArchive::CREATE)) {
132
            self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", unable to create zip.");
133
            return;
134
        }
135
136
        $zip->addEmptyDir("docProps/");
137
        $zip->addFromString("docProps/app.xml", self::buildAppXML());
138
        $zip->addFromString("docProps/core.xml", self::buildCoreXML());
139
140
        $zip->addEmptyDir("_rels/");
141
        $zip->addFromString("_rels/.rels", self::buildRelationshipsXML());
142
143
        $zip->addEmptyDir("xl/worksheets/");
144
        foreach ($this->sheets as $sheet) {
145
            $zip->addFile($sheet->filename, "xl/worksheets/" . $sheet->xmlname);
146
        }
147
        $zip->addFromString("xl/workbook.xml", self::buildWorkbookXML());
148
        $zip->addFile($this->writeStylesXML(),
149
            "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...
150
        $zip->addFromString("[Content_Types].xml", self::buildContentTypesXML());
151
152
        $zip->addEmptyDir("xl/_rels/");
153
        $zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelsXML());
154
        $zip->close();
155
    }
156
157
    protected function initializeSheet(
158
        $sheet_name,
159
        $col_widths = array(),
160
        $auto_filter = false,
161
        $freeze_rows = false,
162
        $freeze_columns = false
163
    ) {
164
        //if already initialized
165
        if ($this->current_sheet == $sheet_name || isset($this->sheets[$sheet_name])) {
166
            return;
167
        }
168
169
        $sheet_filename = $this->tempFilename();
170
        $sheet_xmlname = 'sheet' . (count($this->sheets) + 1) . ".xml";
171
        $this->sheets[$sheet_name] = (object)array(
172
            'filename' => $sheet_filename,
173
            'sheetname' => $sheet_name,
174
            'xmlname' => $sheet_xmlname,
175
            'row_count' => 0,
176
            'file_writer' => new XLSXWriter_BuffererWriter($sheet_filename),
177
            'columns' => array(),
178
            'merge_cells' => array(),
179
            'max_cell_tag_start' => 0,
180
            'max_cell_tag_end' => 0,
181
            'auto_filter' => $auto_filter,
182
            'freeze_rows' => $freeze_rows,
183
            'freeze_columns' => $freeze_columns,
184
            'finalized' => false,
185
        );
186
        $sheet = &$this->sheets[$sheet_name];
187
        $tabselected = count($this->sheets) == 1 ? 'true' : 'false';//only first sheet is selected
188
        $max_cell = XLSXWriter::xlsCell(self::EXCEL_2007_MAX_ROW, self::EXCEL_2007_MAX_COL);//XFE1048577
189
        $sheet->file_writer->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n");
190
        $sheet->file_writer->write('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
191
        $sheet->file_writer->write('<sheetPr filterMode="false">');
192
        $sheet->file_writer->write('<pageSetUpPr fitToPage="false"/>');
193
        $sheet->file_writer->write('</sheetPr>');
194
        $sheet->max_cell_tag_start = $sheet->file_writer->ftell();
195
        $sheet->file_writer->write('<dimension ref="A1:' . $max_cell . '"/>');
196
        $sheet->max_cell_tag_end = $sheet->file_writer->ftell();
197
        $sheet->file_writer->write('<sheetViews>');
198
        $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">');
199
        if ($sheet->freeze_rows && $sheet->freeze_columns) {
200
            $sheet->file_writer->write('<pane ySplit="' . $sheet->freeze_rows . '" xSplit="' . $sheet->freeze_columns . '" topLeftCell="' . self::xlsCell($sheet->freeze_rows,
201
                    $sheet->freeze_columns) . '" activePane="bottomRight" state="frozen"/>');
202
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell($sheet->freeze_rows,
203
                    0) . '" activeCellId="0" pane="topRight" sqref="' . self::xlsCell($sheet->freeze_rows, 0) . '"/>');
204
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell(0,
205
                    $sheet->freeze_columns) . '" activeCellId="0" pane="bottomLeft" sqref="' . self::xlsCell(0,
206
                    $sheet->freeze_columns) . '"/>');
207
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell($sheet->freeze_rows,
208
                    $sheet->freeze_columns) . '" activeCellId="0" pane="bottomRight" sqref="' . self::xlsCell($sheet->freeze_rows,
209
                    $sheet->freeze_columns) . '"/>');
210
        } elseif ($sheet->freeze_rows) {
211
            $sheet->file_writer->write('<pane ySplit="' . $sheet->freeze_rows . '" topLeftCell="' . self::xlsCell($sheet->freeze_rows,
212
                    0) . '" activePane="bottomLeft" state="frozen"/>');
213
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell($sheet->freeze_rows,
214
                    0) . '" activeCellId="0" pane="bottomLeft" sqref="' . self::xlsCell($sheet->freeze_rows,
215
                    0) . '"/>');
216
        } elseif ($sheet->freeze_columns) {
217
            $sheet->file_writer->write('<pane xSplit="' . $sheet->freeze_columns . '" topLeftCell="' . self::xlsCell(0,
218
                    $sheet->freeze_columns) . '" activePane="topRight" state="frozen"/>');
219
            $sheet->file_writer->write('<selection activeCell="' . self::xlsCell(0,
220
                    $sheet->freeze_columns) . '" activeCellId="0" pane="topRight" sqref="' . self::xlsCell(0,
221
                    $sheet->freeze_columns) . '"/>');
222
        } else { // not frozen
223
            $sheet->file_writer->write('<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
224
        }
225
        $sheet->file_writer->write('</sheetView>');
226
        $sheet->file_writer->write('</sheetViews>');
227
        $sheet->file_writer->write('<cols>');
228
        $i = 0;
229
        if (!empty($col_widths)) {
230
            foreach ($col_widths as $column_width) {
231
                $sheet->file_writer->write('<col collapsed="false" hidden="false" max="' . ($i + 1) . '" min="' . ($i + 1) . '" style="0" customWidth="true" width="' . floatval($column_width) . '"/>');
232
                $i++;
233
            }
234
        }
235
        $sheet->file_writer->write('<col collapsed="false" hidden="false" max="1024" min="' . ($i + 1) . '" style="0" customWidth="false" width="11.5"/>');
236
        $sheet->file_writer->write('</cols>');
237
        $sheet->file_writer->write('<sheetData>');
238
    }
239
240
    private function addCellStyle($number_format, $cell_style_string)
241
    {
242
        $number_format_idx = self::add_to_list_get_index($this->number_formats, $number_format);
243
        $lookup_string = $number_format_idx . ";" . $cell_style_string;
244
        $cell_style_idx = self::add_to_list_get_index($this->cell_styles, $lookup_string);
245
        return $cell_style_idx;
246
    }
247
248
    private function initializeColumnTypes($header_types)
249
    {
250
        $column_types = array();
251
        foreach ($header_types as $v) {
252
            $number_format = self::numberFormatStandardized($v);
253
            $number_format_type = self::determineNumberFormatType($number_format);
254
            $cell_style_idx = $this->addCellStyle($number_format, $style_string = null);
255
            $column_types[] = array(
256
                'number_format' => $number_format,//contains excel format like 'YYYY-MM-DD HH:MM:SS'
257
                'number_format_type' => $number_format_type, //contains friendly format like 'datetime'
258
                'default_cell_style' => $cell_style_idx,
259
            );
260
        }
261
        return $column_types;
262
    }
263
264
    public function writeSheetHeader($sheet_name, array $header_types, $col_options = null)
265
    {
266
        if (empty($sheet_name) || empty($header_types) || !empty($this->sheets[$sheet_name])) {
267
            return;
268
        }
269
270
        $suppress_row = isset($col_options['suppress_row']) ? intval($col_options['suppress_row']) : false;
271
        if (is_bool($col_options)) {
272
            self::log("Warning! passing $suppress_row=false|true to writeSheetHeader() is deprecated, this will be removed in a future version.");
273
            $suppress_row = intval($col_options);
274
        }
275
        $style = &$col_options;
276
277
        $col_widths = isset($col_options['widths']) ? (array)$col_options['widths'] : array();
278
        $auto_filter = isset($col_options['auto_filter']) ? intval($col_options['auto_filter']) : false;
279
        $freeze_rows = isset($col_options['freeze_rows']) ? intval($col_options['freeze_rows']) : false;
280
        $freeze_columns = isset($col_options['freeze_columns']) ? intval($col_options['freeze_columns']) : false;
281
        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 278 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 279 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 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...
282
        $sheet = &$this->sheets[$sheet_name];
283
        $sheet->columns = $this->initializeColumnTypes($header_types);
284
        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...
285
            $header_row = array_keys($header_types);
286
287
            $sheet->file_writer->write('<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="' . (1) . '">');
288
            foreach ($header_row as $c => $v) {
289
                $cell_style_idx = empty($style) ? $sheet->columns[$c]['default_cell_style'] : $this->addCellStyle('GENERAL',
290
                    json_encode(isset($style[0]) ? $style[$c] : $style));
291
                $this->writeCell($sheet->file_writer, 0, $c, $v, $number_format_type = 'n_string', $cell_style_idx);
292
            }
293
            $sheet->file_writer->write('</row>');
294
            $sheet->row_count++;
295
        }
296
        $this->current_sheet = $sheet_name;
297
    }
298
299
    public function writeSheetRow($sheet_name, array $row, $row_options = null)
300
    {
301
        if (empty($sheet_name)) {
302
            return;
303
        }
304
305
        self::initializeSheet($sheet_name);
306
        $sheet = &$this->sheets[$sheet_name];
307
        if (count($sheet->columns) < count($row)) {
308
            $default_column_types = $this->initializeColumnTypes(array_fill($from = 0, $until = count($row),
309
                'GENERAL'));//will map to n_auto
310
            $sheet->columns = array_merge((array)$sheet->columns, $default_column_types);
311
        }
312
313
        if (!empty($row_options)) {
314
            $ht = isset($row_options['height']) ? floatval($row_options['height']) : 12.1;
315
            $customHt = isset($row_options['height']) ? true : false;
316
            $hidden = isset($row_options['hidden']) ? (bool)($row_options['hidden']) : false;
317
            $collapsed = isset($row_options['collapsed']) ? (bool)($row_options['collapsed']) : false;
318
            $sheet->file_writer->write('<row collapsed="' . ($collapsed) . '" customFormat="false" customHeight="' . ($customHt) . '" hidden="' . ($hidden) . '" ht="' . ($ht) . '" outlineLevel="0" r="' . ($sheet->row_count + 1) . '">');
319
        } else {
320
            $sheet->file_writer->write('<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="' . ($sheet->row_count + 1) . '">');
321
        }
322
323
        $style = &$row_options;
324
        $c = 0;
325
        foreach ($row as $v) {
326
            $number_format = $sheet->columns[$c]['number_format'];
327
            $number_format_type = $sheet->columns[$c]['number_format_type'];
328
            $cell_style_idx = empty($style) ? $sheet->columns[$c]['default_cell_style'] : $this->addCellStyle($number_format,
329
                json_encode(isset($style[0]) ? $style[$c] : $style));
330
            $this->writeCell($sheet->file_writer, $sheet->row_count, $c, $v, $number_format_type, $cell_style_idx);
331
            $c++;
332
        }
333
        $sheet->file_writer->write('</row>');
334
        $sheet->row_count++;
335
        $this->current_sheet = $sheet_name;
336
    }
337
338
    public function countSheetRows($sheet_name = '')
339
    {
340
        $sheet_name = $sheet_name ?: $this->current_sheet;
341
        return array_key_exists($sheet_name, $this->sheets) ? $this->sheets[$sheet_name]->row_count : 0;
342
    }
343
344
    protected function finalizeSheet($sheet_name)
345
    {
346
        if (empty($sheet_name) || $this->sheets[$sheet_name]->finalized) {
347
            return;
348
        }
349
350
        $sheet = &$this->sheets[$sheet_name];
351
352
        $sheet->file_writer->write('</sheetData>');
353
354
        if (!empty($sheet->merge_cells)) {
355
            $sheet->file_writer->write('<mergeCells>');
356
            foreach ($sheet->merge_cells as $range) {
357
                $sheet->file_writer->write('<mergeCell ref="' . $range . '"/>');
358
            }
359
            $sheet->file_writer->write('</mergeCells>');
360
        }
361
362
        $max_cell = self::xlsCell($sheet->row_count - 1, count($sheet->columns) - 1);
363
364
        if ($sheet->auto_filter) {
365
            $sheet->file_writer->write('<autoFilter ref="A1:' . $max_cell . '"/>');
366
        }
367
368
        $sheet->file_writer->write('<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"/>');
369
        $sheet->file_writer->write('<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>');
370
        $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"/>');
371
        $sheet->file_writer->write('<headerFooter differentFirst="false" differentOddEven="false">');
372
        $sheet->file_writer->write('<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>');
373
        $sheet->file_writer->write('<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>');
374
        $sheet->file_writer->write('</headerFooter>');
375
        $sheet->file_writer->write('</worksheet>');
376
377
        $max_cell_tag = '<dimension ref="A1:' . $max_cell . '"/>';
378
        $padding_length = $sheet->max_cell_tag_end - $sheet->max_cell_tag_start - strlen($max_cell_tag);
379
        $sheet->file_writer->fseek($sheet->max_cell_tag_start);
380
        $sheet->file_writer->write($max_cell_tag . str_repeat(" ", $padding_length));
381
        $sheet->file_writer->close();
382
        $sheet->finalized = true;
383
    }
384
385
    public function markMergedCell($sheet_name, $start_cell_row, $start_cell_column, $end_cell_row, $end_cell_column)
386
    {
387
        if (empty($sheet_name) || $this->sheets[$sheet_name]->finalized) {
388
            return;
389
        }
390
391
        self::initializeSheet($sheet_name);
392
        $sheet = &$this->sheets[$sheet_name];
393
394
        $startCell = self::xlsCell($start_cell_row, $start_cell_column);
395
        $endCell = self::xlsCell($end_cell_row, $end_cell_column);
396
        $sheet->merge_cells[] = $startCell . ":" . $endCell;
397
    }
398
399
    public function writeSheet(array $data, $sheet_name = '', array $header_types = array())
400
    {
401
        $sheet_name = empty($sheet_name) ? 'Sheet1' : $sheet_name;
402
        $data = empty($data) ? array(array('')) : $data;
403
        if (!empty($header_types)) {
404
            $this->writeSheetHeader($sheet_name, $header_types);
405
        }
406
        foreach ($data as $i => $row) {
407
            $this->writeSheetRow($sheet_name, $row);
408
        }
409
        $this->finalizeSheet($sheet_name);
410
    }
411
412
    protected function writeCell(
413
        XLSXWriter_BuffererWriter &$file,
414
        $row_number,
415
        $column_number,
416
        $value,
417
        $num_format_type,
418
        $cell_style_idx
419
    ) {
420
        $cell_name = self::xlsCell($row_number, $column_number);
421
422
        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...
423
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '"/>');
424
        } elseif (is_string($value) && $value{0} == '=') {
425
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="s"><f>' . self::xmlspecialchars($value) . '</f></c>');
426
        } elseif ($num_format_type == 'n_date') {
427
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . intval(self::convert_date_time($value)) . '</v></c>');
428
        } elseif ($num_format_type == 'n_datetime') {
429
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . self::convert_date_time($value) . '</v></c>');
430 View Code Duplication
        } elseif ($num_format_type == 'n_numeric') {
431
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . self::xmlspecialchars($value) . '</v></c>');//int,float,currency
432
        } elseif ($num_format_type == 'n_string') {
433
            $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="inlineStr"><is><t>' . self::xmlspecialchars($value) . '</t></is></c>');
434
        } elseif ($num_format_type == 'n_auto' || 1) { //auto-detect unknown column types
435
            if (!is_string($value) || $value == '0' || ($value[0] != '0' && ctype_digit($value)) || preg_match("/^\-?(0|[1-9][0-9]*)(\.[0-9]+)?$/",
436
                    $value)
437
            ) {
438
                $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="n"><v>' . self::xmlspecialchars($value) . '</v></c>');//int,float,currency
439 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...
440
                $file->write('<c r="' . $cell_name . '" s="' . $cell_style_idx . '" t="inlineStr"><is><t>' . self::xmlspecialchars($value) . '</t></is></c>');
441
            }
442
        }
443
    }
444
445
    protected function styleFontIndexes()
446
    {
447
        static $border_allowed = array('left', 'right', 'top', 'bottom');
448
        static $border_style_allowed = array(
449
            'thin',
450
            'medium',
451
            'thick',
452
            'dashDot',
453
            'dashDotDot',
454
            'dashed',
455
            'dotted',
456
            'double',
457
            'hair',
458
            'mediumDashDot',
459
            'mediumDashDotDot',
460
            'mediumDashed',
461
            'slantDashDot'
462
        );
463
        static $horizontal_allowed = array('general', 'left', 'right', 'justify', 'center');
464
        static $vertical_allowed = array('bottom', 'center', 'distributed', 'top');
465
        $default_font = array('size' => '10', 'name' => 'Arial', 'family' => '2');
466
        $fills = array('', '');//2 placeholders for static xml later
467
        $fonts = array('', '', '', '');//4 placeholders for static xml later
468
        $borders = array('');//1 placeholder for static xml later
469
        $style_indexes = array();
470
        foreach ($this->cell_styles as $i => $cell_style_string) {
471
            $semi_colon_pos = strpos($cell_style_string, ";");
472
            $number_format_idx = substr($cell_style_string, 0, $semi_colon_pos);
473
            $style_json_string = substr($cell_style_string, $semi_colon_pos + 1);
474
            $style = @json_decode($style_json_string, $as_assoc = true);
475
476
            $style_indexes[$i] = array('num_fmt_idx' => $number_format_idx);//initialize entry
477
            if (isset($style['border']) && is_string($style['border']))//border is a comma delimited str
478
            {
479
                $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...
480
                if (isset($style['border-style']) && in_array($style['border-style'], $border_style_allowed)) {
481
                    $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...
482
                }
483 View Code Duplication
                if (isset($style['border-color']) && is_string($style['border-color']) && $style['border-color'][0] == '#') {
484
                    $v = substr($style['border-color'], 1, 6);
485
                    $v = strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v;// expand cf0 => ccff00
486
                    $border_value['color'] = "FF" . strtoupper($v);
487
                }
488
                $style_indexes[$i]['border_idx'] = self::add_to_list_get_index($borders, json_encode($border_value));
489
            }
490 View Code Duplication
            if (isset($style['fill']) && is_string($style['fill']) && $style['fill'][0] == '#') {
491
                $v = substr($style['fill'], 1, 6);
492
                $v = strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v;// expand cf0 => ccff00
493
                $style_indexes[$i]['fill_idx'] = self::add_to_list_get_index($fills, "FF" . strtoupper($v));
494
            }
495 View Code Duplication
            if (isset($style['halign']) && in_array($style['halign'], $horizontal_allowed)) {
496
                $style_indexes[$i]['alignment'] = true;
497
                $style_indexes[$i]['halign'] = $style['halign'];
498
            }
499 View Code Duplication
            if (isset($style['valign']) && in_array($style['valign'], $vertical_allowed)) {
500
                $style_indexes[$i]['alignment'] = true;
501
                $style_indexes[$i]['valign'] = $style['valign'];
502
            }
503
            if (isset($style['wrap_text'])) {
504
                $style_indexes[$i]['alignment'] = true;
505
                $style_indexes[$i]['wrap_text'] = (bool)$style['wrap_text'];
506
            }
507
508
            $font = $default_font;
509
            if (isset($style['font-size'])) {
510
                $font['size'] = floatval($style['font-size']);//floatval to allow "10.5" etc
511
            }
512
            if (isset($style['font']) && is_string($style['font'])) {
513
                if ($style['font'] == 'Comic Sans MS') {
514
                    $font['family'] = 4;
515
                }
516
                if ($style['font'] == 'Times New Roman') {
517
                    $font['family'] = 1;
518
                }
519
                if ($style['font'] == 'Courier New') {
520
                    $font['family'] = 3;
521
                }
522
                $font['name'] = strval($style['font']);
523
            }
524
            if (isset($style['font-style']) && is_string($style['font-style'])) {
525 View Code Duplication
                if (strpos($style['font-style'], 'bold') !== false) {
526
                    $font['bold'] = true;
527
                }
528 View Code Duplication
                if (strpos($style['font-style'], 'italic') !== false) {
529
                    $font['italic'] = true;
530
                }
531 View Code Duplication
                if (strpos($style['font-style'], 'strike') !== false) {
532
                    $font['strike'] = true;
533
                }
534 View Code Duplication
                if (strpos($style['font-style'], 'underline') !== false) {
535
                    $font['underline'] = true;
536
                }
537
            }
538 View Code Duplication
            if (isset($style['color']) && is_string($style['color']) && $style['color'][0] == '#') {
539
                $v = substr($style['color'], 1, 6);
540
                $v = strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v;// expand cf0 => ccff00
541
                $font['color'] = "FF" . strtoupper($v);
542
            }
543
            if ($font != $default_font) {
544
                $style_indexes[$i]['font_idx'] = self::add_to_list_get_index($fonts, json_encode($font));
545
            }
546
        }
547
        return array('fills' => $fills, 'fonts' => $fonts, 'borders' => $borders, 'styles' => $style_indexes);
548
    }
549
550
    protected function writeStylesXML()
551
    {
552
        $r = self::styleFontIndexes();
553
        $fills = $r['fills'];
554
        $fonts = $r['fonts'];
555
        $borders = $r['borders'];
556
        $style_indexes = $r['styles'];
557
558
        $temporary_filename = $this->tempFilename();
559
        $file = new XLSXWriter_BuffererWriter($temporary_filename);
560
        $file->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n");
561
        $file->write('<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
562
        $file->write('<numFmts count="' . count($this->number_formats) . '">');
563
        foreach ($this->number_formats as $i => $v) {
564
            $file->write('<numFmt numFmtId="' . (164 + $i) . '" formatCode="' . self::xmlspecialchars($v) . '" />');
565
        }
566
        //$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...
567
        //$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...
568
        //$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...
569
        //$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...
570
        $file->write('</numFmts>');
571
572
        $file->write('<fonts count="' . (count($fonts)) . '">');
573
        $file->write('<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
574
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
575
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
576
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
577
578
        foreach ($fonts as $font) {
579
            if (!empty($font)) { //fonts have 4 empty placeholders in array to offset the 4 static xml entries above
580
                $f = json_decode($font, true);
581
                $file->write('<font>');
582
                $file->write('<name val="' . htmlspecialchars($f['name']) . '"/><charset val="1"/><family val="' . intval($f['family']) . '"/>');
583
                $file->write('<sz val="' . intval($f['size']) . '"/>');
584
                if (!empty($f['color'])) {
585
                    $file->write('<color rgb="' . strval($f['color']) . '"/>');
586
                }
587
                if (!empty($f['bold'])) {
588
                    $file->write('<b val="true"/>');
589
                }
590
                if (!empty($f['italic'])) {
591
                    $file->write('<i val="true"/>');
592
                }
593
                if (!empty($f['underline'])) {
594
                    $file->write('<u val="single"/>');
595
                }
596
                if (!empty($f['strike'])) {
597
                    $file->write('<strike val="true"/>');
598
                }
599
                $file->write('</font>');
600
            }
601
        }
602
        $file->write('</fonts>');
603
604
        $file->write('<fills count="' . (count($fills)) . '">');
605
        $file->write('<fill><patternFill patternType="none"/></fill>');
606
        $file->write('<fill><patternFill patternType="gray125"/></fill>');
607
        foreach ($fills as $fill) {
608
            if (!empty($fill)) { //fills have 2 empty placeholders in array to offset the 2 static xml entries above
609
                $file->write('<fill><patternFill patternType="solid"><fgColor rgb="' . strval($fill) . '"/><bgColor indexed="64"/></patternFill></fill>');
610
            }
611
        }
612
        $file->write('</fills>');
613
614
        $file->write('<borders count="' . (count($borders)) . '">');
615
        $file->write('<border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border>');
616
        foreach ($borders as $border) {
617
            if (!empty($border)) { //fonts have an empty placeholder in the array to offset the static xml entry above
618
                $pieces = json_decode($border, true);
619
                $border_style = !empty($pieces['style']) ? $pieces['style'] : 'hair';
620
                $border_color = !empty($pieces['color']) ? '<color rgb="' . strval($pieces['color']) . '"/>' : '';
621
                $file->write('<border diagonalDown="false" diagonalUp="false">');
622
                foreach (array('left', 'right', 'top', 'bottom') as $side) {
623
                    $show_side = in_array($side, $pieces['side']) ? true : false;
624
                    $file->write($show_side ? "<$side style=\"$border_style\">$border_color</$side>" : "<$side/>");
625
                }
626
                $file->write('<diagonal/>');
627
                $file->write('</border>');
628
            }
629
        }
630
        $file->write('</borders>');
631
632
        $file->write('<cellStyleXfs count="20">');
633
        $file->write('<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
634
        $file->write('<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
635
        $file->write('<protection hidden="false" locked="true"/>');
636
        $file->write('</xf>');
637
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
638
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
639
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
640
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
641
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
642
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" 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="1" numFmtId="43"/>');
652
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
653
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
654
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
655
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
656
        $file->write('</cellStyleXfs>');
657
658
        $file->write('<cellXfs count="' . (count($style_indexes)) . '">');
659
        //$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...
660
        //$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...
661
        //$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...
662
        //$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...
663
        foreach ($style_indexes as $v) {
664
            $applyAlignment = isset($v['alignment']) ? 'true' : 'false';
665
            $wrapText = !empty($v['wrap_text']) ? 'true' : 'false';
666
            $horizAlignment = isset($v['halign']) ? $v['halign'] : 'general';
667
            $vertAlignment = isset($v['valign']) ? $v['valign'] : 'bottom';
668
            $applyBorder = isset($v['border_idx']) ? 'true' : 'false';
669
            $applyFont = 'true';
670
            $borderIdx = isset($v['border_idx']) ? intval($v['border_idx']) : 0;
671
            $fillIdx = isset($v['fill_idx']) ? intval($v['fill_idx']) : 0;
672
            $fontIdx = isset($v['font_idx']) ? intval($v['font_idx']) : 0;
673
            //$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...
674
            $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">');
675
            $file->write('	<alignment horizontal="' . $horizAlignment . '" vertical="' . $vertAlignment . '" textRotation="0" wrapText="' . $wrapText . '" indent="0" shrinkToFit="false"/>');
676
            $file->write('	<protection locked="true" hidden="false"/>');
677
            $file->write('</xf>');
678
        }
679
        $file->write('</cellXfs>');
680
        $file->write('<cellStyles count="6">');
681
        $file->write('<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
682
        $file->write('<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
683
        $file->write('<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
684
        $file->write('<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
685
        $file->write('<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
686
        $file->write('<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
687
        $file->write('</cellStyles>');
688
        $file->write('</styleSheet>');
689
        $file->close();
690
        return $temporary_filename;
691
    }
692
693
    protected function buildAppXML()
694
    {
695
        $app_xml = "";
696
        $app_xml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
697
        $app_xml .= '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">';
698
        $app_xml .= '<TotalTime>0</TotalTime>';
699
        $app_xml .= '<Company>' . self::xmlspecialchars($this->company) . '</Company>';
700
        $app_xml .= '</Properties>';
701
        return $app_xml;
702
    }
703
704
    protected function buildCoreXML()
705
    {
706
        $core_xml = "";
707
        $core_xml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
708
        $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">';
709
        $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...
710
        $core_xml .= '<dc:title>' . self::xmlspecialchars($this->title) . '</dc:title>';
711
        $core_xml .= '<dc:subject>' . self::xmlspecialchars($this->subject) . '</dc:subject>';
712
        $core_xml .= '<dc:creator>' . self::xmlspecialchars($this->author) . '</dc:creator>';
713
        if (!empty($this->keywords)) {
714
            $core_xml .= '<cp:keywords>' . self::xmlspecialchars(implode(", ",
715
                    (array)$this->keywords)) . '</cp:keywords>';
716
        }
717
        $core_xml .= '<dc:description>' . self::xmlspecialchars($this->description) . '</dc:description>';
718
        $core_xml .= '<cp:revision>0</cp:revision>';
719
        $core_xml .= '</cp:coreProperties>';
720
        return $core_xml;
721
    }
722
723
    protected function buildRelationshipsXML()
724
    {
725
        $rels_xml = "";
726
        $rels_xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
727
        $rels_xml .= '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
728
        $rels_xml .= '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>';
729
        $rels_xml .= '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>';
730
        $rels_xml .= '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>';
731
        $rels_xml .= "\n";
732
        $rels_xml .= '</Relationships>';
733
        return $rels_xml;
734
    }
735
736
    protected function buildWorkbookXML()
737
    {
738
        $i = 0;
739
        $workbook_xml = "";
740
        $workbook_xml .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
741
        $workbook_xml .= '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
742
        $workbook_xml .= '<fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/>';
743
        $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>';
744
        $workbook_xml .= '<sheets>';
745
        foreach ($this->sheets as $sheet_name => $sheet) {
746
            $sheetname = self::sanitize_sheetname($sheet->sheetname);
747
            $workbook_xml .= '<sheet name="' . self::xmlspecialchars($sheetname) . '" sheetId="' . ($i + 1) . '" state="visible" r:id="rId' . ($i + 2) . '"/>';
748
            $i++;
749
        }
750
        $workbook_xml .= '</sheets>';
751
        $workbook_xml .= '<definedNames>';
752
        foreach ($this->sheets as $sheet_name => $sheet) {
753
            if ($sheet->auto_filter) {
754
                $sheetname = self::sanitize_sheetname($sheet->sheetname);
755
                $workbook_xml .= '<definedName name="_xlnm._FilterDatabase" localSheetId="0" hidden="1">\'' . self::xmlspecialchars($sheetname) . '\'!$A$1:' . self::xlsCell($sheet->row_count - 1,
756
                        count($sheet->columns) - 1, true) . '</definedName>';
757
                $i++;
758
            }
759
        }
760
        $workbook_xml .= '</definedNames>';
761
        $workbook_xml .= '<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
762
        return $workbook_xml;
763
    }
764
765
    protected function buildWorkbookRelsXML()
766
    {
767
        $i = 0;
768
        $wkbkrels_xml = "";
769
        $wkbkrels_xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
770
        $wkbkrels_xml .= '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
771
        $wkbkrels_xml .= '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>';
772
        foreach ($this->sheets as $sheet_name => $sheet) {
773
            $wkbkrels_xml .= '<Relationship Id="rId' . ($i + 2) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/' . ($sheet->xmlname) . '"/>';
774
            $i++;
775
        }
776
        $wkbkrels_xml .= "\n";
777
        $wkbkrels_xml .= '</Relationships>';
778
        return $wkbkrels_xml;
779
    }
780
781
    protected function buildContentTypesXML()
782
    {
783
        $content_types_xml = "";
784
        $content_types_xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
785
        $content_types_xml .= '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
786
        $content_types_xml .= '<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
787
        $content_types_xml .= '<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
788
        foreach ($this->sheets as $sheet_name => $sheet) {
789
            $content_types_xml .= '<Override PartName="/xl/worksheets/' . ($sheet->xmlname) . '" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
790
        }
791
        $content_types_xml .= '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
792
        $content_types_xml .= '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
793
        $content_types_xml .= '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>';
794
        $content_types_xml .= '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>';
795
        $content_types_xml .= "\n";
796
        $content_types_xml .= '</Types>';
797
        return $content_types_xml;
798
    }
799
800
    //------------------------------------------------------------------
801
    /*
802
     * @param $row_number int, zero based
803
     * @param $column_number int, zero based
804
     * @param $absolute bool
805
     * @return Cell label/coordinates, ex: A1, C3, AA42 (or if $absolute==true: $A$1, $C$3, $AA$42)
806
     * */
807
    public static function xlsCell($row_number, $column_number, $absolute = false)
808
    {
809
        $n = $column_number;
810
        for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
811
            $r = chr($n % 26 + 0x41) . $r;
812
        }
813
        if ($absolute) {
814
            return '$' . $r . '$' . ($row_number + 1);
815
        }
816
        return $r . ($row_number + 1);
817
    }
818
819
    //------------------------------------------------------------------
820
    public static function log($string)
821
    {
822
        file_put_contents("php://stderr",
823
            date("Y-m-d H:i:s:") . rtrim(is_array($string) ? json_encode($string) : $string) . "\n");
824
    }
825
826
    //------------------------------------------------------------------
827
    public static function sanitize_filename($filename
828
    ) //http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
829
    {
830
        $nonprinting = array_map('chr', range(0, 31));
831
        $invalid_chars = array('<', '>', '?', '"', ':', '|', '\\', '/', '*', '&');
832
        $all_invalids = array_merge($nonprinting, $invalid_chars);
833
        return str_replace($all_invalids, "", $filename);
834
    }
835
836
    //------------------------------------------------------------------
837
    public static function sanitize_sheetname($sheetname)
838
    {
839
        static $badchars = '\\/?*:[]';
840
        static $goodchars = '        ';
841
        $sheetname = strtr($sheetname, $badchars, $goodchars);
842
        $sheetname = substr($sheetname, 0, 31);
843
        $sheetname = trim(trim(trim($sheetname), "'"));//trim before and after trimming single quotes
844
        return !empty($sheetname) ? $sheetname : 'Sheet' . ((rand() % 900) + 100);
845
    }
846
847
    //------------------------------------------------------------------
848
    public static function xmlspecialchars($val)
849
    {
850
        //note, badchars does not include \t\n\r (\x09\x0a\x0d)
851
        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";
852
        static $goodchars = "                              ";
853
        return strtr(htmlspecialchars($val, ENT_QUOTES | ENT_XML1), $badchars,
854
            $goodchars);//strtr appears to be faster than str_replace
855
    }
856
857
    //------------------------------------------------------------------
858
    public static function array_first_key(array $arr)
859
    {
860
        reset($arr);
861
        $first_key = key($arr);
862
        return $first_key;
863
    }
864
865
    //------------------------------------------------------------------
866
    private static function determineNumberFormatType($num_format)
867
    {
868
        $num_format = preg_replace("/\[(Black|Blue|Cyan|Green|Magenta|Red|White|Yellow)\]/i", "", $num_format);
869
        if ($num_format == 'GENERAL') {
870
            return 'n_auto';
871
        }
872
        if ($num_format == '@') {
873
            return 'n_string';
874
        }
875
        if ($num_format == '0') {
876
            return 'n_numeric';
877
        }
878
        if (preg_match("/[H]{1,2}:[M]{1,2}/i", $num_format)) {
879
            return 'n_datetime';
880
        }
881
        if (preg_match("/[M]{1,2}:[S]{1,2}/i", $num_format)) {
882
            return 'n_datetime';
883
        }
884
        if (preg_match("/[Y]{2,4}/i", $num_format)) {
885
            return 'n_date';
886
        }
887
        if (preg_match("/[D]{1,2}/i", $num_format)) {
888
            return 'n_date';
889
        }
890
        if (preg_match("/[M]{1,2}/i", $num_format)) {
891
            return 'n_date';
892
        }
893
        if (preg_match("/$/", $num_format)) {
894
            return 'n_numeric';
895
        }
896
        if (preg_match("/%/", $num_format)) {
897
            return 'n_numeric';
898
        }
899
        if (preg_match("/0/", $num_format)) {
900
            return 'n_numeric';
901
        }
902
        return 'n_auto';
903
    }
904
905
    //------------------------------------------------------------------
906
    private static function numberFormatStandardized($num_format)
907
    {
908
        if ($num_format == 'money') {
909
            $num_format = 'dollar';
910
        }
911
        if ($num_format == 'number') {
912
            $num_format = 'integer';
913
        }
914
915
        if ($num_format == 'string') {
916
            $num_format = '@';
917
        } else {
918
            if ($num_format == 'integer') {
919
                $num_format = '0';
920
            } else {
921
                if ($num_format == 'date') {
922
                    $num_format = 'YYYY-MM-DD';
923
                } else {
924
                    if ($num_format == 'datetime') {
925
                        $num_format = 'YYYY-MM-DD HH:MM:SS';
926
                    } else {
927
                        if ($num_format == 'price') {
928
                            $num_format = '#,##0.00';
929
                        } else {
930
                            if ($num_format == 'dollar') {
931
                                $num_format = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
932
                            } else {
933
                                if ($num_format == 'euro') {
934
                                    $num_format = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]';
935
                                }
936
                            }
937
                        }
938
                    }
939
                }
940
            }
941
        }
942
        $ignore_until = '';
943
        $escaped = '';
944
        for ($i = 0, $ix = strlen($num_format); $i < $ix; $i++) {
945
            $c = $num_format[$i];
946
            if ($ignore_until == '' && $c == '[') {
947
                $ignore_until = ']';
948
            } else {
949
                if ($ignore_until == '' && $c == '"') {
950
                    $ignore_until = '"';
951
                } else {
952
                    if ($ignore_until == $c) {
953
                        $ignore_until = '';
954
                    }
955
                }
956
            }
957
            if ($ignore_until == '' && ($c == ' ' || $c == '-' || $c == '(' || $c == ')') && ($i == 0 || $num_format[$i - 1] != '_')) {
958
                $escaped .= "\\" . $c;
959
            } else {
960
                $escaped .= $c;
961
            }
962
        }
963
        return $escaped;
964
    }
965
966
    //------------------------------------------------------------------
967
    public static function add_to_list_get_index(&$haystack, $needle)
968
    {
969
        $existing_idx = array_search($needle, $haystack, $strict = true);
970
        if ($existing_idx === false) {
971
            $existing_idx = count($haystack);
972
            $haystack[] = $needle;
973
        }
974
        return $existing_idx;
975
    }
976
977
    //------------------------------------------------------------------
978
    public static function convert_date_time($date_input) //thanks to Excel::Writer::XLSX::Worksheet.pm (perl)
979
    {
980
        $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...
981
        $seconds = 0;    # Time expressed as fraction of 24h hours in seconds
982
        $year = $month = $day = 0;
983
        $hour = $min = $sec = 0;
984
985
        $date_time = $date_input;
986
        if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $date_time, $matches)) {
987
            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...
988
        }
989
        if (preg_match("/(\d+):(\d{2}):(\d{2})/", $date_time, $matches)) {
990
            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...
991
            $seconds = ($hour * 60 * 60 + $min * 60 + $sec) / (24 * 60 * 60);
992
        }
993
994
        //using 1900 as epoch, not 1904, ignoring 1904 special case
995
996
        # Special cases for Excel.
997
        if ("$year-$month-$day" == '1899-12-31') {
998
            return $seconds;
999
        }    # Excel 1900 epoch
1000
        if ("$year-$month-$day" == '1900-01-00') {
1001
            return $seconds;
1002
        }    # Excel 1900 epoch
1003
        if ("$year-$month-$day" == '1900-02-29') {
1004
            return 60 + $seconds;
1005
        }    # Excel false leapday
1006
1007
        # We calculate the date by calculating the number of days since the epoch
1008
        # and adjust for the number of leap days. We calculate the number of leap
1009
        # days by normalising the year in relation to the epoch. Thus the year 2000
1010
        # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
1011
        $epoch = 1900;
1012
        $offset = 0;
1013
        $norm = 300;
1014
        $range = $year - $epoch;
1015
1016
        # Set month days and check for leap year.
1017
        $leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100))) ? 1 : 0;
1018
        $mdays = array(31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
1019
1020
        # Some boundary checks
1021
        if ($year < $epoch || $year > 9999) {
1022
            return 0;
1023
        }
1024
        if ($month < 1 || $month > 12) {
1025
            return 0;
1026
        }
1027
        if ($day < 1 || $day > $mdays[$month - 1]) {
1028
            return 0;
1029
        }
1030
1031
        # Accumulate the number of days since the epoch.
1032
        $days = $day;    # Add days for current month
1033
        $days += array_sum(array_slice($mdays, 0, $month - 1));    # Add days for past months
1034
        $days += $range * 365;                      # Add days for past years
1035
        $days += intval(($range) / 4);             # Add leapdays
1036
        $days -= intval(($range + $offset) / 100); # Subtract 100 year leapdays
1037
        $days += intval(($range + $offset + $norm) / 400);  # Add 400 year leapdays
1038
        $days -= $leap;                                      # Already counted above
1039
1040
        # Adjust for Excel erroneously treating 1900 as a leap year.
1041
        if ($days > 59) {
1042
            $days++;
1043
        }
1044
1045
        return $days + $seconds;
1046
    }
1047
    //------------------------------------------------------------------
1048
}
1049
1050
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...
1051
{
1052
    protected $fd = null;
1053
    protected $buffer = '';
1054
    protected $check_utf8 = false;
1055
1056
    public function __construct($filename, $fd_fopen_flags = 'w', $check_utf8 = false)
1057
    {
1058
        $this->check_utf8 = $check_utf8;
1059
        $this->fd = fopen($filename, $fd_fopen_flags);
1060
        if ($this->fd === false) {
1061
            XLSXWriter::log("Unable to open $filename for writing.");
1062
        }
1063
    }
1064
1065
    public function write($string)
1066
    {
1067
        $this->buffer .= $string;
1068
        if (isset($this->buffer[8191])) {
1069
            $this->purge();
1070
        }
1071
    }
1072
1073
    protected function purge()
1074
    {
1075
        if ($this->fd) {
1076
            if ($this->check_utf8 && !self::isValidUTF8($this->buffer)) {
1077
                XLSXWriter::log("Error, invalid UTF8 encoding detected.");
1078
                $this->check_utf8 = false;
1079
            }
1080
            fwrite($this->fd, $this->buffer);
1081
            $this->buffer = '';
1082
        }
1083
    }
1084
1085
    public function close()
1086
    {
1087
        $this->purge();
1088
        if ($this->fd) {
1089
            fclose($this->fd);
1090
            $this->fd = null;
1091
        }
1092
    }
1093
1094
    public function __destruct()
1095
    {
1096
        $this->close();
1097
    }
1098
1099
    public function ftell()
1100
    {
1101
        if ($this->fd) {
1102
            $this->purge();
1103
            return ftell($this->fd);
1104
        }
1105
        return -1;
1106
    }
1107
1108
    public function fseek($pos)
1109
    {
1110
        if ($this->fd) {
1111
            $this->purge();
1112
            return fseek($this->fd, $pos);
1113
        }
1114
        return -1;
1115
    }
1116
1117
    protected static function isValidUTF8($string)
1118
    {
1119
        if (function_exists('mb_check_encoding')) {
1120
            return mb_check_encoding($string, 'UTF-8') ? true : false;
1121
        }
1122
        return preg_match("//u", $string) ? true : false;
1123
    }
1124
}
1125