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;
 | |
| 	}
 | |
| }
 | |
| 
 |