datatable.directory codebase
https://datatable.directory/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
10 KiB
415 lines
10 KiB
<?php
|
|
|
|
namespace MightyPork\Utils;
|
|
use Lang;
|
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
|
|
|
|
// this requires "phpoffice/phpspreadsheet": "^1.3",
|
|
|
|
/**
|
|
* Abstraction above PhpOffice's spreadsheets with stable API.
|
|
* Fixing incompatibilities in one place will fix them in the entire application.
|
|
*/
|
|
class SpreadsheetBuilder
|
|
{
|
|
private $activeSheetNum = -1;
|
|
private $sheetCount = 0;
|
|
|
|
/** @var Spreadsheet Spreadsheet */
|
|
private $book = null;
|
|
|
|
/** @var Worksheet */
|
|
private $activeSheet = null;
|
|
|
|
/** @var string - document title */
|
|
private $title;
|
|
|
|
public function __construct($title, $subject = '', $creator='PhpOffice Spreadsheet')
|
|
{
|
|
$this->title = $title;
|
|
$this->book = new Spreadsheet();
|
|
|
|
$this->book->getProperties()
|
|
->setCreator($creator)
|
|
->setTitle($title)
|
|
->setSubject($subject)
|
|
->setDescription('Created ' . date('r'));
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getTitle()
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
/**
|
|
* Add a sheet and select it as active.
|
|
* Returns sheet index.
|
|
*
|
|
* @param $label - sheet label
|
|
* @return int - new sheet number, 0-based
|
|
*/
|
|
public function addSheet($label)
|
|
{
|
|
if ($this->activeSheetNum == -1) {
|
|
// PhpOffice workbook has one sheet by default,
|
|
// we require user to 'add' it before using it
|
|
$this->sheetCount = 1;
|
|
$this->activeSheet = $this->book->setActiveSheetIndex(0);
|
|
$this->activeSheetNum = 0;
|
|
}
|
|
else {
|
|
$this->sheetCount++;
|
|
$this->activeSheet = $this->book->addSheet(new Worksheet());
|
|
$this->activeSheetNum = $this->sheetCount-1;
|
|
}
|
|
|
|
$this->activeSheet->setTitle($label);
|
|
return $this->activeSheetNum;
|
|
}
|
|
|
|
/**
|
|
* Select a sheet for cell modifications
|
|
*
|
|
* @param int $number - sheet index, 0-based
|
|
*/
|
|
public function selectSheet($number)
|
|
{
|
|
$this->activeSheet = $this->book->setActiveSheetIndex($number);
|
|
$this->activeSheetNum = $number;
|
|
|
|
}
|
|
|
|
/**
|
|
* Set a cell value in the current sheet
|
|
*
|
|
* @param int $row - 0-based row
|
|
* @param int $column - 0-based column
|
|
* @param mixed $value - value to set
|
|
*/
|
|
public function setValue($row, $column, $value)
|
|
{
|
|
$this->activeSheet->setCellValueByColumnAndRow($column+1, $row+1, $value);
|
|
}
|
|
|
|
/**
|
|
* Set a row of values
|
|
*
|
|
* @param int $row - 0-based row
|
|
* @param int $startcol - first column to fill
|
|
* @param array $values - array of values to write into the row
|
|
*/
|
|
public function setRow($row, $startcol, $values)
|
|
{
|
|
$i = 0;
|
|
foreach ($values as $value) {
|
|
$this->setValue($row, $startcol + $i, $value);
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a row of values
|
|
*
|
|
* @param int $startrow - 0-based start row
|
|
* @param int $startcol - 0-based start column
|
|
* @param array $values - array of row vectors ([ [first,row], [second,row], ...]
|
|
*/
|
|
public function setGrid($startrow, $startcol, $values)
|
|
{
|
|
foreach ($values as $row => $row_values) {
|
|
$this->setRow($startrow + $row, $startcol, $row_values);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make a cell a given style
|
|
*
|
|
* @param int $row - 0-based row
|
|
* @param int $column - 0-based column
|
|
*/
|
|
private function setStyle($row, $column, $array)
|
|
{
|
|
$coord = Coordinate::stringFromColumnIndex($column+1) . ($row+1);
|
|
|
|
$this->activeSheet->getStyle($coord)
|
|
->applyFromArray($array);
|
|
}
|
|
|
|
/**
|
|
* Make an area of cells a given style
|
|
*
|
|
* @param $startrow - 0-based first row
|
|
* @param $startcol - 0-based first column
|
|
* @param $rows - number of rows
|
|
* @param $cols - number of columns
|
|
*/
|
|
private function setStyleArea($startrow, $startcol, $rows, $cols, $array)
|
|
{
|
|
$coord = Coordinate::stringFromColumnIndex($startcol+1) . ($startrow+1) .
|
|
':' . Coordinate::stringFromColumnIndex($startcol+$cols) . ($startrow+$rows);
|
|
|
|
$this->activeSheet->getStyle($coord)
|
|
->applyFromArray($array);
|
|
}
|
|
|
|
/**
|
|
* Make a cell bold
|
|
*
|
|
* @param int $row - 0-based row
|
|
* @param int $column - 0-based column
|
|
*/
|
|
public function setBold($row, $column)
|
|
{
|
|
$this->setStyle($row, $column, ['font' => ['bold' => 'true']]);
|
|
}
|
|
|
|
/**
|
|
* Make an area of cells bold
|
|
*
|
|
* @param $startrow - 0-based first row
|
|
* @param $startcol - 0-based first column
|
|
* @param $rows - number of rows
|
|
* @param $cols - number of columns
|
|
*/
|
|
public function setBoldArea($startrow, $startcol, $rows, $cols)
|
|
{
|
|
$this->setStyleArea($startrow, $startcol, $rows, $cols, ['font' => ['bold' => 'true']]);
|
|
}
|
|
|
|
/**
|
|
* Make a cell italic
|
|
*
|
|
* @param int $row - 0-based row
|
|
* @param int $column - 0-based column
|
|
*/
|
|
public function setItalic($row, $column)
|
|
{
|
|
$this->setStyle($row, $column, ['font' => ['italic' => 'true']]);
|
|
}
|
|
|
|
/**
|
|
* Make an area of cells italic
|
|
*
|
|
* @param $startrow - 0-based first row
|
|
* @param $startcol - 0-based first column
|
|
* @param $rows - number of rows
|
|
* @param $cols - number of columns
|
|
*/
|
|
public function setItalicArea($startrow, $startcol, $rows, $cols)
|
|
{
|
|
$this->setStyleArea($startrow, $startcol, $rows, $cols, ['font' => ['italic' => 'true']]);
|
|
}
|
|
|
|
private function convertColor($color)
|
|
{
|
|
if ($color[0] == '#') $rgb = substr($color, 1);
|
|
else {
|
|
$map = [
|
|
'black' => '000000',
|
|
'gray' => '808080',
|
|
'silver' => 'C0C0C0',
|
|
'white' => 'FFFFFF',
|
|
'maroon' => '800000',
|
|
'red' => 'FF0000',
|
|
'olive' => '808000',
|
|
'yellow' => 'FFFF00',
|
|
'green' => '008000',
|
|
'lime' => '00FF00',
|
|
'teal' => '008080',
|
|
'aqua' => '00FFFF',
|
|
'navy' => '000080',
|
|
'blue' => '0000FF',
|
|
'purple' => '800080',
|
|
'fuchsia' => 'FF00FF',
|
|
];
|
|
|
|
if (isset($map[$color])) {
|
|
$rgb = $map[$color];
|
|
} else {
|
|
throw new \LogicException("Bad color format $color");
|
|
}
|
|
}
|
|
|
|
return ['rgb' => $rgb];
|
|
}
|
|
|
|
/**
|
|
* Set cell colors
|
|
*
|
|
* @param int $row - 0-based row
|
|
* @param int $column - 0-based column
|
|
* @param string $text - foreground
|
|
* @param string $background - background
|
|
*/
|
|
public function setColors($row, $column, $text = null, $background = null)
|
|
{
|
|
$this->setColorsArea($row, $column, 1, 1, $text, $background);
|
|
}
|
|
|
|
/**
|
|
* Set colors of an area of cells
|
|
*
|
|
* @param $startrow - 0-based first row
|
|
* @param $startcol - 0-based first column
|
|
* @param $rows - number of rows
|
|
* @param $cols - number of columns
|
|
* @param string $text - foreground
|
|
* @param string $background - background
|
|
*/
|
|
public function setColorsArea($startrow, $startcol, $rows, $cols, $text = null, $background = null)
|
|
{
|
|
$ar = [];
|
|
|
|
if ($text !== null) {
|
|
$ar['font'] = [
|
|
'color' => $this->convertColor($text),
|
|
];
|
|
}
|
|
|
|
if ($background !== null) {
|
|
$ar['fill'] = [
|
|
'fillType' => Fill::FILL_SOLID,
|
|
'color' => $this->convertColor($background),
|
|
];
|
|
}
|
|
|
|
$this->setStyleArea($startrow, $startcol, $rows, $cols, $ar);
|
|
}
|
|
|
|
/**
|
|
* Merge cells
|
|
*
|
|
* @param $startrow - 0-based first row
|
|
* @param $startcol - 0-based first column
|
|
* @param $rows - number of rows
|
|
* @param $cols - number of columns
|
|
*/
|
|
public function mergeCells($startrow, $startcol, $rows, $cols)
|
|
{
|
|
$coord = Coordinate::stringFromColumnIndex($startcol+1) . ($startrow+1) .
|
|
':' . Coordinate::stringFromColumnIndex($startcol+$cols) . ($startrow+$rows);
|
|
|
|
$this->activeSheet->mergeCells($coord);
|
|
}
|
|
|
|
/**
|
|
* Merge row cells
|
|
*
|
|
* @param $startrow - 0-based first row
|
|
* @param $startcol - 0-based first column
|
|
* @param $rows - number of rows
|
|
* @param $cols - number of columns
|
|
*/
|
|
public function mergeRowCells($startrow, $startcol, $cols)
|
|
{
|
|
$this->mergeCells($startrow, $startcol, 1, $cols);
|
|
}
|
|
|
|
/**
|
|
* Set a column's width
|
|
*
|
|
* @param int $column - 0-based column number
|
|
* @param int $width - width in pixels
|
|
*/
|
|
public function setColumnWidth($column, $width)
|
|
{
|
|
$this->activeSheet->getColumnDimensionByColumn($column+1)->setWidth($width);
|
|
}
|
|
|
|
/**
|
|
* Prepare writer and resolve mime type for export
|
|
*
|
|
* @param string $format - one of : xls, xlsx, csv, ods
|
|
* @return array (mime, writer)
|
|
*/
|
|
private function prepareExport($format = 'xlsx')
|
|
{
|
|
switch ($format) {
|
|
case 'xls':
|
|
$writerName = 'Xls';
|
|
$mimeType = 'application/vnd.ms-excel';
|
|
break;
|
|
|
|
case 'xlsx':
|
|
$writerName = 'Xlsx';
|
|
$mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
break;
|
|
|
|
case 'ods':
|
|
$writerName = 'Ods';
|
|
$mimeType = 'application/vnd.oasis.opendocument.spreadsheet';
|
|
break;
|
|
|
|
default:
|
|
case 'csv':
|
|
$writerName = 'Csv';
|
|
$mimeType = 'text/csv';
|
|
break;
|
|
}
|
|
|
|
// Set active sheet index to the first sheet, so Excel opens this as the first sheet
|
|
$this->book->setActiveSheetIndex(0);
|
|
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($this->book, $writerName);
|
|
|
|
return [$mimeType, $writer];
|
|
}
|
|
|
|
/**
|
|
* Export and send to browser as a response for user to download
|
|
*
|
|
* @param string $base_filename - filename without suffix
|
|
* @param string $format - one of : xls, xlsx, csv, ods
|
|
*/
|
|
public function exportToBrowser($base_filename, $format = 'xlsx')
|
|
{
|
|
list($mimeType, $writer) = $this->prepareExport($format);
|
|
|
|
/** @var IWriter $writer */
|
|
$base_filename .= '.' . $format;
|
|
|
|
ob_end_clean();
|
|
|
|
// Redirect output to a client’s web browser (Xlsx)
|
|
header("Content-Type: $mimeType; charset=utf-8");
|
|
header("Content-Disposition: attachment;filename=\"$base_filename\"");
|
|
header('Content-Language: ' . Lang::locale());
|
|
|
|
// Cache headers
|
|
|
|
header('Cache-Control: max-age=0');
|
|
// IE9
|
|
header('Cache-Control: max-age=1');
|
|
// Other IE headers
|
|
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
|
|
header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
|
|
header('Pragma: no-cache'); // HTTP/1.0
|
|
|
|
$writer->save('php://output');
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Export and send to browser as a response for user to download
|
|
*
|
|
* @param string $base_filename - filename without suffix
|
|
* @param string $format - one of : xls, xlsx, csv, ods
|
|
* @return string - the mime type
|
|
*/
|
|
public function exportToFile($base_filename, $format = 'xlsx', $path='/tmp')
|
|
{
|
|
list($mimeType, $writer) = $this->prepareExport($format);
|
|
|
|
/** @var IWriter $writer */
|
|
$writer->save($path . DIRECTORY_SEPARATOR . "$base_filename.$format");
|
|
|
|
return $mimeType;
|
|
}
|
|
}
|
|
|