forked from vergnet/site-accueil-insa
531 lines
17 KiB
PHP
531 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* Matomo - free/libre analytics platform
|
|
*
|
|
* @link https://matomo.org
|
|
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*
|
|
*/
|
|
namespace Piwik\DataTable;
|
|
|
|
use Exception;
|
|
use Piwik\Columns\Dimension;
|
|
use Piwik\Common;
|
|
use Piwik\DataTable;
|
|
use Piwik\Metrics;
|
|
use Piwik\Piwik;
|
|
use Piwik\BaseFactory;
|
|
|
|
/**
|
|
* A DataTable Renderer can produce an output given a DataTable object.
|
|
* All new Renderers must be copied in DataTable/Renderer and added to the factory() method.
|
|
* To use a renderer, simply do:
|
|
* $render = new Xml();
|
|
* $render->setTable($dataTable);
|
|
* echo $render;
|
|
*/
|
|
abstract class Renderer extends BaseFactory
|
|
{
|
|
protected $table;
|
|
|
|
/**
|
|
* @var Exception
|
|
*/
|
|
protected $exception;
|
|
protected $renderSubTables = false;
|
|
protected $hideIdSubDatatable = false;
|
|
|
|
/**
|
|
* Whether to translate column names (i.e. metric names) or not
|
|
* @var bool
|
|
*/
|
|
public $translateColumnNames = false;
|
|
|
|
/**
|
|
* Column translations
|
|
* @var array
|
|
*/
|
|
private $columnTranslations = false;
|
|
|
|
/**
|
|
* The API method that has returned the data that should be rendered
|
|
* @var string
|
|
*/
|
|
public $apiMethod = false;
|
|
|
|
/**
|
|
* API metadata for the current report
|
|
* @var array
|
|
*/
|
|
private $apiMetaData = null;
|
|
|
|
/**
|
|
* The current idSite
|
|
* @var int
|
|
*/
|
|
public $idSite = 'all';
|
|
|
|
public function __construct()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Sets whether to render subtables or not
|
|
*
|
|
* @param bool $enableRenderSubTable
|
|
*/
|
|
public function setRenderSubTables($enableRenderSubTable)
|
|
{
|
|
$this->renderSubTables = (bool)$enableRenderSubTable;
|
|
}
|
|
|
|
/**
|
|
* @param bool $bool
|
|
*/
|
|
public function setHideIdSubDatableFromResponse($bool)
|
|
{
|
|
$this->hideIdSubDatatable = (bool)$bool;
|
|
}
|
|
|
|
/**
|
|
* Returns whether to render subtables or not
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function isRenderSubtables()
|
|
{
|
|
return $this->renderSubTables;
|
|
}
|
|
|
|
/**
|
|
* Output HTTP Content-Type header
|
|
*/
|
|
protected function renderHeader()
|
|
{
|
|
Common::sendHeader('Content-Type: text/plain; charset=utf-8');
|
|
}
|
|
|
|
/**
|
|
* Computes the dataTable output and returns the string/binary
|
|
*
|
|
* @return mixed
|
|
*/
|
|
abstract public function render();
|
|
|
|
/**
|
|
* @see render()
|
|
* @return string
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return $this->render();
|
|
}
|
|
|
|
/**
|
|
* Set the DataTable to be rendered
|
|
*
|
|
* @param DataTableInterface $table table to be rendered
|
|
* @throws Exception
|
|
*/
|
|
public function setTable($table)
|
|
{
|
|
if (!is_array($table)
|
|
&& !($table instanceof DataTableInterface)
|
|
) {
|
|
throw new Exception("DataTable renderers renderer accepts only DataTable, Simple and Map instances, and arrays.");
|
|
}
|
|
$this->table = $table;
|
|
}
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected static $availableRenderers = array('xml',
|
|
'json',
|
|
'csv',
|
|
'tsv',
|
|
'html'
|
|
);
|
|
|
|
/**
|
|
* Returns available renderers
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function getRenderers()
|
|
{
|
|
return self::$availableRenderers;
|
|
}
|
|
|
|
protected static function getClassNameFromClassId($id)
|
|
{
|
|
$className = ucfirst(strtolower($id));
|
|
$className = 'Piwik\DataTable\Renderer\\' . $className;
|
|
|
|
return $className;
|
|
}
|
|
|
|
protected static function getInvalidClassIdExceptionMessage($id)
|
|
{
|
|
$availableRenderers = implode(', ', self::getRenderers());
|
|
$klassName = self::getClassNameFromClassId($id);
|
|
|
|
return Piwik::translate('General_ExceptionInvalidRendererFormat', array($klassName, $availableRenderers));
|
|
}
|
|
|
|
/**
|
|
* Format a value to xml
|
|
*
|
|
* @param string|number|bool $value value to format
|
|
* @return int|string
|
|
*/
|
|
public static function formatValueXml($value)
|
|
{
|
|
if (is_string($value)
|
|
&& !is_numeric($value)
|
|
) {
|
|
$value = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
|
|
// make sure non-UTF-8 chars don't cause htmlspecialchars to choke
|
|
if (function_exists('mb_convert_encoding')) {
|
|
$value = @mb_convert_encoding($value, 'UTF-8', 'UTF-8');
|
|
}
|
|
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
|
|
|
|
$htmlentities = array(" ", "¡", "¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€");
|
|
$xmlentities = array("¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€");
|
|
$value = str_replace($htmlentities, $xmlentities, $value);
|
|
} elseif ($value === false) {
|
|
$value = 0;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Translate column names to the current language.
|
|
* Used in subclasses.
|
|
*
|
|
* @param array $names
|
|
* @return array
|
|
*/
|
|
protected function translateColumnNames($names)
|
|
{
|
|
if (!$this->apiMethod) {
|
|
return $names;
|
|
}
|
|
|
|
// load the translations only once
|
|
// when multiple dates are requested (date=...,...&period=day), the meta data would
|
|
// be loaded lots of times otherwise
|
|
if ($this->columnTranslations === false) {
|
|
$meta = $this->getApiMetaData();
|
|
if ($meta === false) {
|
|
return $names;
|
|
}
|
|
|
|
$t = Metrics::getDefaultMetricTranslations();
|
|
foreach (array('metrics', 'processedMetrics', 'metricsGoal', 'processedMetricsGoal') as $index) {
|
|
if (isset($meta[$index]) && is_array($meta[$index])) {
|
|
$t = array_merge($t, $meta[$index]);
|
|
}
|
|
}
|
|
|
|
foreach (Dimension::getAllDimensions() as $dimension) {
|
|
$dimensionId = str_replace('.', '_', $dimension->getId());
|
|
$dimensionName = $dimension->getName();
|
|
|
|
if (!empty($dimensionId) && !empty($dimensionName)) {
|
|
$t[$dimensionId] = $dimensionName;
|
|
}
|
|
}
|
|
|
|
$this->columnTranslations = & $t;
|
|
}
|
|
|
|
foreach ($names as &$name) {
|
|
if (isset($this->columnTranslations[$name])) {
|
|
$name = $this->columnTranslations[$name];
|
|
}
|
|
}
|
|
|
|
return $names;
|
|
}
|
|
|
|
/**
|
|
* @return array|null
|
|
*/
|
|
protected function getApiMetaData()
|
|
{
|
|
if ($this->apiMetaData === null) {
|
|
list($apiModule, $apiAction) = explode('.', $this->apiMethod);
|
|
|
|
if (!$apiModule || !$apiAction) {
|
|
$this->apiMetaData = false;
|
|
}
|
|
|
|
$api = \Piwik\Plugins\API\API::getInstance();
|
|
$meta = $api->getMetadata($this->idSite, $apiModule, $apiAction);
|
|
if (isset($meta[0]) && is_array($meta[0])) {
|
|
$meta = $meta[0];
|
|
}
|
|
|
|
$this->apiMetaData = & $meta;
|
|
}
|
|
|
|
return $this->apiMetaData;
|
|
}
|
|
|
|
/**
|
|
* Translates the given column name
|
|
*
|
|
* @param string $column
|
|
* @return mixed
|
|
*/
|
|
protected function translateColumnName($column)
|
|
{
|
|
$columns = array($column);
|
|
$columns = $this->translateColumnNames($columns);
|
|
return $columns[0];
|
|
}
|
|
|
|
/**
|
|
* Enables column translating
|
|
*
|
|
* @param bool $bool
|
|
*/
|
|
public function setTranslateColumnNames($bool)
|
|
{
|
|
$this->translateColumnNames = $bool;
|
|
}
|
|
|
|
/**
|
|
* Sets the api method
|
|
*
|
|
* @param $method
|
|
*/
|
|
public function setApiMethod($method)
|
|
{
|
|
$this->apiMethod = $method;
|
|
}
|
|
|
|
/**
|
|
* Sets the site id
|
|
*
|
|
* @param int $idSite
|
|
*/
|
|
public function setIdSite($idSite)
|
|
{
|
|
$this->idSite = $idSite;
|
|
}
|
|
|
|
/**
|
|
* Returns true if an array should be wrapped before rendering. This is used to
|
|
* mimic quirks in the old rendering logic (for backwards compatibility). The
|
|
* specific meaning of 'wrap' is left up to the Renderer. For XML, this means a
|
|
* new <row> node. For JSON, this means wrapping in an array.
|
|
*
|
|
* In the old code, arrays were added to new DataTable instances, and then rendered.
|
|
* This transformation wrapped associative arrays except under certain circumstances,
|
|
* including:
|
|
* - single element (ie, array('nb_visits' => 0)) (not wrapped for some renderers)
|
|
* - empty array (ie, array())
|
|
* - array w/ arrays/DataTable instances as values (ie,
|
|
* array('name' => 'myreport',
|
|
* 'reportData' => new DataTable())
|
|
* OR array('name' => 'myreport',
|
|
* 'reportData' => array(...)) )
|
|
*
|
|
* @param array $array
|
|
* @param bool $wrapSingleValues Whether to wrap array('key' => 'value') arrays. Some
|
|
* renderers wrap them and some don't.
|
|
* @param bool|null $isAssociativeArray Whether the array is associative or not.
|
|
* If null, it is determined.
|
|
* @return bool
|
|
*/
|
|
protected static function shouldWrapArrayBeforeRendering(
|
|
$array, $wrapSingleValues = true, $isAssociativeArray = null)
|
|
{
|
|
if (empty($array)) {
|
|
return false;
|
|
}
|
|
|
|
if ($isAssociativeArray === null) {
|
|
$isAssociativeArray = Piwik::isAssociativeArray($array);
|
|
}
|
|
|
|
$wrap = true;
|
|
if ($isAssociativeArray) {
|
|
// we don't wrap if the array has one element that is a value
|
|
$firstValue = reset($array);
|
|
if (!$wrapSingleValues
|
|
&& count($array) === 1
|
|
&& (!is_array($firstValue)
|
|
&& !is_object($firstValue))
|
|
) {
|
|
$wrap = false;
|
|
} else {
|
|
foreach ($array as $value) {
|
|
if (is_array($value)
|
|
|| is_object($value)
|
|
) {
|
|
$wrap = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$wrap = false;
|
|
}
|
|
|
|
return $wrap;
|
|
}
|
|
|
|
/**
|
|
* Produces a flat php array from the DataTable, putting "columns" and "metadata" on the same level.
|
|
*
|
|
* For example, when a originalRender() would be
|
|
* array( 'columns' => array( 'col1_name' => value1, 'col2_name' => value2 ),
|
|
* 'metadata' => array( 'metadata1_name' => value_metadata) )
|
|
*
|
|
* a flatRender() is
|
|
* array( 'col1_name' => value1,
|
|
* 'col2_name' => value2,
|
|
* 'metadata1_name' => value_metadata )
|
|
*
|
|
* @param null|DataTable|DataTable\Map|Simple $dataTable
|
|
* @return array Php array representing the 'flat' version of the datatable
|
|
*/
|
|
protected function convertDataTableToArray($dataTable = null)
|
|
{
|
|
if (is_null($dataTable)) {
|
|
$dataTable = $this->table;
|
|
}
|
|
|
|
if (is_array($dataTable)) {
|
|
$flatArray = $dataTable;
|
|
if (self::shouldWrapArrayBeforeRendering($flatArray)) {
|
|
$flatArray = array($flatArray);
|
|
}
|
|
} elseif ($dataTable instanceof DataTable\Map) {
|
|
$flatArray = array();
|
|
foreach ($dataTable->getDataTables() as $keyName => $table) {
|
|
$flatArray[$keyName] = $this->convertDataTableToArray($table);
|
|
}
|
|
} elseif ($dataTable instanceof Simple) {
|
|
$flatArray = $this->convertSimpleTable($dataTable);
|
|
|
|
reset($flatArray);
|
|
$firstKey = key($flatArray);
|
|
|
|
// if we return only one numeric value then we print out the result in a simple <result> tag
|
|
// keep it simple!
|
|
if (count($flatArray) == 1
|
|
&& $firstKey !== DataTable\Row::COMPARISONS_METADATA_NAME
|
|
) {
|
|
$flatArray = current($flatArray);
|
|
}
|
|
} // A normal DataTable needs to be handled specifically
|
|
else {
|
|
$array = $this->convertTable($dataTable);
|
|
$flatArray = $this->flattenArray($array);
|
|
}
|
|
|
|
return $flatArray;
|
|
}
|
|
|
|
/**
|
|
* Converts the given data table to an array
|
|
*
|
|
* @param DataTable $table
|
|
* @return array
|
|
*/
|
|
protected function convertTable($table)
|
|
{
|
|
$array = [];
|
|
|
|
foreach ($table->getRows() as $id => $row) {
|
|
$newRow = array(
|
|
'columns' => $row->getColumns(),
|
|
'metadata' => $row->getMetadata(),
|
|
'idsubdatatable' => $row->getIdSubDataTable(),
|
|
);
|
|
|
|
if ($id == DataTable::ID_SUMMARY_ROW) {
|
|
$newRow['issummaryrow'] = true;
|
|
}
|
|
|
|
if (isset($newRow['metadata'][DataTable\Row::COMPARISONS_METADATA_NAME])) {
|
|
$newRow['metadata'][DataTable\Row::COMPARISONS_METADATA_NAME] = $row->getComparisons();
|
|
}
|
|
|
|
$subTable = $row->getSubtable();
|
|
if ($this->isRenderSubtables()
|
|
&& $subTable
|
|
) {
|
|
$subTable = $this->convertTable($subTable);
|
|
$newRow['subtable'] = $subTable;
|
|
if ($this->hideIdSubDatatable === false
|
|
&& isset($newRow['metadata']['idsubdatatable_in_db'])
|
|
) {
|
|
$newRow['columns']['idsubdatatable'] = $newRow['metadata']['idsubdatatable_in_db'];
|
|
}
|
|
unset($newRow['metadata']['idsubdatatable_in_db']);
|
|
}
|
|
if ($this->hideIdSubDatatable !== false) {
|
|
unset($newRow['idsubdatatable']);
|
|
}
|
|
|
|
$array[] = $newRow;
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Converts the simple data table to an array
|
|
*
|
|
* @param Simple $table
|
|
* @return array
|
|
*/
|
|
protected function convertSimpleTable($table)
|
|
{
|
|
$array = [];
|
|
|
|
$row = $table->getFirstRow();
|
|
if ($row === false) {
|
|
return $array;
|
|
}
|
|
foreach ($row->getColumns() as $columnName => $columnValue) {
|
|
$array[$columnName] = $columnValue;
|
|
}
|
|
|
|
$comparisons = $row->getComparisons();
|
|
if (!empty($comparisons)) {
|
|
$array[DataTable\Row::COMPARISONS_METADATA_NAME] = $comparisons;
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param array $array
|
|
* @return array
|
|
*/
|
|
protected function flattenArray($array)
|
|
{
|
|
$flatArray = [];
|
|
foreach ($array as $row) {
|
|
$newRow = $row['columns'] + $row['metadata'];
|
|
if (isset($row['idsubdatatable'])
|
|
&& $this->hideIdSubDatatable === false
|
|
) {
|
|
$newRow += array('idsubdatatable' => $row['idsubdatatable']);
|
|
}
|
|
if (isset($row['subtable'])) {
|
|
$newRow += array('subtable' => $this->flattenArray($row['subtable']));
|
|
}
|
|
$flatArray[] = $newRow;
|
|
}
|
|
return $flatArray;
|
|
}
|
|
}
|