1
0
Fork 0
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:
epriestley 2018-01-29 06:50:11 -08:00
parent a067f64ebb
commit 0409279595
9 changed files with 223 additions and 3 deletions

View file

@ -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',

View file

@ -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);

View file

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

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

View file

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

View file

@ -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();

View file

@ -7,4 +7,8 @@ final class PhabricatorIDExportField
return (int)$value;
}
public function getCharacterWidth() {
return 12;
}
}

View file

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

View file

@ -1,4 +1,10 @@
<?php
final class PhabricatorPHIDExportField
extends PhabricatorExportField {}
extends PhabricatorExportField {
public function getCharacterWidth() {
return 32;
}
}