Skip to content
Snippets Groups Projects
Commit 03e931b9 authored by bgm's avatar bgm Committed by Aegir user
Browse files

Report export: set column widths, wrap text, changes to file name, file metadata.

parent 416f1c9f
No related branches found
No related tags found
No related merge requests found
<?php
use CRM_Civiexportexcel_ExtensionUtil as E;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Row;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
/**
* @package civiexportexcel
* @copyright Mathieu Lutfy (c) 2014-2015
*/
class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
const ROW_PADDING = 5;
const DEFAULT_CELL_WIDTH = 9.14;
const DEFAULT_ROW_HEIGHT = 10;
/**
* Generates a XLS 2007 file and forces the browser to download it.
*
......@@ -18,12 +29,13 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
* See @CRM_Report_Utils_Report::export2csv().
*/
static function export2excel2007(&$form, &$rows) {
//Force a download and name the file using the current timestamp.
$datetime = date('Ymd-Gi', $_SERVER['REQUEST_TIME']);
// Force a download and name the file using the current timestamp.
$datetime = date('Y-m-d H:i');
$filename = $form->getTitle() . ' - ' . $datetime . '.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="Report_' . $datetime . '.xlsx"');
header("Content-Description: " . ts('CiviCRM report'));
header('Content-Disposition: attachment;filename="' . $filename . '"');
header("Content-Description: " . $filename);
header("Content-Transfer-Encoding: binary");
header("Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0");
......@@ -70,12 +82,20 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
// $validLocale = \PhpOffice\PhpSpreadsheet\Settings::setLocale('fr');
// Set document properties
$contact_id = CRM_Core_Session::singleton()->get('userID');
$display_name = civicrm_api3('Contact', 'getsingle', ['contact_id' => $contact_id])['display_name'];
$domain_name = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Domain', CRM_Core_Config::domainID(), 'name');
$title = $form->getTitle();
$objPHPExcel->getProperties()
->setCreator("CiviCRM")
->setLastModifiedBy("CiviCRM")
->setTitle(ts('Report'))
->setSubject(ts('Report'))
->setDescription(ts('Report'));
->setCreator($display_name)
->setLastModifiedBy($display_name)
->setTitle($form->getTitle())
->setDescription(E::ts('Report exported from CiviCRM (%1) by %2 at %3', [
1 => $domain_name,
2 => $display_name,
3 => date('Y-m-d H:i:s'),
]));
$sheet = $objPHPExcel->setActiveSheetIndex(0);
$objPHPExcel->getActiveSheet()->setTitle('Report');
......@@ -90,6 +110,9 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
}
}
// Used for later calculating the column widths
$widths = [];
// Add the column headers.
$col = 0;
$cpt = 1;
......@@ -101,6 +124,8 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
->setCellValue($cell, $h)
->getStyle($cell)->applyFromArray(['font' => ['bold' => true]]);
self::addValueLengthToColumnWidths($h, $cells[$col], $widths);
$col++;
}
......@@ -140,6 +165,8 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
$objPHPExcel->getActiveSheet()
->setCellValue($cells[$col] . $cpt, $value);
self::addValueLengthToColumnWidths($value, $cells[$col], $widths);
// Cell formats
if (CRM_Utils_Array::value('type', $form->_columnHeaders[$v]) & CRM_Utils_Type::T_DATE) {
$objPHPExcel->getActiveSheet()
......@@ -147,7 +174,7 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
->getNumberFormat()
->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_YYYYMMDD);
// Set autosize on date columns.
// Set autosize on date columns.
// We only do it for dates because we know they have a fixed width, unlike strings.
// For eco-friendlyness, this should only be done once, perhaps when processing the headers initially
$objPHPExcel->getActiveSheet()->getColumnDimension($cells[$col])->setAutoSize(true);
......@@ -164,9 +191,173 @@ class CRM_CiviExportExcel_Utils_Report extends CRM_Core_Page {
$cpt++;
}
// Re-adjust the column widths
foreach ($cells as $key => $val) {
$w = self::getRecommendedColumnWidth($val, $widths);
// Ignore width that would be too small to be practical.
if ($w < 5) {
continue;
}
if ($w > 75) {
$w = 75;
// Ex: A0:A100
$area = $val . '0:' . $val . $cpt;
$objPHPExcel->getActiveSheet()->getStyle($area)
->getAlignment()->setWrapText(true);
}
$objPHPExcel->getActiveSheet()->getColumnDimension($val)->setWidth($w);
}
// Now set row heights (skip the header)
$sheet = $objPHPExcel->getActiveSheet();
for ($i = 2; $i < $cpt; $i++) {
$row = new Row($sheet, $i);
self::autofitRowHeight($row);
}
$objWriter = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($objPHPExcel, 'Xlsx');
$objWriter->save($filename);
return ''; // FIXME
}
/**
* Helper function to log data widths. Later used by getRecommendedColumnWidth().
* This might be a bit overkill, but worth testing.
*/
static public function addValueLengthToColumnWidths($value, $column, &$widths) {
$len = mb_strlen($value);
// Ignore empty cells
if (!$len) {
return;
}
if (!isset($widths[$column])) {
$widths[$column] = [];
}
$widths[$column][] = $len;
}
/**
* Returns a recommended column width, based on 90th percentile of rows with data.
*
* Percentile function based on:
* http://php.net/manual/en/function.stats-stat-percentile.php#79752
*/
static public function getRecommendedColumnWidth($column, &$widths) {
$p = 0.9; // 90% percentile
if (empty($widths[$column])) {
return 0;
}
$data = $widths[$column];
sort($data);
$count = count($data);
$allindex = ($count-1) * $p;
$intvalindex = intval($allindex);
$floatval = $allindex - $intvalindex;
if (!is_float($floatval)) {
$result = $data[$intvalindex];
}
else {
if ($count > $intvalindex+1) {
$result = $floatval * ($data[$intvalindex+1] - $data[$intvalindex]) + $data[$intvalindex];
}
else {
$result = $data[$intvalindex];
}
}
// Add a bit of padding.
$result += 2;
return $result;
}
/**
* Auto-fit the row height based on the largest cell
*
* Source: https://github.com/PHPOffice/PhpSpreadsheet/issues/333#issuecomment-385943533
*
* @param Row $start
* @return Worksheet
*/
public static function autofitRowHeight(Row $row, $rowPadding = self::ROW_PADDING) {
$ws = $row->getWorksheet();
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(true);
$maxCellLines = 1;
/* @var $cell Cell */
foreach ($cellIterator as $cell) {
$cellLength = mb_strlen($cell->getValue());
$cellWidth = $ws->getColumnDimension($cell->getParent()->getCurrentColumn())->getWidth();
// If no column width is set, set the default
if ($cellWidth === -1) {
// [ML] Let our own function do this
// $ws->getColumnDimension($cell->getParent()->getCurrentColumn())->setWidth(self::DEFAULT_CELL_WIDTH);
$cellWidth = $ws->getColumnDimension($cell->getParent()->getCurrentColumn())->getWidth();
}
// If the cell is in a merge range we need to determine the full width of the range
if($cell->isInMergeRange()) {
// We only need to do this for the master (first) cell in the range, the rest need to have a line height of 1
if ($cell->isMergeRangeValueCell()) {
$mergeRange = $cell->getMergeRange();
if ($mergeRange) {
$mergeWidth = 0;
$mergeRefs = Coordinate::extractAllCellReferencesInRange($mergeRange);
foreach ($mergeRefs as $cellRef) {
$mergeCell = $ws->getCell($cellRef);
$width = $ws->getColumnDimension($mergeCell->getParent()->getCurrentColumn())->getWidth();
if($width === -1) {
// [ML] Let our own function do this
// $ws->getColumnDimension($mergeCell->getParent()->getCurrentColumn())->setWidth(self::DEFAULT_CELL_WIDTH);
$width = $ws->getColumnDimension($mergeCell->getParent()->getCurrentColumn())->getWidth();
}
$mergeWidth += $width;
}
$cellWidth = $mergeWidth;
}
else {
$cellWidth = 1;
}
} else {
$cellWidth = 1;
}
}
// Calculate the number of cell lines with a 10% additional margin
$cellLines = ceil(($cellLength * 1.1) / $cellWidth);
$maxCellLines = $cellLines > $maxCellLines ? $cellLines : $maxCellLines;
}
$rowDimension= $ws->getRowDimension($row->getRowIndex());
$rowHeight = $rowDimension->getRowHeight();
// If no row height is set, set the default
if ($rowHeight === -1) {
$rowDimension->setRowHeight(self::DEFAULT_ROW_HEIGHT);
$rowHeight = $rowDimension->getRowHeight();
}
$rowLines = $maxCellLines <= 0 ? 1 : $maxCellLines;
$rowDimension->setRowHeight(($rowHeight * $rowLines) + $rowPadding);
return $ws;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment