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