mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 10:12:41 +01:00
Support Excel as a data export format
Summary: Depends on D18954. Ref T13049. This brings over the existing Maniphest Excel export pipeline in a generic way. The `<Type>ExportField` classes know directly that `PHPExcel` exists, which is a little sketchy, but writing an Excel indirection layer sounds like a lot of work and I don't anticipate us changing Excel backends anytime soon, so trying to abstract this feels YAGNI. This doesn't bring over the install instructions for PHPExcel or the detection of whether or not it exists. I'll bring that over in a future change. Test Plan: Exported users as Excel, opened them up, got a sensible-looking Excel sheet. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13049 Differential Revision: https://secure.phabricator.com/D18955
This commit is contained in:
parent
a067f64ebb
commit
0409279595
9 changed files with 223 additions and 3 deletions
|
@ -2847,6 +2847,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php',
|
||||
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
|
||||
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
|
||||
'PhabricatorExcelExportFormat' => 'infrastructure/export/PhabricatorExcelExportFormat.php',
|
||||
'PhabricatorExecFutureFileUploadSource' => 'applications/files/uploadsource/PhabricatorExecFutureFileUploadSource.php',
|
||||
'PhabricatorExportEngineExtension' => 'infrastructure/export/PhabricatorExportEngineExtension.php',
|
||||
'PhabricatorExportField' => 'infrastructure/export/PhabricatorExportField.php',
|
||||
|
@ -8282,6 +8283,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEventListener' => 'PhutilEventListener',
|
||||
'PhabricatorEventType' => 'PhutilEventType',
|
||||
'PhabricatorExampleEventListener' => 'PhabricatorEventListener',
|
||||
'PhabricatorExcelExportFormat' => 'PhabricatorExportFormat',
|
||||
'PhabricatorExecFutureFileUploadSource' => 'PhabricatorFileUploadSource',
|
||||
'PhabricatorExportEngineExtension' => 'Phobject',
|
||||
'PhabricatorExportField' => 'Phobject',
|
||||
|
|
|
@ -410,8 +410,10 @@ final class PhabricatorApplicationSearchController
|
|||
|
||||
if ($named_query) {
|
||||
$filename = $named_query->getQueryName();
|
||||
$sheet_title = $named_query->getQueryName();
|
||||
} else {
|
||||
$filename = $engine->getResultTypeDescription();
|
||||
$sheet_title = $engine->getResultTypeDescription();
|
||||
}
|
||||
$filename = phutil_utf8_strtolower($filename);
|
||||
$filename = PhabricatorFile::normalizeFileName($filename);
|
||||
|
@ -445,8 +447,9 @@ final class PhabricatorApplicationSearchController
|
|||
$mime_type = $format->getMIMEContentType();
|
||||
$filename = $filename.'.'.$extension;
|
||||
|
||||
$format = clone $format;
|
||||
$format->setViewer($viewer);
|
||||
$format = id(clone $format)
|
||||
->setViewer($viewer)
|
||||
->setTitle($sheet_title);
|
||||
|
||||
$export_data = $engine->newExport($objects);
|
||||
$objects = array_values($objects);
|
||||
|
|
|
@ -24,4 +24,24 @@ final class PhabricatorEpochExportField
|
|||
return (int)$value;
|
||||
}
|
||||
|
||||
public function getPHPExcelValue($value) {
|
||||
$epoch = $this->getNaturalValue($value);
|
||||
|
||||
$seconds_per_day = phutil_units('1 day in seconds');
|
||||
$offset = ($seconds_per_day * 25569);
|
||||
|
||||
return ($epoch + $offset) / $seconds_per_day;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPExcel_Style_NumberFormat
|
||||
*/
|
||||
public function formatPHPExcelCell($cell, $style) {
|
||||
$code = PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2;
|
||||
|
||||
$style
|
||||
->getNumberFormat()
|
||||
->setFormatCode($code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
145
src/infrastructure/export/PhabricatorExcelExportFormat.php
Normal file
145
src/infrastructure/export/PhabricatorExcelExportFormat.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorExcelExportFormat
|
||||
extends PhabricatorExportFormat {
|
||||
|
||||
const EXPORTKEY = 'excel';
|
||||
|
||||
private $workbook;
|
||||
private $sheet;
|
||||
private $rowCursor;
|
||||
|
||||
public function getExportFormatName() {
|
||||
return pht('Excel (.xlsx)');
|
||||
}
|
||||
|
||||
public function isExportFormatEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFileExtension() {
|
||||
return 'xlsx';
|
||||
}
|
||||
|
||||
public function getMIMEContentType() {
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPExcel_Cell_DataType
|
||||
*/
|
||||
public function addHeaders(array $fields) {
|
||||
$sheet = $this->getSheet();
|
||||
|
||||
$header_format = array(
|
||||
'font' => array(
|
||||
'bold' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$row = 1;
|
||||
$col = 0;
|
||||
foreach ($fields as $field) {
|
||||
$cell_value = $field->getLabel();
|
||||
|
||||
$cell_name = $this->getCellName($col, $row);
|
||||
|
||||
$cell = $sheet->setCellValue(
|
||||
$cell_name,
|
||||
$cell_value,
|
||||
$return_cell = true);
|
||||
|
||||
$sheet->getStyle($cell_name)->applyFromArray($header_format);
|
||||
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_STRING);
|
||||
|
||||
$width = $field->getCharacterWidth();
|
||||
if ($width !== null) {
|
||||
$col_name = $this->getCellName($col);
|
||||
$sheet->getColumnDimension($col_name)
|
||||
->setWidth($width);
|
||||
}
|
||||
|
||||
$col++;
|
||||
}
|
||||
}
|
||||
|
||||
public function addObject($object, array $fields, array $map) {
|
||||
$sheet = $this->getSheet();
|
||||
|
||||
$col = 0;
|
||||
foreach ($fields as $key => $field) {
|
||||
$cell_value = $map[$key];
|
||||
$cell_value = $field->getPHPExcelValue($cell_value);
|
||||
|
||||
$cell_name = $this->getCellName($col, $this->rowCursor);
|
||||
|
||||
$cell = $sheet->setCellValue(
|
||||
$cell_name,
|
||||
$cell_value,
|
||||
$return_cell = true);
|
||||
|
||||
$style = $sheet->getStyle($cell_name);
|
||||
$field->formatPHPExcelCell($cell, $style);
|
||||
|
||||
$col++;
|
||||
}
|
||||
|
||||
$this->rowCursor++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPExcel_IOFactory
|
||||
*/
|
||||
public function newFileData() {
|
||||
$workbook = $this->getWorkbook();
|
||||
$writer = PHPExcel_IOFactory::createWriter($workbook, 'Excel2007');
|
||||
|
||||
ob_start();
|
||||
$writer->save('php://output');
|
||||
$data = ob_get_clean();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getWorkbook() {
|
||||
if (!$this->workbook) {
|
||||
$this->workbook = $this->newWorkbook();
|
||||
}
|
||||
return $this->workbook;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPExcel
|
||||
*/
|
||||
private function newWorkbook() {
|
||||
include_once 'PHPExcel.php';
|
||||
return new PHPExcel();
|
||||
}
|
||||
|
||||
private function getSheet() {
|
||||
if (!$this->sheet) {
|
||||
$workbook = $this->getWorkbook();
|
||||
|
||||
$sheet = $workbook->setActiveSheetIndex(0);
|
||||
$sheet->setTitle($this->getTitle());
|
||||
|
||||
$this->sheet = $sheet;
|
||||
|
||||
// The row cursor starts on the second row, after the header row.
|
||||
$this->rowCursor = 2;
|
||||
}
|
||||
|
||||
return $this->sheet;
|
||||
}
|
||||
|
||||
private function getCellName($col, $row = null) {
|
||||
$col_name = chr(ord('A') + $col);
|
||||
|
||||
if ($row === null) {
|
||||
return $col_name;
|
||||
}
|
||||
|
||||
return $col_name.$row;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,4 +32,19 @@ abstract class PhabricatorExportField
|
|||
return $value;
|
||||
}
|
||||
|
||||
public function getPHPExcelValue($value) {
|
||||
return $this->getTextValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPExcel_Cell_DataType
|
||||
*/
|
||||
public function formatPHPExcelCell($cell, $style) {
|
||||
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_STRING);
|
||||
}
|
||||
|
||||
public function getCharacterWidth() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ abstract class PhabricatorExportFormat
|
|||
extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $title;
|
||||
|
||||
final public function getExportFormatKey() {
|
||||
return $this->getPhobjectClassConstant('EXPORTKEY');
|
||||
|
@ -18,6 +19,15 @@ abstract class PhabricatorExportFormat
|
|||
return $this->viewer;
|
||||
}
|
||||
|
||||
final public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
abstract public function getExportFormatName();
|
||||
abstract public function getMIMEContentType();
|
||||
abstract public function getFileExtension();
|
||||
|
|
|
@ -7,4 +7,8 @@ final class PhabricatorIDExportField
|
|||
return (int)$value;
|
||||
}
|
||||
|
||||
public function getCharacterWidth() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,22 @@ final class PhabricatorIntExportField
|
|||
extends PhabricatorExportField {
|
||||
|
||||
public function getNaturalValue($value) {
|
||||
if ($value === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPExcel_Cell_DataType
|
||||
*/
|
||||
public function formatPHPExcelCell($cell, $style) {
|
||||
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_NUMERIC);
|
||||
}
|
||||
|
||||
public function getCharacterWidth() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPHIDExportField
|
||||
extends PhabricatorExportField {}
|
||||
extends PhabricatorExportField {
|
||||
|
||||
public function getCharacterWidth() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue