diff --git a/composer.json b/composer.json index db1e73412..2f935b316 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ ], "require": { "php": ">=8.2", + "mk-j/php_xlsxwriter": "^0.39.0", "nette/application": "^3.2.0", "nette/di": "^3.0.0", "nette/forms": "^3.2.0", diff --git a/src/Datagrid.php b/src/Datagrid.php index 4eff3ed26..a47adf9df 100644 --- a/src/Datagrid.php +++ b/src/Datagrid.php @@ -21,6 +21,7 @@ use Contributte\Datagrid\Exception\DatagridHasToBeAttachedToPresenterComponentException; use Contributte\Datagrid\Export\Export; use Contributte\Datagrid\Export\ExportCsv; +use Contributte\Datagrid\Export\ExportExcel; use Contributte\Datagrid\Filter\Filter; use Contributte\Datagrid\Filter\FilterDate; use Contributte\Datagrid\Filter\FilterDateRange; @@ -1581,6 +1582,19 @@ public function addExportCsv( return $exportCsv; } + public function addExportExcel( + string $text, + string $fileName, + bool $filtered = false + ): ExportExcel + { + $exportExcel = new ExportExcel($this, $text, $fileName, $filtered); + + $this->addToExports($exportExcel); + + return $exportExcel; + } + public function resetExportsLinks(): void { foreach ($this->exports as $id => $export) { @@ -1858,7 +1872,7 @@ public function handleExport(mixed $id): void $rows[] = new Row($this, $item, $this->getPrimaryKey()); } - if ($export instanceof ExportCsv) { + if ($export instanceof ExportCsv || $export instanceof ExportExcel) { $export->invoke($rows); } else { $export->invoke($items); diff --git a/src/ExcelDataModel.php b/src/ExcelDataModel.php new file mode 100644 index 000000000..93d41ac36 --- /dev/null +++ b/src/ExcelDataModel.php @@ -0,0 +1,61 @@ +getHeader(); + } + + foreach ($this->data as $item) { + $return[] = $this->getRow($item); + } + + return $return; + } + + public function getHeader(): array + { + $header = []; + + foreach ($this->columns as $column) { + $header[] = $this->translator->translate($column->getName()); + } + + return $header; + } + + /** + * Get item values saved into row + */ + public function getRow(mixed $item): array + { + $row = []; + + foreach ($this->columns as $column) { + $row[] = strip_tags((string) $column->render($item)); + } + + return $row; + } + +} diff --git a/src/Export/ExportExcel.php b/src/Export/ExportExcel.php new file mode 100644 index 000000000..28d6262f6 --- /dev/null +++ b/src/Export/ExportExcel.php @@ -0,0 +1,49 @@ +getExportCallback($name), + $filtered + ); + } + + private function getExportCallback(string $name): callable + { + return function ( + array $data, + Datagrid $grid + ) use ($name): void { + $columns = $this->getColumns(); + + if ($columns === []) { + $columns = $this->grid->getColumns(); + } + + $excelDataModel = new ExcelDataModel($data, $columns, $this->grid->getTranslator()); + + $this->grid->getPresenter()->sendResponse(new ExcelResponse($excelDataModel->getSimpleData(), $name)); + }; + } + +} diff --git a/src/Response/ExcelResponse.php b/src/Response/ExcelResponse.php new file mode 100644 index 000000000..bf9a311a7 --- /dev/null +++ b/src/Response/ExcelResponse.php @@ -0,0 +1,78 @@ +> */ + protected array $data; + + protected string $name; + + /** @var string[] */ + protected array $headers = [ + 'Expires' => '0', + 'Cache-Control' => 'no-cache', + 'Pragma' => 'Public', + ]; + + /** + * @param array> $data Input data + */ + public function __construct( + array $data, + string $name = 'export.xlsx', + ) + { + if (!str_contains($name, '.xlsx')) { + $name = sprintf('%s.xlsx', $name); + } + + $this->name = $name; + $this->data = $data; + } + + public function send(HttpRequest $httpRequest, HttpResponse $httpResponse): void + { + // Disable tracy bar + if (class_exists(Debugger::class)) { + Debugger::$productionMode = true; + } + + // Set Content-Type header + $httpResponse->setContentType(self::CONTENT_TYPE); + + // Set Content-Disposition header + $httpResponse->setHeader('Content-Disposition', sprintf('attachment; filename="%s"', $this->name)); + + // Set other headers + foreach ($this->headers as $key => $value) { + $httpResponse->setHeader($key, $value); + } + + if (function_exists('ob_start')) { + ob_start(); + } + + $writer = new XLSXWriter(); + $writer->writeSheet($this->data); + $writer->writeToStdOut(); + + if (function_exists('ob_end_flush')) { + ob_end_flush(); + } + } + +} diff --git a/tests/Cases/FilterTest.phpt b/tests/Cases/FilterTest.phpt index ba6bbed2f..841ef9e9a 100644 --- a/tests/Cases/FilterTest.phpt +++ b/tests/Cases/FilterTest.phpt @@ -35,7 +35,7 @@ final class FilterTest extends TestCase public function testFilterSubmitWithInvalidInlineAddOpen(): void { $factory = new TestingDataGridFactoryRouter(); - /** @var \Ublaboo\DataGrid\Datagrid $grid */ + /** @var Datagrid $grid */ $grid = $factory->createTestingDataGrid()->getComponent('grid'); $grid->addColumnText('status', 'Status');