Commit 0baf77e4 authored by Chenu Denis's avatar Chenu Denis

[feature] Export as xlsx

parent fb330fad
......@@ -16,8 +16,11 @@
# tmp directory : whole except the index.html for directory create
/tmp/*
!/tmp/index.html
/tmp/assets/*
!/tmp/assets/index.html
/tmp/runtime/*
!/tmp/runtime/index.html
/tmp/upload/*
!/tmp/upload/index.html
# ignore plugins directory by default except LS plugin
......
# Change Log
Only partial changelog, [commit history](https://framagit.org/Shnoulle/LimeSurvey/commits/2.06_SondagesPro) show all changelog.
## Not released
### Feature
- Export as xlsx (not as xls)
## [1.4.0] - 2017-03-02
### Fix
......
......@@ -12,10 +12,17 @@ class ExcelWriter extends Writer
private $separator;
private $hasOutputHeader;
private $rowCounter;
private $forceDownload=true;
//Indicates if the Writer is outputting to a file rather than sending via HTTP.
private $outputToFile = false;
/**
* The filename to use for the resulting file when export
* @var string
*/
protected $xlsFilename = 'Excel.xlsx';
/**
* The presence of a filename will cause the writer to output to
* a file rather than send.
......@@ -25,7 +32,7 @@ class ExcelWriter extends Writer
*/
public function __construct($filename = null)
{
require_once(APPPATH.'/third_party/pear/Spreadsheet/Excel/Xlswriter.php');
require_once(APPPATH.'/third_party/xlsx_writer/xlsxwriter.class.php');
$this->separator = '~|';
$this->hasOutputHeader = false;
$this->rowCounter = 0;
......@@ -34,29 +41,15 @@ class ExcelWriter extends Writer
public function init(SurveyObj $survey, $sLanguageCode, FormattingOptions $oOptions)
{
parent::init($survey, $sLanguageCode, $oOptions);
$this->xlsFilename = "results-survey".$survey->id.".xlsx";
if ($oOptions->output=='file')
{
$this->workbook = new xlswriter($this->filename);
$this->outputToFile = true;
}
else
{
$this->workbook = new xlswriter;
}
$this->workbook->setTempDir(Yii::app()->getConfig("tempdir"));
$this->workbook = new XLSXWriter();
if ($oOptions->output=='display') {
$this->workbook->send('results-survey'.$survey->id.'.xls');
}
$worksheetName = $survey->languageSettings['surveyls_title'];
$worksheetName=substr(str_replace(array('*', ':', '/', '\\', '?', '[', ']'),array(' '),$worksheetName),0,31); // Remove invalid characters
$this->workbook->setVersion(8);
$sheet =$this->workbook->addWorksheet($worksheetName); // do not translate/change this - the library does not support any special chars in sheet name
$sheet->setInputEncoding('utf-8');
$this->currentSheet = $sheet;
$this->currentSheet = $worksheetName;
$this->forceDownload=!($oOptions->output=='file');
}
protected function outputRecord($headers, $values, FormattingOptions $oOptions)
......@@ -64,35 +57,21 @@ class ExcelWriter extends Writer
if (!$this->hasOutputHeader)
{
$columnCounter = 0;
foreach ($headers as $header)
{
$this->currentSheet->write($this->rowCounter,$columnCounter,str_replace('?', '-', $this->excelEscape($header)));
$columnCounter++;
}
$this->workbook->writeSheetRow($this->currentSheet, $headers );
$this->hasOutputHeader = true;
$this->rowCounter++;
}
$columnCounter = 0;
foreach ($values as $value)
{
$this->currentSheet->write($this->rowCounter, $columnCounter, $this->excelEscape($value));
$columnCounter++;
}
$this->rowCounter++;
}
private function excelEscape($value)
{
if ((substr($value, 0, 1) == '=') || (substr($value, 0, 1) == '@'))
{
$value = '"'.$value.'"';
}
return $value;
$this->workbook->writeSheetRow($this->currentSheet, $values );
}
public function close()
{
$this->workbook->close();
$this->workbook->writeToFile($this->filename);
if ($this->forceDownload){
header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
header("Content-Disposition: attachment; filename=\"{$this->xlsFilename}\"");
header('Content-Length: ' . filesize($this->filename));
readfile($this->filename);
}
return $this->workbook;
}
}
\ No newline at end of file
}
......@@ -11,7 +11,8 @@ abstract class Writer implements IWriter
protected $sLanguageCode;
protected $translator;
public $filename;
public $webfilename;
protected function translate($key, $sLanguageCode)
{
return $this->translator->translate($key, $sLanguageCode);
......@@ -30,10 +31,7 @@ abstract class Writer implements IWriter
{
$this->languageCode = $sLanguageCode;
$this->translator = new Translator();
if ($oOptions->output == 'file') {
$sRandomFileName=Yii::app()->getConfig("tempdir") . DIRECTORY_SEPARATOR . randomChars(40);
$this->filename = $sRandomFileName;
}
$this->filename = Yii::app()->getConfig("tempdir") . DIRECTORY_SEPARATOR . randomChars(40);
}
/**
......@@ -93,7 +91,7 @@ abstract class Writer implements IWriter
/**
* Return the subquestion part, if not empty : add a space before it.
*
*
* @param Survey $oSurvey
* @param FormattingOptions $oOptions
* @param string $fieldName
......@@ -114,7 +112,7 @@ abstract class Writer implements IWriter
/**
* Return the question text part without any subquestion
*
*
* @param Survey $oSurvey
* @param FormattingOptions $oOptions
* @param string $fieldName
......@@ -300,7 +298,7 @@ abstract class Writer implements IWriter
}
//Output the results.
$sFile='';
// If empty survey, prepare an empty responses array, and output just 1 empty record with header.
if ($oSurvey->responses->rowCount == 0)
{
......@@ -310,7 +308,7 @@ abstract class Writer implements IWriter
}
$this->outputRecord($headers, $elementArray, $oOptions);
}
// If no empty survey, render/export responses array.
foreach($oSurvey->responses as $response)
{
......
The MIT License (MIT)
Copyright (c) 2013 Mark Jones
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# LimeSurvey integration #
- Source : [PHP_XLSXWriter 0.31](https://github.com/mk-j/PHP_XLSXWriter/releases/tag/0.31)
- Keep only LICENCE, README.md and class
- Thanks to [contributors](https://github.com/mk-j/PHP_XLSXWriter/graphs/contributors)
PHP_XLSXWriter
==============
This library is designed to be lightweight, and have minimal memory usage.
It is designed to output an Excel compatible spreadsheet in (Office 2007+) xlsx format, with just basic features supported:
* supports PHP 5.2.1+
* takes UTF-8 encoded input
* multiple worksheets
* supports currency/date/numeric cell formatting, simple formulas
* supports basic cell styling
* supports writing huge 100K+ row spreadsheets
[Never run out of memory with PHPExcel again](https://github.com/mk-j/PHP_XLSXWriter).
Simple PHP CLI example:
```php
$data = array(
array('year','month','amount'),
array('2003','1','220'),
array('2003','2','153.5'),
);
$writer = new XLSXWriter();
$writer->writeSheet($data);
$writer->writeToFile('output.xlsx');
```
Simple/Advanced Cell Formats:
```php
$header = array(
'created'=>'date',
'product_id'=>'integer',
'quantity'=>'#,##0',
'amount'=>'price',
'description'=>'string',
'tax'=>'[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00',
);
$data = array(
array('2015-01-01',873,1,'44.00','misc','=D2*0.05'),
array('2015-01-12',324,2,'88.00','none','=D3*0.05'),
);
$writer = new XLSXWriter();
$writer->writeSheetHeader('Sheet1', $header );
foreach($data as $row)
$writer->writeSheetRow('Sheet1', $row );
$writer->writeToFile('example.xlsx');
```
50000 rows: (1.4s, 0MB memory usage)
```php
include_once("xlsxwriter.class.php");
$writer = new XLSXWriter();
$writer->writeSheetHeader('Sheet1', array('c1'=>'integer','c2'=>'integer','c3'=>'integer','c4'=>'integer') );
for($i=0; $i<50000; $i++)
{
$writer->writeSheetRow('Sheet1', array($i, $i+1, $i+2, $i+3) );
}
$writer->writeToFile('huge.xlsx');
echo '#'.floor((memory_get_peak_usage())/1024/1024)."MB"."\n";
```
| rows | time | memory |
| ------ | ---- | ------ |
| 50000 | 1.4s | 0MB |
| 100000 | 2.7s | 0MB |
| 150000 | 4.1s | 0MB |
| 200000 | 5.7s | 0MB |
| 250000 | 7.0s | 0MB |
Simple cell formats map to more advanced cell formats
| simple formats | format code |
| ---------- | ---- |
| string | @ |
| integer | 0 |
| date | YYYY-MM-DD |
| datetime | YYYY-MM-DD HH:MM:SS |
| price | #,##0.00 |
| dollar | [$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00 |
| euro | #,##0.00 [$€-407];[RED]-#,##0.00 [$€-407] |
Basic cell styles have been available since version 0.30
| style | allowed values |
| ---------- | ---- |
| font | Arial, Times New Roman, Courier New, Comic Sans MS |
| font-size | 8,9,10,11,12 ... |
| font-style | bold, italic, underline, strikethrough or multiple ie: 'bold,italic' |
| border | left, right, top, bottom, or multiple ie: 'top,left' |
| color | #RRGGBB, ie: #ff99cc or #f9c |
| fill | #RRGGBB, ie: #eeffee or #efe |
| halign | general, left, right, justify, center |
| valign | bottom, center, distributed |
This diff is collapsed.
<script type="text/javascript">
var sMsgMaximumExcelColumns = '<?php eT("You can only choose 255 colums at a maximum for Excel export.",'js'); ?>';
var sMsgExcelColumnsReduced = '<?php eT("The number of selected columns was reduced automatically.",'js'); ?>';
var sMsgColumnCount = '<?php eT("%s of %s columns selected",'js'); ?>';
</script>
<div class='header ui-widget-header'><?php eT("Export results");?>
<?php
<?php
if (App()->request->getQuery('statfilter'))
echo" - ".gT("Filtered from statistics script");
elseif ($SingleResponse) {
echo " - ".sprintf(gT("Single response: ID %s"),$SingleResponse);}
echo " - ".sprintf(gT("Single response: ID %s"),$SingleResponse);}
?>
</div>
<div class='wrap2columns'>
<?php echo CHtml::form(array("admin/export","sa"=>'exportresults','surveyid'=>$surveyid,'statfilter'=>App()->request->getQuery('statfilter')), 'post', array('id'=>'resultexport'));
?>
<div class='left'>
<fieldset><legend><?php eT("Format");?></legend>
<ul>
<ul>
<?php
$hasTips = false;
foreach ($exports as $key => $info)
......@@ -51,13 +49,13 @@
}
if ($hasTips) {
// We have tooltips, now register javascript
App()->clientScript->registerScript('tooltip-export',
App()->clientScript->registerScript('tooltip-export',
"jQuery('div.tooltip-export').popover({
html: true,
content: function() {
return $(this).find('div.exporttip').clone();
},
title: function() {
title: function() {
return $(this).parent().find('label').text();
},
trigger: 'hover'
......@@ -65,7 +63,7 @@
");
}
?>
</ul></fieldset>
</ul></fieldset>
<fieldset <?php if ($SingleResponse) {?>
style='display:none';
<?php } ?>
......@@ -144,15 +142,15 @@
<legend><?php eT("Column control");?></legend>
<input type='hidden' name='sid' value='<?php echo $surveyid; ?>' />
<?php
<?php
if ($SingleResponse) { ?>
<input type='hidden' name='response_id' value="<?php echo $SingleResponse;?>" />
<?php }
eT("Choose columns");?>:
<br />
<?php
<?php
echo CHtml::listBox('colselect[]',array_keys($aFields),$aFields,array('multiple'=>'multiple','size'=>'20','style'=>'width:370px;','options'=>$aFieldsOptions));
echo "\t<img src='$imageurl/help.gif' alt='".gT("Help")."' onclick='javascript:alert(\"".gT("Please note: The export to Excel is currently limited to loading no more than 255 columns.","js")."\")'>";?>
?>
<span id='columncount'>&nbsp;</span>
</fieldset>
<?php if ($thissurvey['anonymized'] == "N" && tableExists("{{tokens_$surveyid}}") && Permission::model()->hasSurveyPermission($surveyid,'tokens','read')) { ?>
......
$(document).ready(function(){
updateColumnCountDisplay();
$('#colselect').change(function(){
if ($(this).val().length > 255 && $('#xls').prop('checked')) {
alert(sMsgMaximumExcelColumns);
$(this).val(last_valid_selection);
} else {
last_valid_selection = $(this).val();
}
updateColumnCountDisplay();
updateColumnCountDisplay();
});
$('#xls').change(function(){
aOptions=$($("#colselect :selected").get().reverse());
selectedCount = $("#colselect :selected").length;
bLimited=false;
aOptions.each(function(){
if (selectedCount>255)
{
this.selected=false;
selectedCount--;
bLimited=true;
}
});
if (bLimited)
{
alert(sMsgMaximumExcelColumns+' '+"\n"+sMsgExcelColumnsReduced);
}
updateColumnCountDisplay();
});
});
function updateColumnCountDisplay()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment