diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2847,6 +2847,7 @@ '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 @@ 'PhabricatorEventListener' => 'PhutilEventListener', 'PhabricatorEventType' => 'PhutilEventType', 'PhabricatorExampleEventListener' => 'PhabricatorEventListener', + 'PhabricatorExcelExportFormat' => 'PhabricatorExportFormat', 'PhabricatorExecFutureFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorExportEngineExtension' => 'Phobject', 'PhabricatorExportField' => 'Phobject', diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -410,8 +410,10 @@ 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 @@ $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); diff --git a/src/infrastructure/export/PhabricatorEpochExportField.php b/src/infrastructure/export/PhabricatorEpochExportField.php --- a/src/infrastructure/export/PhabricatorEpochExportField.php +++ b/src/infrastructure/export/PhabricatorEpochExportField.php @@ -24,4 +24,24 @@ 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); + } + } diff --git a/src/infrastructure/export/PhabricatorExcelExportFormat.php b/src/infrastructure/export/PhabricatorExcelExportFormat.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/export/PhabricatorExcelExportFormat.php @@ -0,0 +1,145 @@ +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; + } + +} diff --git a/src/infrastructure/export/PhabricatorExportField.php b/src/infrastructure/export/PhabricatorExportField.php --- a/src/infrastructure/export/PhabricatorExportField.php +++ b/src/infrastructure/export/PhabricatorExportField.php @@ -32,4 +32,19 @@ 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; + } + } diff --git a/src/infrastructure/export/PhabricatorExportFormat.php b/src/infrastructure/export/PhabricatorExportFormat.php --- a/src/infrastructure/export/PhabricatorExportFormat.php +++ b/src/infrastructure/export/PhabricatorExportFormat.php @@ -4,6 +4,7 @@ extends Phobject { private $viewer; + private $title; final public function getExportFormatKey() { return $this->getPhobjectClassConstant('EXPORTKEY'); @@ -18,6 +19,15 @@ 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(); diff --git a/src/infrastructure/export/PhabricatorIDExportField.php b/src/infrastructure/export/PhabricatorIDExportField.php --- a/src/infrastructure/export/PhabricatorIDExportField.php +++ b/src/infrastructure/export/PhabricatorIDExportField.php @@ -7,4 +7,8 @@ return (int)$value; } + public function getCharacterWidth() { + return 12; + } + } diff --git a/src/infrastructure/export/PhabricatorIntExportField.php b/src/infrastructure/export/PhabricatorIntExportField.php --- a/src/infrastructure/export/PhabricatorIntExportField.php +++ b/src/infrastructure/export/PhabricatorIntExportField.php @@ -4,7 +4,22 @@ 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; + } + } diff --git a/src/infrastructure/export/PhabricatorPHIDExportField.php b/src/infrastructure/export/PhabricatorPHIDExportField.php --- a/src/infrastructure/export/PhabricatorPHIDExportField.php +++ b/src/infrastructure/export/PhabricatorPHIDExportField.php @@ -1,4 +1,10 @@