diff --git a/ExcelDataReader.php b/ExcelDataReader.php index 80f30f5..2579ac2 100644 --- a/ExcelDataReader.php +++ b/ExcelDataReader.php @@ -2,7 +2,7 @@ namespace alexgx\phpexcel; -class ExcelDataReader extends \yii\base\Object +class ExcelDataReader extends \yii\base\BaseObject { // TODO: implement excel to yii model reader // - key (pk) param diff --git a/ExcelDataWriter.php b/ExcelDataWriter.php index 9a2ba44..8e52672 100644 --- a/ExcelDataWriter.php +++ b/ExcelDataWriter.php @@ -2,12 +2,20 @@ namespace alexgx\phpexcel; +use Closure; +use DateTime; +use DateTimeZone; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use yii\base\BaseObject; use yii\helpers\ArrayHelper; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; -class ExcelDataWriter extends \yii\base\Object +class ExcelDataWriter extends BaseObject { /** - * @var \PHPExcel_Worksheet + * @var Worksheet */ public $sheet; @@ -29,21 +37,73 @@ class ExcelDataWriter extends \yii\base\Object /** * @var string */ - public $defaultDateFormat = \PHPExcel_Style_NumberFormat::FORMAT_DATE_DDMMYYYY; + public $defaultDateFormat = NumberFormat::FORMAT_DATE_DDMMYYYY; /** - * @var int Default value is `1` + * @var int Start row Default value is `1` */ protected $j = 1; + /** + * @var int + */ + protected $startRow = 1; + + /** + * @var int start column + */ + protected $startColumn = 1; + + /** + * @var int + */ + protected $endColumn = 1; + + /** + * @var bool + */ + protected $freezeHeader = false; + + /** + * @var bool + */ + protected $autoFilter = false; + + + + /** + * @param bool $freezeHeader + */ + public function setFreezeHeader($freezeHeader) + { + $this->freezeHeader = $freezeHeader; + } + + + + /** + * @param int $startRow + */ + public function setStartRow($startRow){ + $this->j = $startRow; + $this->startRow = $startRow; + } + + /** + * @param int $startColumn + */ + public function setStartColumn($startColumn){ + $this->startColumn = $startColumn; + } + + + public function write() { if (!is_array($this->data) || !is_array($this->columns)) { return; } - $this->j = 1; - $this->writeHeaderRow(); $this->writeDataRows(); $this->writeFooterRow(); @@ -51,15 +111,22 @@ public function write() protected function writeHeaderRow() { - $i = 0; + + if($this->freezeHeader){ + $startColumnAsString = Coordinate::stringFromColumnIndex($this->startColumn); + $this->sheet->freezePane($startColumnAsString . ($this->j+1)); + } + $i = $this->startColumn; foreach ($this->columns as $column) { + $this->endColumn = $i + 1; if (isset($column['header'])) { $this->sheet->setCellValueByColumnAndRow($i, $this->j, $column['header']); } if (isset($column['headerStyles'])) { $this->sheet->getStyleByColumnAndRow($i, $this->j)->applyFromArray($column['headerStyles']); } - if (isset($column['width'])) { + + if (isset($column['width']) && $column['width'] !== 'autosize') { $this->sheet->getColumnDimensionByColumn($i)->setWidth($column['width']); } ++$i; @@ -70,34 +137,39 @@ protected function writeHeaderRow() protected function writeDataRows() { foreach ($this->data as $key => $row) { - $i = 0; - if (isset($this->options['rowOptions']) && $this->options['rowOptions'] instanceof \Closure) { + $i = $this->startColumn; + if (isset($this->options['rowOptions']) && $this->options['rowOptions'] instanceof Closure) { $rowOptions = call_user_func($this->options['rowOptions'], $row, $key); } foreach ($this->columns as $column) { if (isset($rowOptions)) { $column = ArrayHelper::merge($column, $rowOptions); } - if (isset($column['cellOptions']) && $column['cellOptions'] instanceof \Closure) { + if (isset($column['cellOptions']) && $column['cellOptions'] instanceof Closure) { $column = ArrayHelper::merge($column, call_user_func($column['cellOptions'], $row, $key, $i, $this->j)); } $value = null; if (isset($column['value'])) { - $value = ($column['value'] instanceof \Closure) ? call_user_func($column['value'], $row, $key) : $column['value']; + $value = ($column['value'] instanceof Closure) ? call_user_func($column['value'], $row, $key) : $column['value']; } elseif (isset($column['attribute']) && isset($row[$column['attribute']])) { $value = $row[$column['attribute']]; } + $this->writeCell($value, $i, $this->j, $column); ++$i; } + ++$this->j; } } protected function writeFooterRow() { - $i = 0; + $i = $this->startColumn; foreach ($this->columns as $column) { + if (isset($column['width']) && $column['width'] === 'autosize') { + $this->sheet->getColumnDimensionByColumn($this->j)->setAutoSize(true); + } // footer config $config = []; if (isset($column['footerStyles'])) { @@ -109,16 +181,19 @@ protected function writeFooterRow() if (isset($column['footerLabel'])) { $config['label'] = $column['footerLabel']; } - if (isset($column['footerOptions']) && $column['footerOptions'] instanceof \Closure) { + if (isset($column['footerOptions']) && $column['footerOptions'] instanceof Closure) { $config = ArrayHelper::merge($config, call_user_func($column['footerOptions'], null, null, $i, $this->j)); } $value = null; if (isset($column['footer'])) { - $value = ($column['footer'] instanceof \Closure) ? call_user_func($column['footer'], null, null) : $column['footer']; + $value = ($column['footer'] instanceof Closure) ? call_user_func($column['footer'], null, null) : $column['footer']; } $this->writeCell($value, $i, $this->j, $config); + + ++$i; } + ++$this->j; } @@ -128,14 +203,47 @@ protected function writeCell($value, $column, $row, $config) if (!isset($config['type']) || $config['type'] === null) { $this->sheet->setCellValueByColumnAndRow($column, $row, $value); } elseif ($config['type'] === 'date') { - $timestamp = !is_int($value) ? strtotime($value) : $value; - $this->sheet->SetCellValueByColumnAndRow($column, $row, \PHPExcel_Shared_Date::PHPToExcel($timestamp)); + $timestamp = false; + if(is_int($value)){ + $timestamp = $value; + } + $gmt = new DateTimeZone('GMT'); + if(!$timestamp){ + //, new \DateTimeZone('Europe/London') + if($dt = DateTime::createFromFormat('Y-m-d H:i:s',$value, $gmt)){ + $dt->setTimeZone($gmt); + $timestamp = $dt->getTimestamp(); + } + } + if(!$timestamp){ + if($dt = DateTime::createFromFormat('Y-m-d',$value,$gmt)){ + $dt->setTimeZone($gmt); + $timestamp = $dt->getTimestamp(); + } + } + if(!$timestamp){ + $timestamp = strtotime($value); + } + + $this->sheet + ->getStyleByColumnAndRow($column, $row) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); + + + $this->sheet->setCellValueByColumnAndRow($column, $row, Date::PHPToExcel($timestamp)); if (!isset($config['styles']['numberformat']['code'])) { $config['styles']['numberformat']['code'] = $this->defaultDateFormat; } + + $this->sheet + ->getStyleByColumnAndRow($column, $row) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY); + } elseif ($config['type'] === 'url') { if (isset($config['label'])) { - if ($config['label'] instanceof \Closure) { + if ($config['label'] instanceof Closure) { // NOTE: calculate label on top level $label = call_user_func($config['label']/*, TODO */); } else { @@ -148,16 +256,32 @@ protected function writeCell($value, $column, $row, $config) if (!$urlValid) { $label = ''; } + $this->sheet->setCellValueByColumnAndRow($column, $row, $label); + if ($urlValid) { $this->sheet->getCellByColumnAndRow($column, $row)->getHyperlink()->setUrl($value); } } else { $this->sheet->setCellValueExplicitByColumnAndRow($column, $row, $value, $config['type']); } + if (isset($config['styles'])) { $this->sheet->getStyleByColumnAndRow($column, $row)->applyFromArray($config['styles']); } + + if (isset($config['format']['decimal'])) { + if($config['format']['decimal'] === 0){ + $numberFormat = NumberFormat::FORMAT_NUMBER; + }else{ + $numberFormat = '0.' . str_repeat('0',$config['format']['decimal']); + } + $this->sheet + ->getStyleByColumnAndRow($column, $row) + ->getNumberFormat() + ->setFormatCode($numberFormat); + } + } /** diff --git a/ExcelTemplateWriter.php b/ExcelTemplateWriter.php index c108090..889dc29 100644 --- a/ExcelTemplateWriter.php +++ b/ExcelTemplateWriter.php @@ -2,7 +2,7 @@ namespace alexgx\phpexcel; -class ExcelTemplateWriter extends \yii\base\Object +class ExcelTemplateWriter extends \yii\base\BaseObject { // TODO: implement excel template writer } \ No newline at end of file diff --git a/PhpExcel.php b/PhpExcel.php index 1b09d9c..d703f33 100644 --- a/PhpExcel.php +++ b/PhpExcel.php @@ -5,7 +5,7 @@ /** * Class PhpExcel */ -class PhpExcel extends \yii\base\Object +class PhpExcel extends \yii\base\BaseObject { /** * @var string @@ -14,41 +14,43 @@ class PhpExcel extends \yii\base\Object /** * Creates new workbook - * @return \PHPExcel + * @return \PhpOffice\PhpSpreadsheet\Spreadsheet */ public function create() { - return new \PHPExcel(); + return new \PhpOffice\PhpSpreadsheet\Spreadsheet(); } /** * Creates new Worksheet Drawing - * @return \PHPExcel_Worksheet_Drawing + * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing */ public function getObjDrawing() { - return new \PHPExcel_Worksheet_Drawing(); + return new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); } /** * @param string $filename name of the spreadsheet file - * @return \PHPExcel + * @return \PhpOffice\PhpSpreadsheet\Spreadsheet */ public function load($filename) { - return \PHPExcel_IOFactory::load($filename); + return \PhpOffice\PhpSpreadsheet\IOFactory::load($filename); } /** - * @param \PHPExcel $object + * @param \PhpOffice\PhpSpreadsheet\Spreadsheet $object * @param string $name attachment name * @param string $format output format */ - public function responseFile(\PHPExcel $object, $filename, $format = null) + public function responseFile(\PhpOffice\PhpSpreadsheet\Spreadsheet $object, $filename, $format = null, $beautifyFileName = true) { if ($format === null) { $format = $this->resolveFormat($filename); } - $writer = \PHPExcel_IOFactory::createWriter($object, $format); + + $filename = $this->filterFilename($filename, $beautifyFileName); + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($object, $format); ob_start(); $writer->save('php://output'); $content = ob_get_clean(); @@ -106,15 +108,73 @@ protected function resolveFormat($filename) { // see IOFactory::createReaderForFile etc. $list = [ - 'ods' => 'OpenDocument', - 'xls' => 'Excel5', - 'xlsx' => 'Excel2007', - 'csv' => 'CSV', - 'pdf' => 'PDF', - 'html' => 'HTML', + 'ods' => 'Ods', + 'xls' => 'Xls', + 'xlsx' => 'Xlsx', + 'csv' => 'Csv', + 'pdf' => 'Pdf', + 'html' => 'Html', ]; // TODO: check strtolower $extension = pathinfo($filename, PATHINFO_EXTENSION); return isset($list[$extension]) ? $list[$extension] : $this->defaultFormat; } + + /** + * sanitise file name + * + * @param string $filename + * @param bool $beautify + * @return string + */ + public function filterFilename($filename, $beautify = true) + { + // sanitize filename + $filename = preg_replace( + '~ + [<>:"/\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words + [\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + [\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN + [#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2 + [{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt + ~x', + '-', $filename); + // avoids ".", ".." or ".hiddenFiles" + $filename = ltrim($filename, '.-'); + // optional beautification + if ($beautify) $filename = $this->beautifyFilename($filename); + // maximise filename length to 255 bytes http://serverfault.com/a/9548/44086 + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $filename = mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext ? '.' . $ext : ''); + return $filename; + } + + /** + * + * @param string $filename + * @return string + */ + function beautifyFilename($filename) + { + // reduce consecutive characters + $filename = preg_replace(array( + // "file name.zip" becomes "file-name.zip" + '/ +/', + // "file___name.zip" becomes "file-name.zip" + '/_+/', + // "file---name.zip" becomes "file-name.zip" + '/-+/' + ), '-', $filename); + $filename = preg_replace(array( + // "file--.--.-.--name.zip" becomes "file.name.zip" + '/-*\.-*/', + // "file...name..zip" becomes "file.name.zip" + '/\.{2,}/' + ), '.', $filename); + // lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 + $filename = mb_strtolower($filename, mb_detect_encoding($filename)); + // ".file-name.-" becomes "file-name" + $filename = trim($filename, '.-'); + return $filename; + } } diff --git a/README.md b/README.md index 3957565..28ecb86 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -PHPExcel extension for Yii2 +PhpSpreadsheet extension for Yii2 ============= [![Latest Stable Version](https://poser.pugx.org/alexgx/yii2-phpexcel/v/stable.svg)](https://packagist.org/packages/alexgx/yii2-phpexcel) [![Total Downloads](https://poser.pugx.org/alexgx/yii2-phpexcel/downloads.svg)](https://packagist.org/packages/alexgx/yii2-phpexcel) [![Latest Unstable Version](https://poser.pugx.org/alexgx/yii2-phpexcel/v/unstable.svg)](https://packagist.org/packages/alexgx/yii2-phpexcel) [![License](https://poser.pugx.org/alexgx/yii2-phpexcel/license.svg)](https://packagist.org/packages/alexgx/yii2-phpexcel) @@ -29,13 +29,75 @@ TDB Usage ----- -``` +```php + +use alexgx\phpexcel\PhpExcel; + +$phpExcel = new PhpExcel(); +$objPHPExcel = $phpExcel->create(); + +$objPHPExcel->getProperties()->setCreator("Traiding") + ->setLastModifiedBy("Uldis Nelsons") + ->setTitle("Packing list") + ->setSubject("Packing list"); + +$activeSheet = $objPHPExcel->setActiveSheetIndex(0); +$activeSheet->setTitle('Packing List') + ->setCellValue('A1', 'PACKING LIST') + ->setCellValue('A3', 'VESSEL:') + ->setCellValue('A4', 'B/L date:') + ->setCellValue('A5', 'B/L No.:'); + + + +$activeSheet->getStyle('A1')->getFont()->setBold(true)->setSize(14); +$activeSheet->getStyle('A3:c9')->getFont()->setBold(true)->setSize(11); + +$writer = new ExcelDataWriter(); +$writer->setStartRow(11); +$writer->sheet = $activeSheet; +$writer->data = $packingList; + +$headerStyles = [ + 'font' => [ + 'bold' => true + ] +]; +$writer->columns = [ + [ + 'attribute' => 'departure_date', + 'header' => 'Departure Date', + 'headerStyles' => $headerStyles, + ], + [ + 'attribute' => 'car_number', + 'header' => 'Car Number', + 'headerStyles' => $headerStyles, + ], + [ + 'attribute' => 'delivery_note', + 'header' => 'Delivery Note', + 'headerStyles' => $headerStyles, + ], + [ + 'attribute' => 'weight', + 'header' => 'Weight', + 'headerStyles' => $headerStyles, + ], + [ + 'attribute' => 'gtd', + 'header' => 'Gtd', + 'headerStyles' => $headerStyles, + ] +]; + +$writer->write(); +$phpExcel->responseFile($objPHPExcel, 'packing.xls'); -use alexgx\phpexcel; ``` TBD Further Information ------------------- -Please, check the [PHPOffice/PHPExcel github repo](https://github.com/PHPOffice/PHPExcel) documentation for further information about its configuration options. +Please, check the [PHPOffice/PhpSpreadsheet github repo](https://github.com/PHPOffice/phpspreadsheet) documentation for further information about its configuration options. diff --git a/composer.json b/composer.json index b2780b3..60b0dc4 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require": { "yiisoft/yii2": "*", - "phpoffice/phpexcel": "1.8.1" + "phpoffice/phpspreadsheet": "*" }, "autoload": { "psr-4": {