Newer
Older
mattpiwik
a validé
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
mattpiwik
a validé
*
mattpiwik
a validé
*/
/**
* CSV export
*
* When rendered using the default settings, a CSV report has the following characteristics:
* The first record contains headers for all the columns in the report.
* All rows have the same number of columns.
* The default field delimiter string is a comma (,).
* Formatting and layout are ignored.
*
mattpiwik
a validé
*/
class Piwik_DataTable_Renderer_Csv extends Piwik_DataTable_Renderer
{
/**
* Column separator
*
* @var string
*/
public $separator = ",";
mattpiwik
a validé
/**
* Line end
*
* @var string
*/
public $lineEnd = "\n";
/**
* 'metadata' columns will be exported, prefixed by 'metadata_'
*
* @var bool
*/
public $exportMetadata = true;
/**
* Converts the content to unicode so that UTF8 characters (eg. chinese) can be imported in Excel
*
* @var bool
*/
public $convertToUnicode = true;
/**
* idSubtable will be exported in a column called 'idsubdatatable'
*
* @var bool
*/
public $exportIdSubtable = true;
public function render()
{
$str = $this->renderTable($this->table);
if(empty($str))
{
return 'No data available';
}
if($this->convertToUnicode
&& function_exists('mb_convert_encoding'))
{
$str = chr(255) . chr(254) . mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
}
return $str;
function renderException()
{
$exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
return 'Error: '.$exceptionMessage;
}
public function setConvertToUnicode($bool)
public function setSeparator($separator)
mattpiwik
a validé
{
$this->separator = $separator;
mattpiwik
a validé
}
protected function renderTable($table, &$allColumns = array() )
mattpiwik
a validé
{
if($table instanceof Piwik_DataTable_Array)
{
$str = '';
mattpiwik
a validé
foreach($table->getArray() as $currentLinePrefix => $dataTable)
{
$returned = explode("\n",$this->renderTable($dataTable, $allColumns));
// get rid of the columns names
mattpiwik
a validé
$returned = array_slice($returned,1);
// case empty datatable we dont print anything in the CSV export
// when in xml we would output <result date="2008-01-15" />
if(!empty($returned))
{
foreach($returned as &$row)
{
$row = $currentLinePrefix . $this->separator . $row;
}
$str .= "\n" . implode("\n", $returned);
}
}
// prepend table key to column list
$allColumns = array_merge(array($table->getKeyName() => true), $allColumns);
// add header to output string
$str = $this->getHeaderLine(array_keys($allColumns)).$str;
mattpiwik
a validé
}
else
{
$str = $this->renderDataTable($table, $allColumns);
mattpiwik
a validé
}
return $str;
mattpiwik
a validé
}
protected function renderDataTable( $table, &$allColumns = array() )
mattpiwik
a validé
{
if($table instanceof Piwik_DataTable_Simple)
mattpiwik
a validé
{
$row = $table->getFirstRow();
if($row !== false)
{
$columnNameToValue = $row->getColumns();
if(count($columnNameToValue) == 1)
{
// simple tables should only have one column, the value
$allColumns['value'] = true;
$value = array_values($columnNameToValue);
$str = 'value' . $this->lineEnd . $this->formatValue($value[0]);
return $str;
}
}
mattpiwik
a validé
}
$csv = array();
mattpiwik
a validé
foreach($table->getRows() as $row)
{
$csvRow = array();
$columns = $row->getColumns();
foreach($columns as $name => $value)
{
//goals => array( 'idgoal=1' =>array(..), 'idgoal=2' => array(..))
if(is_array($value))
{
foreach($value as $key => $subValues)
{
if(is_array($subValues))
{
foreach($subValues as $subKey => $subValue)
{
if ($this->translateColumnNames)
{
$subName = $name != 'goals' ? $name . ' ' . $key
: Piwik_Translate('Goals_GoalX', $key);
$columnName = $this->translateColumnName($subKey)
.' ('. $subName . ')';
}
else
{
// goals_idgoal=1
$columnName = $name . "_" . $key . "_" . $subKey;
}
$allColumns[$columnName] = true;
$csvRow[$columnName] = $subValue;
}
}
}
}
else
mattpiwik
a validé
{
$allColumns[$name] = true;
mattpiwik
a validé
}
}
mattpiwik
a validé
if($this->exportMetadata)
{
$metadata = $row->getMetadata();
foreach($metadata as $name => $value)
{
mattpiwik
a validé
if($name == 'idsubdatatable_in_db') {
continue;
}
mattpiwik
a validé
//if a metadata and a column have the same name make sure they dont overwrite
if($this->translateColumnNames)
{
$name = Piwik_Translate('General_Metadata').': '.$name;
}
else
{
$name = 'metadata_'.$name;
}
mattpiwik
a validé
$allColumns[$name] = true;
$csvRow[$name] = $value;
}
}
if($this->exportIdSubtable)
{
$idsubdatatable = $row->getIdSubDataTable();
if($idsubdatatable !== false
&& $this->hideIdSubDatatable === false)
mattpiwik
a validé
{
$csvRow['idsubdatatable'] = $idsubdatatable;
}
}
mattpiwik
a validé
}
// now we make sure that all the rows in the CSV array have all the columns
foreach($csv as &$row)
{
foreach($allColumns as $columnName => $true)
{
if(!isset($row[$columnName]))
{
$row[$columnName] = '';
}
}
}
mattpiwik
a validé
$str = '';
// specific case, we have only one column and this column wasn't named properly (indexed by a number)
// we don't print anything in the CSV file => an empty line
if(sizeof($allColumns) == 1
&& reset($allColumns)
&& !is_string(key($allColumns)))
{
$str .= '';
}
else
{
$str .= $this->getHeaderLine(array_keys($allColumns)).$this->lineEnd;
mattpiwik
a validé
}
// we render the CSV
foreach($csv as $theRow)
{
$rowStr = '';
foreach($allColumns as $columnName => $true)
{
$rowStr .= $this->formatValue($theRow[$columnName]) . $this->separator;
}
// remove the last separator
$rowStr = substr_replace($rowStr,"",-strlen($this->separator));
$str .= $rowStr . $this->lineEnd;
}
$str = substr($str, 0, -strlen($this->lineEnd));
return $str;
}
/**
* Returns the CSV header line for a set of metrics. Will translate columns if desired.
*
* @param array $columnMetrics
* @return array
*/
private function getHeaderLine( $columnMetrics )
{
if ($this->translateColumnNames)
{
$columnMetrics = $this->translateColumnNames($columnMetrics);
}
return implode($this->separator, $columnMetrics);
}
mattpiwik
a validé
protected function formatValue($value)
{
if(is_string($value)
&& !is_numeric($value))
{
$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
}
elseif($value === false)
{
$value = 0;
}
if(is_string($value)
&& (strpos($value, '"') !== false
|| strpos($value, $this->separator) !== false )
)
{
$value = '"'. str_replace('"', '""', $value). '"';
}
// in some number formats (e.g. German), the decimal separator is a comma
// we need to catch and replace this
if (is_numeric($value))
{
$value = (string) $value;
$value = str_replace(',', '.', $value);
}
mattpiwik
a validé
return $value;
}
mattpiwik
a validé
{
$fileName = 'Piwik '.Piwik_Translate('General_Export');
$period = Piwik_Common::getRequestVar('period', false);
$date = Piwik_Common::getRequestVar('date', false);
if ($period || $date) // in test cases, there are no request params set
{
if ($period == 'range')
{
$period = new Piwik_Period_Range($period, $date);
}
else if (strpos($date, ',') !== false)
{
$period = new Piwik_Period_Range('range', $date);
}
else
{
$period = Piwik_Period::factory($period, Piwik_Date::factory($date));
}
$prettyDate = $period->getLocalizedLongString();
$fileName .= ' _ '.$meta['name']
.' _ '.$prettyDate.'.csv';
}
mattpiwik
a validé
// silent fail otherwise unit tests fail
@header('Content-Disposition: attachment; filename="'.$fileName.'"');
mattpiwik
a validé
}