dolibarr 21.0.0-alpha
export_excel2007.modules.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2012 Marcos García <marcosgdf@gmail.com>
4 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
28require_once DOL_DOCUMENT_ROOT.'/core/modules/export/modules_export.php';
29require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
30
31use PhpOffice\PhpSpreadsheet\Spreadsheet;
32use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
33use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
34
39{
43 public $label;
44
48 public $extension;
49
54 public $version = 'dolibarr';
55
57 public $label_lib;
58
60 public $version_lib;
61
63 public $workbook; // Handle file
64
66 public $worksheet; // Handle sheet
67
69 public $styleArray;
70
72 public $row;
73
75 public $col;
76
78 public $file; // To save filename
79
80
86 public function __construct($db)
87 {
88 global $langs;
89 $this->db = $db;
90
91 $this->id = 'excel2007'; // Same value then xxx in file name export_xxx.modules.php
92 $this->label = 'Excel 2007'; // Label of driver
93 $this->desc = $langs->trans('Excel2007FormatDesc');
94 $this->extension = 'xlsx'; // Extension for generated file by this driver
95 $this->picto = 'mime/xls'; // Picto
96 $this->version = '1.30'; // Driver version
97 $this->phpmin = array(7, 0); // Minimum version of PHP required by module
98
99 $this->disabled = 0;
100
101 if (empty($this->disabled)) {
102 require_once PHPEXCELNEW_PATH.'Spreadsheet.php';
103 $this->label_lib = 'PhpSpreadSheet';
104 $this->version_lib = '1.12.0'; // No way to get info from library
105 }
106
107 $this->row = 0;
108 }
109
115 public function getDriverId()
116 {
117 return $this->id;
118 }
119
125 public function getDriverLabel()
126 {
127 return $this->label;
128 }
129
135 public function getDriverLabelBis()
136 {
137 global $langs;
138 $langs->load("errors");
139 return $langs->trans("NumberOfLinesLimited");
140 }
141
147 public function getDriverDesc()
148 {
149 return $this->desc;
150 }
151
157 public function getDriverExtension()
158 {
159 return $this->extension;
160 }
161
167 public function getDriverVersion()
168 {
169 return $this->version;
170 }
171
177 public function getLibLabel()
178 {
179 return $this->label_lib;
180 }
181
187 public function getLibVersion()
188 {
189 return $this->version_lib;
190 }
191
192
193 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
201 public function open_file($file, $outputlangs)
202 {
203 // phpcs:enable
204 global $user, $langs;
205
206 dol_syslog(get_class($this)."::open_file file=".$file);
207 $this->file = $file;
208
209 $ret = 1;
210
211 $outputlangs->load("exports");
212
213 require_once DOL_DOCUMENT_ROOT.'/includes/phpoffice/phpspreadsheet/src/autoloader.php';
214 require_once DOL_DOCUMENT_ROOT.'/includes/Psr/autoloader.php';
215 require_once PHPEXCELNEW_PATH.'Spreadsheet.php';
216
217 if ($this->id == 'excel2007') {
218 if (!class_exists('ZipArchive')) { // For Excel2007, PHPSpreadSheet may need ZipArchive
219 $langs->load("errors");
220 $this->error = $langs->trans('ErrorPHPNeedModule', 'zip');
221 return -1;
222 }
223 }
224
225 $this->workbook = new Spreadsheet();
226 $this->workbook->getProperties()->setCreator($user->getFullName($outputlangs).' - '.DOL_APPLICATION_TITLE.' '.DOL_VERSION);
227 //$this->workbook->getProperties()->setLastModifiedBy('Dolibarr '.DOL_VERSION);
228 $this->workbook->getProperties()->setTitle(basename($file));
229 $this->workbook->getProperties()->setSubject(basename($file));
230 $this->workbook->getProperties()->setDescription(DOL_APPLICATION_TITLE.' '.DOL_VERSION);
231
232 $this->workbook->setActiveSheetIndex(0);
233 $this->workbook->getActiveSheet()->setTitle($outputlangs->trans("Sheet"));
234 $this->workbook->getActiveSheet()->getDefaultRowDimension()->setRowHeight(16);
235
236 return $ret;
237 }
238
239 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
246 public function write_header($outputlangs)
247 {
248 // phpcs:enable
249 //$outputlangs->charset_output='ISO-8859-1'; // Because Excel 5 format is ISO
250
251 return 0;
252 }
253
254
255 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
265 public function write_title($array_export_fields_label, $array_selected_sorted, $outputlangs, $array_types)
266 {
267 // phpcs:enable
268
269 // Create a format for the column headings
270 $this->workbook->getActiveSheet()->getStyle('1')->getFont()->setBold(true);
271 $this->workbook->getActiveSheet()->getStyle('1')->getAlignment()->setHorizontal(PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
272 $selectlabel = array();
273
274 $this->col = 1;
275
276 foreach ($array_selected_sorted as $code => $value) {
277 $alias = $array_export_fields_label[$code];
278 //print "dd".$alias;
279 if (empty($alias)) {
280 dol_syslog('Bad value for field with code='.$code.'. Try to redefine export.', LOG_WARNING);
281 continue;
282 }
283 $typefield = isset($array_types[$code]) ? $array_types[$code] : '';
284
285 if (preg_match('/^Select:/i', $typefield) && $typefield = substr($typefield, 7)) {
286 $selectlabel[$code."_label"] = $alias."_label";
287 }
288 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, $outputlangs->transnoentities($alias));
289 if (!empty($array_types[$code]) && in_array($array_types[$code], array('Date', 'Numeric', 'TextAuto'))) { // Set autowidth for some types
290 $this->workbook->getActiveSheet()->getColumnDimension($this->column2Letter($this->col + 1))->setAutoSize(true);
291 }
292 $this->col++;
293 }
294
295 // Complete with some columns to add columns with the labels of columns of type Select, so we have more then the ID
296 foreach ($selectlabel as $key => $value) {
297 $code = preg_replace('/_label$/', '', $key);
298 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, $outputlangs->transnoentities($value));
299 if (!empty($array_types[$code]) && in_array($array_types[$code], array('Date', 'Numeric', 'TextAuto'))) { // Set autowidth for some types
300 $this->workbook->getActiveSheet()->getColumnDimension($this->column2Letter($this->col + 1))->setAutoSize(true);
301 }
302 $this->col++;
303 }
304
305 $this->row++;
306 return 0;
307 }
308
309 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
319 public function write_record($array_selected_sorted, $objp, $outputlangs, $array_types)
320 {
321 // phpcs:enable
322
323 // Define first row
324 $this->col = 1;
325
326 $reg = array();
327 $selectlabelvalues = array();
328 foreach ($array_selected_sorted as $code => $value) {
329 if (strpos($code, ' as ') == 0) {
330 $alias = str_replace(array('.', '-', '(', ')'), '_', $code);
331 } else {
332 $alias = substr($code, strpos($code, ' as ') + 4);
333 }
334 if (empty($alias)) {
335 dol_syslog('Bad value for field with code='.$code.'. Try to redefine export.', LOG_WARNING);
336 continue;
337 }
338
339 $newvalue = !empty($objp->$alias) ? $objp->$alias : '';
340
341 $newvalue = $this->excel_clean($newvalue);
342 $typefield = isset($array_types[$code]) ? $array_types[$code] : '';
343
344 if (preg_match('/^Select:/i', $typefield) && $typefield = substr($typefield, 7)) {
345 $array = jsonOrUnserialize($typefield);
346 if (is_array($array) && !empty($newvalue)) {
347 $array = $array['options'];
348 $selectlabelvalues[$code."_label"] = $array[$newvalue];
349 } else {
350 $selectlabelvalues[$code."_label"] = "";
351 }
352 }
353
354 // Traduction newvalue
355 if (preg_match('/^\‍((.*)\‍)$/i', $newvalue, $reg)) {
356 $newvalue = $outputlangs->transnoentities($reg[1]);
357 } else {
358 $newvalue = $outputlangs->convToOutputCharset($newvalue);
359 }
360
361 if (preg_match('/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/i', $newvalue)) {
362 $newvalue = dol_stringtotime($newvalue);
363 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($newvalue));
364 $coord = $this->workbook->getActiveSheet()->getCellByColumnAndRow($this->col, $this->row + 1)->getCoordinate();
365 $this->workbook->getActiveSheet()->getStyle($coord)->getNumberFormat()->setFormatCode('yyyy-mm-dd');
366 } elseif (preg_match('/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/i', $newvalue)) {
367 $newvalue = dol_stringtotime($newvalue);
368 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($newvalue));
369 $coord = $this->workbook->getActiveSheet()->getCellByColumnAndRow($this->col, $this->row + 1)->getCoordinate();
370 $this->workbook->getActiveSheet()->getStyle($coord)->getNumberFormat()->setFormatCode('yyyy-mm-dd h:mm:ss');
371 } else {
372 if ($typefield == 'Text' || $typefield == 'TextAuto') {
373 // If $newvalue start with an equal sign we don't want it to be interpreted as a formula, so we add a '. Such transformation should be
374 // done by SetCellValueByColumnAndRow but it is not, so we do it ourself.
375 $newvalue = (dol_substr($newvalue, 0, 1) === '=' ? '\'' : '') . $newvalue;
376 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, $newvalue);
377 $coord = $this->workbook->getActiveSheet()->getCellByColumnAndRow($this->col, $this->row + 1)->getCoordinate();
378 $this->workbook->getActiveSheet()->getStyle($coord)->getNumberFormat()->setFormatCode('@');
379 $this->workbook->getActiveSheet()->getStyle($coord)->getAlignment()->setHorizontal(PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
380 } else {
381 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, $newvalue);
382 }
383 }
384 $this->col++;
385 }
386
387 // Complete with some columns to add columns with the labels of columns of type Select, so we have more then the ID
388 foreach ($selectlabelvalues as $key => $newvalue) {
389 $code = preg_replace('/_label$/', '', $key);
390 $typefield = isset($array_types[$code]) ? $array_types[$code] : '';
391
392 if (preg_match('/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/i', $newvalue)) {
393 $newvalue = dol_stringtotime($newvalue);
394 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($newvalue));
395 $coord = $this->workbook->getActiveSheet()->getCellByColumnAndRow($this->col, $this->row + 1)->getCoordinate();
396 $this->workbook->getActiveSheet()->getStyle($coord)->getNumberFormat()->setFormatCode('yyyy-mm-dd');
397 } elseif (preg_match('/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/i', $newvalue)) {
398 $newvalue = dol_stringtotime($newvalue);
399 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($newvalue));
400 $coord = $this->workbook->getActiveSheet()->getCellByColumnAndRow($this->col, $this->row + 1)->getCoordinate();
401 $this->workbook->getActiveSheet()->getStyle($coord)->getNumberFormat()->setFormatCode('yyyy-mm-dd h:mm:ss');
402 } else {
403 if ($typefield == 'Text' || $typefield == 'TextAuto') {
404 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, (string) $newvalue);
405 $coord = $this->workbook->getActiveSheet()->getCellByColumnAndRow($this->col, $this->row + 1)->getCoordinate();
406 $this->workbook->getActiveSheet()->getStyle($coord)->getNumberFormat()->setFormatCode('@');
407 $this->workbook->getActiveSheet()->getStyle($coord)->getAlignment()->setHorizontal(PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
408 } else {
409 $this->workbook->getActiveSheet()->SetCellValueByColumnAndRow($this->col, $this->row + 1, $newvalue);
410 }
411 }
412 $this->col++;
413 }
414
415 $this->row++;
416 return 0;
417 }
418
419
420 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
427 public function write_footer($outputlangs)
428 {
429 // phpcs:enable
430 return 0;
431 }
432
433
434 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
440 public function close_file()
441 {
442 // phpcs:enable
443
444 $objWriter = new Xlsx($this->workbook);
445 $objWriter->save($this->file);
446 $this->workbook->disconnectWorksheets();
447 unset($this->workbook);
448
449 return 1;
450 }
451
452
453 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
460 public function excel_clean($newvalue)
461 {
462 // phpcs:enable
463 // Rule Dolibarr: No HTML
464 $newvalue = dol_string_nohtmltag($newvalue);
465
466 return $newvalue;
467 }
468
469
476 public function column2Letter($c)
477 {
478 $c = intval($c);
479 if ($c <= 0) {
480 return '';
481 }
482
483 $letter = '';
484 while ($c != 0) {
485 $p = ($c - 1) % 26;
486 $c = intval(($c - $p) / 26);
487 $letter = chr(65 + $p).$letter;
488 }
489
490 return $letter;
491 }
492
501 public function setCellValue($val, $startCell, $endCell = '')
502 {
503 try {
504 $this->workbook->getActiveSheet()->setCellValue($startCell, $val);
505
506 if (!empty($endCell)) {
507 $cellRange = $startCell.':'.$endCell;
508 $this->workbook->getActiveSheet()->mergeCells($startCell.':'.$endCell);
509 } else {
510 $cellRange = $startCell;
511 }
512 if (!empty($this->styleArray)) {
513 $this->workbook->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styleArray);
514 }
515 } catch (Exception $e) {
516 $this->error = $e->getMessage();
517 return -1;
518 }
519 return 1;
520 }
521
529 public function setBorderStyle($thickness, $color)
530 {
531 $this->styleArray['borders'] = array(
532 'outline' => array(
533 'borderStyle' => $thickness,
534 'color' => array('argb' => $color)
535 )
536 );
537 return 1;
538 }
539
547 public function setFontStyle($bold, $color)
548 {
549 $this->styleArray['font'] = array(
550 'color' => array('argb' => $color),
551 'bold' => $bold
552 );
553 return 1;
554 }
555
562 public function setAlignmentStyle($horizontal)
563 {
564 $this->styleArray['alignment'] = array('horizontal' => $horizontal);
565 return 1;
566 }
567
572 public function resetStyle()
573 {
574 $this->styleArray = array();
575 return 1;
576 }
577
586 public function setBlock($startCell, $TDatas = array(), $boldTitle = false)
587 {
588 try {
589 if (!empty($TDatas)) {
590 $startCell = $this->workbook->getActiveSheet()->getCell($startCell);
591 $startColumn = Coordinate::columnIndexFromString($startCell->getColumn());
592 $startRow = $startCell->getRow();
593 foreach ($TDatas as $column => $TRows) {
594 if ($boldTitle) {
595 $this->setFontStyle(true, $this->styleArray['font']['color']['argb']);
596 }
597 $cell = $this->workbook->getActiveSheet()->getCellByColumnAndRow($startColumn, $startRow);
598 $this->setCellValue($column, $cell->getCoordinate());
599 $rowPos = $startRow;
600 if ($boldTitle) {
601 $this->setFontStyle(false, $this->styleArray['font']['color']['argb']);
602 }
603 foreach ($TRows as $row) {
604 $rowPos++;
605 $cell = $this->workbook->getActiveSheet()->getCellByColumnAndRow($startColumn, $rowPos);
606 $this->setCellValue($row, $cell->getCoordinate());
607 }
608 $startColumn++;
609 }
610 }
611 } catch (Exception $e) {
612 $this->error = $e->getMessage();
613 return -1;
614 }
615 return 1;
616 }
617
626 public function setBlock2Columns($startCell, $TDatas = array(), $boldTitle = false)
627 {
628 try {
629 if (!empty($TDatas)) {
630 $startCell = $this->workbook->getActiveSheet()->getCell($startCell);
631 $startColumn = Coordinate::columnIndexFromString($startCell->getColumn());
632 $startRow = $startCell->getRow();
633 foreach ($TDatas as $title => $val) {
634 $cell = $this->workbook->getActiveSheet()->getCellByColumnAndRow($startColumn, $startRow);
635 if ($boldTitle) {
636 $this->setFontStyle(true, $this->styleArray['font']['color']['argb']);
637 }
638 $this->setCellValue($title, $cell->getCoordinate());
639 if ($boldTitle) {
640 $this->setFontStyle(false, $this->styleArray['font']['color']['argb']);
641 }
642 $cell2 = $this->workbook->getActiveSheet()->getCellByColumnAndRow($startColumn + 1, $startRow);
643 $this->setCellValue($val, $cell2->getCoordinate());
644 $startRow++;
645 }
646 }
647 } catch (Exception $e) {
648 $this->error = $e->getMessage();
649 return -1;
650 }
651 return 1;
652 }
653
661 public function enableAutosize($firstColumn, $lastColumn)
662 {
663 foreach (range($firstColumn, $lastColumn) as $columnID) {
664 $this->workbook->getActiveSheet()->getColumnDimension($columnID)->setAutoSize(true);
665 }
666 return 1;
667 }
668
678 public function setMergeCellValueByLength($val, $startCell, $length, $offset = 0)
679 {
680 try {
681 $startCell = $this->workbook->getActiveSheet()->getCell($startCell);
682 $startColumn = Coordinate::columnIndexFromString($startCell->getColumn());
683 if (!empty($offset)) {
684 $startColumn += $offset;
685 }
686
687 $startRow = $startCell->getRow();
688 $startCell = $this->workbook->getActiveSheet()->getCellByColumnAndRow($startColumn, $startRow);
689 $startCoordinate = $startCell->getCoordinate();
690 $this->setCellValue($val, $startCell->getCoordinate());
691
692 $endCell = $this->workbook->getActiveSheet()->getCellByColumnAndRow($startColumn + ($length - 1), $startRow);
693 $endCoordinate = $endCell->getCoordinate();
694 $this->workbook->getActiveSheet()->mergeCells($startCoordinate.':'.$endCoordinate);
695 } catch (Exception $e) {
696 $this->error = $e->getMessage();
697 return -1;
698 }
699 return $endCoordinate;
700 }
701}
Class to build export files with Excel format.
write_record($array_selected_sorted, $objp, $outputlangs, $array_types)
Output record line into file.
setBlock2Columns($startCell, $TDatas=array(), $boldTitle=false)
Make a 2xN Tab in Sheet.
getDriverVersion()
getDriverVersion
setCellValue($val, $startCell, $endCell='')
Set cell value and automatically merge if we give an endcell.
setMergeCellValueByLength($val, $startCell, $length, $offset=0)
Set a value cell and merging it by giving a starting cell and a length.
close_file()
Close Excel file.
write_footer($outputlangs)
Write footer.
column2Letter($c)
Convert a column to letter (1->A, 0->B, 27->AA, ...)
getDriverExtension()
getDriverExtension
getDriverLabelBis()
getDriverLabel
__construct($db)
Constructor.
setFontStyle($bold, $color)
Set font style.
setBlock($startCell, $TDatas=array(), $boldTitle=false)
Make a NxN Block in sheet.
open_file($file, $outputlangs)
Open output file.
write_title($array_export_fields_label, $array_selected_sorted, $outputlangs, $array_types)
Output title line into file.
enableAutosize($firstColumn, $lastColumn)
Enable auto sizing for column range.
setAlignmentStyle($horizontal)
Set alignment style (horizontal, left, right, ...)
write_header($outputlangs)
Write header.
excel_clean($newvalue)
Clean a cell to respect rules of Excel file cells.
setBorderStyle($thickness, $color)
Set border style.
Parent class for export modules.
dol_stringtotime($string, $gm=1)
Convert a string date into a GM Timestamps date Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not s...
Definition date.lib.php:427
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
jsonOrUnserialize($stringtodecode)
Decode an encode string.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
a disabled