ILIAS  release_8 Revision v8.24
class.ilExcel.php
Go to the documentation of this file.
1<?php
2
19use PhpOffice\PhpSpreadsheet\Spreadsheet;
20use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
21use PhpOffice\PhpSpreadsheet\IOFactory;
22use PhpOffice\PhpSpreadsheet\Shared\Date;
23use PhpOffice\PhpSpreadsheet\Cell\Cell;
24use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
25use PhpOffice\PhpSpreadsheet\Style\Fill;
26use PhpOffice\PhpSpreadsheet\Style\Border;
27use PhpOffice\PhpSpreadsheet\Cell\DataType;
29
30/*
31 * Wrapper for Microsoft Excel Import/Export (based on PHPSpreadsheet, formerPHPExcel which is deprecated)
32 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com>
33 */
35{
36 public const FORMAT_XML = "Xlsx";
37 public const FORMAT_BIFF = "Xls";
38 protected string $format;
39
40 private array $noncharacters = [
41 '\x{FFFE}-\x{FFFF}',
42 '\x{1FFFE}-\x{1FFFF}',
43 '\x{2FFFE}-\x{2FFFF}',
44 '\x{3FFFE}-\x{3FFFF}',
45 '\x{4FFFE}-\x{4FFFF}',
46 '\x{5FFFE}-\x{5FFFF}',
47 '\x{6FFFE}-\x{6FFFF}',
48 '\x{7FFFE}-\x{7FFFF}',
49 '\x{8FFFE}-\x{8FFFF}',
50 '\x{9FFFE}-\x{9FFFF}',
51 '\x{AFFFE}-\x{AFFFF}',
52 '\x{BFFFE}-\x{BFFFF}',
53 '\x{CFFFE}-\x{CFFFF}',
54 '\x{DFFFE}-\x{DFFFF}',
55 '\x{EFFFE}-\x{EFFFF}',
56 '\x{FFFFE}-\x{FFFFF}',
57 '\x{10FFFE}-\x{10FFFF}',
58 '\x{FDD0}-\x{FDEF}'
59 ];
60
61 protected ilLanguage $lng;
62 protected Spreadsheet $workbook;
63 protected string $type;
64
68 public function __construct()
69 {
70 global $DIC;
71
72 $this->lng = $DIC->language();
73 $this->setFormat(self::FORMAT_XML);
74 $this->workbook = new Spreadsheet();
75 $this->workbook->removeSheetByIndex(0);
76 }
77
78 //
79 // loading files
80 //
81
85 public function loadFromFile(string $filename): void
86 {
87 $this->workbook = IOFactory::load($filename);
88 }
89
90 //
91 // type/format
92 //
93
97 public function getValidFormats(): array
98 {
99 return array(self::FORMAT_XML, self::FORMAT_BIFF);
100 }
101
105 public function setFormat(string $a_format): void
106 {
107 if (in_array($a_format, $this->getValidFormats())) {
108 $this->format = $a_format;
109 }
110 }
111
112
113 //
114 // sheets
115 //
116
122 public function addSheet(
123 string $a_name,
124 bool $a_activate = true
125 ): int {
126 #20749
127 // see Worksheet::$_invalidCharacters;
128 $invalid = array('*', ':', '/', '\\', '?', '[', ']', '\'-','\'');
129
130 $a_name = str_replace($invalid, "", $a_name);
131
132 // #19056 - phpExcel only allows 31 chars
133 // see https://github.com/PHPOffice/PHPExcel/issues/79
134 $a_name = ilStr::shortenTextExtended($a_name, 31);
135
136 $sheet = new Worksheet($this->workbook, $a_name);
137 $this->workbook->addSheet($sheet);
138 $new_index = $this->workbook->getSheetCount() - 1;
139
140 if ($a_activate) {
141 $this->setActiveSheet($new_index);
142 }
143
144 return $new_index;
145 }
146
150 public function setActiveSheet(int $a_index): void
151 {
152 $this->workbook->setActiveSheetIndex($a_index);
153 }
154
155
159 public function getSheetCount(): int
160 {
161 return $this->workbook->getSheetCount();
162 }
163
164
168 public function getSheetTitle(): string
169 {
170 return $this->workbook->getActiveSheet()->getTitle();
171 }
172
173
174 //
175 // cells
176 //
177
184 protected function prepareValue($a_value)
185 {
186 if (is_bool($a_value)) {
187 return $this->prepareBooleanValue($a_value);
188 }
189
190 if ($a_value instanceof ilDateTime) {
191 return $this->prepareDateValue($a_value);
192 }
193
194 if (is_string($a_value)) {
195 if (!mb_check_encoding($a_value, 'UTF-8')) {
196 throw new InvalidArgumentException('Invalid UTF-8 passed.');
197 }
198 return $this->prepareString($a_value);
199 }
200
201 return $a_value;
202 }
203
208 protected function prepareDateValue(ilDateTime $a_value)
209 {
210 switch (true) {
211 case $a_value instanceof ilDate:
212 $a_value = Date::stringToExcel($a_value->get(IL_CAL_DATE));
213 break;
214
215 default:
216 $a_value = Date::stringToExcel($a_value->get(IL_CAL_DATETIME));
217 break;
218 }
219
220 return $a_value;
221 }
222
223 protected function prepareBooleanValue(bool $a_value): string
224 {
226
227 return $a_value ? $lng->txt('yes') : $lng->txt('no');
228 }
229
230 protected function prepareString(string $a_value): string
231 {
232 return $this->cleanupNonCharachters(
233 strip_tags($a_value)
234 ); // #14542
235 }
236
243 protected function setDateFormat(Cell $a_cell, $a_value): void
244 {
245 if ($a_value instanceof ilDate) {
246 $a_cell->getStyle()->getNumberFormat()->setFormatCode("dd.mm.yyyy");
247 } elseif ($a_value instanceof ilDateTime) {
248 $a_cell->getStyle()->getNumberFormat()->setFormatCode("dd.mm.yyyy hh:mm:ss");
249 }
250 }
251
257 public function setCellByCoordinates($a_coords, $a_value): void
258 {
259 if ($a_value instanceof ilDateTime) {
260 $wb = $this->workbook->getActiveSheet()->setCellValue(
261 $a_coords,
262 $this->prepareValue($a_value)
263 );
264 $cell = $wb->getCell($a_coords);
265 $this->setDateFormat($cell, $a_value);
266 } elseif (is_numeric($a_value)) {
267 $this->workbook->getActiveSheet()->setCellValueExplicit(
268 $a_coords,
269 $this->prepareValue($a_value),
270 DataType::TYPE_NUMERIC
271 );
272 } else {
273 $this->workbook->getActiveSheet()->setCellValueExplicit(
274 $a_coords,
275 $this->prepareValue($a_value),
276 DataType::TYPE_STRING
277 );
278 }
279 }
280
288 public function setCell(
289 int $a_row,
290 int $a_col,
291 $a_value,
292 ?string $a_datatype = null
293 ): void {
294 $col = $this->columnIndexAdjustment($a_col);
295
296 if (!is_null($a_datatype)) {
297 $this->workbook->getActiveSheet()->setCellValueExplicitByColumnAndRow(
298 $col,
299 $a_row,
300 $this->prepareValue($a_value),
301 $a_datatype
302 );
303 } elseif ($a_value instanceof ilDateTime) {
304 $wb = $this->workbook->getActiveSheet()->setCellValueByColumnAndRow(
305 $col,
306 $a_row,
307 $this->prepareValue($a_value)
308 );
309 $this->setDateFormat($wb->getCellByColumnAndRow($col, $a_row), $a_value);
310 } elseif (is_numeric($a_value)) {
311 $wb = $this->workbook->getActiveSheet()->setCellValueExplicitByColumnAndRow(
312 $col,
313 $a_row,
314 $this->prepareValue($a_value),
315 DataType::TYPE_NUMERIC
316 );
317 } else {
318 $wb = $this->workbook->getActiveSheet()->setCellValueExplicitByColumnAndRow(
319 $col,
320 $a_row,
321 $this->prepareValue($a_value),
322 DataType::TYPE_STRING
323 );
324 }
325 }
326
334 public function setCellArray(
335 array $a_values,
336 string $a_top_left = "A1",
337 $a_null_value = null
338 ): void {
339 foreach ($a_values as $row_idx => $cols) {
340 if (is_array($cols)) {
341 foreach ($cols as $col_idx => $col_value) {
342 $a_values[$row_idx][$col_idx] = $this->prepareValue($col_value);
343 }
344 } else {
345 $a_values[$row_idx] = $this->prepareValue($cols);
346 }
347 }
348
349 $this->workbook->getActiveSheet()->fromArray($a_values, $a_null_value, $a_top_left);
350 }
351
352
357 public function getCell(
358 int $a_row,
359 int $a_col
360 ) {
361 $col = $this->columnIndexAdjustment($a_col);
362 return $this->workbook->getActiveSheet()->getCellByColumnAndRow($col, $a_row)->getValue();
363 }
364
368 public function getSheetAsArray(): array
369 {
370 return $this->workbook->getActiveSheet()->toArray();
371 }
372
376 public function getColumnCount(): int
377 {
378 return Coordinate::columnIndexFromString($this->workbook->getActiveSheet()->getHighestDataColumn());
379 }
380
384 public function getColumnCoord(int $a_col): string
385 {
386 $col = $this->columnIndexAdjustment($a_col);
387 return Coordinate::stringFromColumnIndex($col);
388 }
389
393 protected function setGlobalAutoSize(): void
394 {
395 // this may change the active sheet
396 foreach ($this->workbook->getWorksheetIterator() as $worksheet) {
397 $this->workbook->setActiveSheetIndex($this->workbook->getIndex($worksheet));
398 $sheet = $this->workbook->getActiveSheet();
399 $cellIterator = $sheet->getRowIterator()->current()->getCellIterator();
400 $cellIterator->setIterateOnlyExistingCells(true);
401 foreach ($cellIterator as $cell) {
402 $sheet->getColumnDimension($cell->getColumn())->setAutoSize(true);
403 }
404 }
405 }
406
407 //
408 // deliver/save
409 //
410
414 protected function prepareStorage(string $a_file_name): string
415 {
416 $this->setGlobalAutoSize();
417 $this->workbook->setActiveSheetIndex(0);
418
419 switch ($this->format) {
420 case self::FORMAT_BIFF:
421 if (!stristr($a_file_name, ".xls")) {
422 $a_file_name .= ".xls";
423 }
424 break;
425
426 case self::FORMAT_XML:
427 if (!stristr($a_file_name, ".xlsx")) {
428 $a_file_name .= ".xlsx";
429 }
430 break;
431 }
432
433 return $a_file_name;
434 }
435
440 public function sendToClient(string $a_file_name): void
441 {
442 $a_file_name = $this->prepareStorage($a_file_name);
443 switch ($this->format) {
444 case self::FORMAT_BIFF:
445 $a_mime_type = MimeType::APPLICATION__VND_MS_EXCEL;
446 break;
447
448 case self::FORMAT_XML:
449 $a_mime_type = MimeType::APPLICATION__VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET;
450 break;
451 default:
452 $a_mime_type = MimeType::APPLICATION__OCTET_STREAM;
453 break;
454 }
455 $tmp_name = ilFileUtils::ilTempnam();
456
457 $writer = IOFactory::createWriter($this->workbook, $this->format);
458 $writer->save($tmp_name);
459
460 ilFileDelivery::deliverFileAttached($tmp_name, $a_file_name, $a_mime_type, true);
461 }
462
468 public function writeToFile(string $a_file): void
469 {
470 $a_file = $this->prepareStorage($a_file);
471
472 $writer = IOFactory::createWriter($this->workbook, $this->format);
473 $writer->save($a_file);
474 }
475
479 public function writeToTmpFile(): string
480 {
481 $writer = IOFactory::createWriter($this->workbook, $this->format);
483 $writer->save($filename);
484
485 return $filename;
486 }
487
491 public function setBold(string $a_coords): void
492 {
493 $this->workbook->getActiveSheet()->getStyle($a_coords)->getFont()->setBold(true);
494 }
495
499 public function setColors(
500 string $a_coords,
501 string $a_background,
502 string $a_font = null
503 ): void {
504 $opts = array(
505 'fill' => array(
506 'fillType' => Fill::FILL_SOLID,
507 'color' => array('rgb' => $a_background)
508 )
509 );
510
511 if ($a_font) {
512 $opts['font'] = array(
513 'color' => array('rgb' => $a_font)
514 );
515 }
516
517 $this->workbook->getActiveSheet()->getStyle($a_coords)->applyFromArray($opts);
518 }
519
523 public function setBorders(
524 string $a_coords,
525 bool $a_top,
526 bool $a_right = false,
527 bool $a_bottom = false,
528 bool $a_left = false
529 ): void {
530 $style = $this->workbook->getActiveSheet()->getStyle($a_coords);
531
532 // :TODO: border styles?
533 if ($a_top) {
534 $style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
535 }
536 if ($a_right) {
537 $style->getBorders()->getRight()->setBorderStyle(Border::BORDER_THIN);
538 }
539 if ($a_bottom) {
540 $style->getBorders()->getBottom()->setBorderStyle(Border::BORDER_THIN);
541 }
542 if ($a_left) {
543 $style->getBorders()->getLeft()->setBorderStyle(Border::BORDER_THIN);
544 }
545 }
546
550 public function getCoordByColumnAndRow(
551 int $pColumn = 1,
552 int $pRow = 1
553 ): string {
554 $col = $this->columnIndexAdjustment($pColumn);
555 $columnLetter = Coordinate::stringFromColumnIndex($col);
556
557 return $columnLetter . $pRow;
558 }
559
563 public function addLink(
564 int $a_row,
565 int $a_column,
566 string $a_path
567 ): void {
568 $column = $this->columnIndexAdjustment($a_column);
569 $this->workbook->getActiveSheet()->getCellByColumnAndRow($column, $a_row)->getHyperlink()->setUrl($a_path);
570 }
571
576 public function columnIndexAdjustment(int $column): int
577 {
578 return ++$column;
579 }
580
585 public function mergeCells(string $coordinatesRange): void
586 {
587 $this->workbook->getActiveSheet()->mergeCells($coordinatesRange);
588 }
589
590 private function cleanupNonCharachters(string $string): string
591 {
592 return mb_ereg_replace('[' . implode('', $this->noncharacters) . ']', '', $string);
593 }
594}
$filename
Definition: buildRTE.php:78
Mime type determination.
Definition: MimeType.php:28
const IL_CAL_DATE
const IL_CAL_DATETIME
@classDescription Date and time handling
get(int $a_format, string $a_format_str='', string $a_tz='')
get formatted date
Class for single dates.
Spreadsheet $workbook
setBold(string $a_coords)
Set cell(s) to bold.
prepareString(string $a_value)
getCell(int $a_row, int $a_col)
Returns the value of a cell.
prepareStorage(string $a_file_name)
Prepare workbook for storage/delivery.
array $noncharacters
getSheetAsArray()
Returns the active sheet as an array.
setCellArray(array $a_values, string $a_top_left="A1", $a_null_value=null)
Set cell values from array.
getSheetCount()
Returns number of sheets.
getColumnCoord(int $a_col)
Get column "name" from number.
prepareValue($a_value)
Prepare value for cell.
addSheet(string $a_name, bool $a_activate=true)
Add sheet.
loadFromFile(string $filename)
Loads a spreadsheet from file.
setGlobalAutoSize()
Set all existing columns on all sheets to autosize.
setBorders(string $a_coords, bool $a_top, bool $a_right=false, bool $a_bottom=false, bool $a_left=false)
Toggle cell(s) borders.
getCoordByColumnAndRow(int $pColumn=1, int $pRow=1)
Get cell coordinate (e.g.
writeToFile(string $a_file)
Save workbook to file.
columnIndexAdjustment(int $column)
Adjustment needed because of migration PHPExcel to PhpSpreadsheet.
sendToClient(string $a_file_name)
Send workbook to client.
ilLanguage $lng
setDateFormat(Cell $a_cell, $a_value)
Set date format of cell.
setColors(string $a_coords, string $a_background, string $a_font=null)
Set cell(s) colors.
const FORMAT_BIFF
setCellByCoordinates($a_coords, $a_value)
Set cell value by coordinates.
setActiveSheet(int $a_index)
mergeCells(string $coordinatesRange)
getColumnCount()
Returns the number of columns the sheet contains.
string $type
prepareDateValue(ilDateTime $a_value)
getSheetTitle()
Return the current sheet title.
setFormat(string $a_format)
Set file format.
const FORMAT_XML
string $format
getValidFormats()
Get valid file formats.
prepareBooleanValue(bool $a_value)
addLink(int $a_row, int $a_column, string $a_path)
setCell(int $a_row, int $a_col, $a_value, ?string $a_datatype=null)
Set cell value.
cleanupNonCharachters(string $string)
static deliverFileAttached(string $path_to_file, ?string $download_file_name=null, ?string $mime_type=null, bool $delete_file=false)
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
language handling
static shortenTextExtended(string $a_str, int $a_len, bool $a_dots=false, bool $a_next_blank=false, bool $a_keep_extension=false)
global $DIC
Definition: feed.php:28
$lng
$cols
Definition: xhr_table.php:11