update Piwik to version 2.16 (fixes #91)
This commit is contained in:
parent
9abe039dc8
commit
47263617c5
3883 changed files with 418860 additions and 226988 deletions
|
|
@ -1,33 +1,8 @@
|
|||
<Files ~ "\.(php|php4|php5|inc|tpl|in|twig)$">
|
||||
<IfModule mod_access.c>
|
||||
Deny from all
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_access_compat>
|
||||
<IfModule mod_authz_host.c>
|
||||
Deny from all
|
||||
Require all denied
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
<IfModule mod_access_compat>
|
||||
Deny from all
|
||||
Require all denied
|
||||
</IfModule>
|
||||
</Files>
|
||||
<Files ~ "\.(test\.php|gif|ico|jpg|png|svg|js|css|swf)$">
|
||||
<IfModule mod_access.c>
|
||||
Allow from all
|
||||
Require all granted
|
||||
</IfModule>
|
||||
<IfModule !mod_access_compat>
|
||||
<IfModule mod_authz_host.c>
|
||||
Allow from all
|
||||
Require all granted
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
<IfModule mod_access_compat>
|
||||
Allow from all
|
||||
Require all granted
|
||||
</IfModule>
|
||||
Satisfy any
|
||||
<Files "*">
|
||||
<IfVersion < 2.4>
|
||||
Deny from all
|
||||
</IfVersion>
|
||||
<IfVersion >= 2.4>
|
||||
Require all denied
|
||||
</IfVersion>
|
||||
</Files>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,24 +10,29 @@ namespace Piwik\Plugins\API;
|
|||
|
||||
use Piwik\API\Proxy;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Columns\Dimension;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Filter\ColumnDelete;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\IP;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\API\DataTable\MergeDataTables;
|
||||
use Piwik\Plugins\CoreAdminHome\CustomLogo;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
use Piwik\Translate;
|
||||
use Piwik\Translation\Translator;
|
||||
use Piwik\Measurable\Type\TypeManager;
|
||||
use Piwik\Version;
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . '/core/Config.php';
|
||||
|
||||
/**
|
||||
* This API is the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API</a>: it gives information about all other available APIs methods, as well as providing
|
||||
* This API is the <a href='http://piwik.org/docs/analytics-api/metadata/' rel='noreferrer' target='_blank'>Metadata API</a>: it gives information about all other available APIs methods, as well as providing
|
||||
* human readable and more complete outputs than normal API methods.
|
||||
*
|
||||
* Some of the information that is returned by the Metadata API:
|
||||
|
|
@ -40,7 +45,7 @@ require_once PIWIK_INCLUDE_PATH . '/core/Config.php';
|
|||
* <li>the method "getSuggestedValuesForSegment" returns top suggested values for a particular segment. It uses the Live.getLastVisitsDetails API to fetch the most recently used values, and will return the most often used values first.</li>
|
||||
* </ul>
|
||||
* The Metadata API is for example used by the Piwik Mobile App to automatically display all Piwik reports, with translated report & columns names and nicely formatted values.
|
||||
* More information on the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API documentation page</a>
|
||||
* More information on the <a href='http://piwik.org/docs/analytics-api/metadata/' rel='noreferrer' target='_blank'>Metadata API documentation page</a>
|
||||
*
|
||||
* @method static \Piwik\Plugins\API\API getInstance()
|
||||
*/
|
||||
|
|
@ -56,6 +61,18 @@ class API extends \Piwik\Plugin\API
|
|||
return Version::VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most accurate IP address availble for the current user, in
|
||||
* IPv4 format. This could be the proxy client's IP address.
|
||||
*
|
||||
* @return string IP address in presentation format.
|
||||
*/
|
||||
public function getIpFromHeader()
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
return IP::getIpFromHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section [APISettings] if defined in config.ini.php
|
||||
* @return array
|
||||
|
|
@ -71,23 +88,67 @@ class API extends \Piwik\Plugin\API
|
|||
* are not visible in the UI and not present in the API meta data. These columns are
|
||||
* translated here.
|
||||
* @return array
|
||||
* @deprecated since Piwik 2.15.1
|
||||
*/
|
||||
static public function getDefaultMetricTranslations()
|
||||
public function getDefaultMetricTranslations()
|
||||
{
|
||||
return Metrics::getDefaultMetricTranslations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available measurable types.
|
||||
* Marked as deprecated so it won't appear in API page. It won't be a public API for now.
|
||||
* @deprecated
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableMeasurableTypes()
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
|
||||
$typeManager = new TypeManager();
|
||||
$types = $typeManager->getAllTypes();
|
||||
|
||||
$available = array();
|
||||
foreach ($types as $type) {
|
||||
$available[] = array(
|
||||
'id' => $type->getId(),
|
||||
'name' => Piwik::translate($type->getName()),
|
||||
'description' => Piwik::translate($type->getDescription()),
|
||||
'howToSetupUrl' => $type->getHowToSetupUrl()
|
||||
);
|
||||
}
|
||||
|
||||
return $available;
|
||||
}
|
||||
|
||||
public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true)
|
||||
{
|
||||
if (empty($idSites)) {
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
} else {
|
||||
Piwik::checkUserHasViewAccess($idSites);
|
||||
}
|
||||
|
||||
$isNotAnonymous = !Piwik::isUserIsAnonymous();
|
||||
|
||||
$segments = array();
|
||||
foreach (Dimension::getAllDimensions() as $dimension) {
|
||||
foreach ($dimension->getSegments() as $segment) {
|
||||
if ($segment->isRequiresAtLeastViewAccess()) {
|
||||
$segment->setPermission($isNotAnonymous);
|
||||
}
|
||||
|
||||
$segments[] = $segment->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when gathering all available segment dimensions.
|
||||
*
|
||||
*
|
||||
* This event can be used to make new segment dimensions available.
|
||||
*
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
*
|
||||
* public function getSegmentsMetadata(&$segments, $idSites)
|
||||
* {
|
||||
* $segments[] = array(
|
||||
|
|
@ -101,11 +162,11 @@ class API extends \Piwik\Plugin\API
|
|||
* 'permission' => $isAuthenticatedWithViewAccess,
|
||||
* );
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @param array &$dimensions The list of available segment dimensions. Append to this list to add
|
||||
* new segments. Each element in this list must contain the
|
||||
* following information:
|
||||
*
|
||||
*
|
||||
* - **type**: Either `'metric'` or `'dimension'`. `'metric'` means
|
||||
* the value is a numeric and `'dimension'` means it is
|
||||
* a string. Also, `'metric'` values will be displayed
|
||||
|
|
@ -129,121 +190,6 @@ class API extends \Piwik\Plugin\API
|
|||
*/
|
||||
Piwik::postEvent('API.getSegmentDimensionMetadata', array(&$segments, $idSites));
|
||||
|
||||
$isAuthenticatedWithViewAccess = Piwik::isUserHasViewAccess($idSites) && !Piwik::isUserIsAnonymous();
|
||||
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_VisitorID',
|
||||
'segment' => 'visitorId',
|
||||
'acceptedValues' => '34c31e04394bdc63 - any 16 Hexadecimal chars ID, which can be fetched using the Tracking API function getVisitorId()',
|
||||
'sqlSegment' => 'log_visit.idvisitor',
|
||||
'sqlFilterValue' => array('Piwik\Common', 'convertVisitorIdToBin'),
|
||||
'permission' => $isAuthenticatedWithViewAccess,
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('General_Visit') . " ID",
|
||||
'segment' => 'visitId',
|
||||
'acceptedValues' => 'Any integer. ',
|
||||
'sqlSegment' => 'log_visit.idvisit',
|
||||
'permission' => $isAuthenticatedWithViewAccess,
|
||||
);
|
||||
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_VisitorIP',
|
||||
'segment' => 'visitIp',
|
||||
'acceptedValues' => '13.54.122.1. </code>Select IP ranges with notation: <code>visitIp>13.54.122.0;visitIp<13.54.122.255',
|
||||
'sqlSegment' => 'log_visit.location_ip',
|
||||
'sqlFilterValue' => array('Piwik\IP', 'P2N'),
|
||||
'permission' => $isAuthenticatedWithViewAccess,
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_NbActions',
|
||||
'segment' => 'actions',
|
||||
'sqlSegment' => 'log_visit.visit_total_actions',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_NbSearches',
|
||||
'segment' => 'searches',
|
||||
'sqlSegment' => 'log_visit.visit_total_searches',
|
||||
'acceptedValues' => 'To select all visits who used internal Site Search, use: &segment=searches>0',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_ColumnVisitDuration',
|
||||
'segment' => 'visitDuration',
|
||||
'sqlSegment' => 'log_visit.visit_total_time',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('General_VisitType'),
|
||||
'segment' => 'visitorType',
|
||||
'acceptedValues' => 'new, returning, returningCustomer' . ". " . Piwik::translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"'),
|
||||
'sqlSegment' => 'log_visit.visitor_returning',
|
||||
'sqlFilterValue' => function ($type) {
|
||||
return $type == "new" ? 0 : ($type == "returning" ? 1 : 2);
|
||||
}
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_DaysSinceLastVisit',
|
||||
'segment' => 'daysSinceLastVisit',
|
||||
'sqlSegment' => 'log_visit.visitor_days_since_last',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_DaysSinceFirstVisit',
|
||||
'segment' => 'daysSinceFirstVisit',
|
||||
'sqlSegment' => 'log_visit.visitor_days_since_first',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_NumberOfVisits',
|
||||
'segment' => 'visitCount',
|
||||
'sqlSegment' => 'log_visit.visitor_count_visits',
|
||||
);
|
||||
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_VisitConvertedGoal',
|
||||
'segment' => 'visitConverted',
|
||||
'acceptedValues' => '0, 1',
|
||||
'sqlSegment' => 'log_visit.visit_goal_converted',
|
||||
);
|
||||
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('General_EcommerceVisitStatusDesc'),
|
||||
'segment' => 'visitEcommerceStatus',
|
||||
'acceptedValues' => implode(", ", self::$visitEcommerceStatus)
|
||||
. '. ' . Piwik::translate('General_EcommerceVisitStatusEg', '"&segment=visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart"'),
|
||||
'sqlSegment' => 'log_visit.visit_goal_buyer',
|
||||
'sqlFilterValue' => __NAMESPACE__ . '\API::getVisitEcommerceStatus',
|
||||
);
|
||||
|
||||
$segments[] = array(
|
||||
'type' => 'metric',
|
||||
'category' => Piwik::translate('General_Visit'),
|
||||
'name' => 'General_DaysSinceLastEcommerceOrder',
|
||||
'segment' => 'daysSinceLastEcommerceOrder',
|
||||
'sqlSegment' => 'log_visit.visitor_days_since_order',
|
||||
);
|
||||
|
||||
foreach ($segments as &$segment) {
|
||||
$segment['name'] = Piwik::translate($segment['name']);
|
||||
$segment['category'] = Piwik::translate($segment['category']);
|
||||
|
|
@ -252,6 +198,12 @@ class API extends \Piwik\Plugin\API
|
|||
unset($segment['sqlFilter']);
|
||||
unset($segment['sqlFilterValue']);
|
||||
unset($segment['sqlSegment']);
|
||||
|
||||
if (isset($segment['suggestedValuesCallback'])
|
||||
&& !is_string($segment['suggestedValuesCallback'])
|
||||
) {
|
||||
unset($segment['suggestedValuesCallback']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,47 +211,37 @@ class API extends \Piwik\Plugin\API
|
|||
return $segments;
|
||||
}
|
||||
|
||||
static protected $visitEcommerceStatus = array(
|
||||
GoalManager::TYPE_BUYER_NONE => 'none',
|
||||
GoalManager::TYPE_BUYER_ORDERED => 'ordered',
|
||||
GoalManager::TYPE_BUYER_OPEN_CART => 'abandonedCart',
|
||||
GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART => 'orderedThenAbandonedCart',
|
||||
);
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param $segmentName
|
||||
* @param $table
|
||||
* @return array
|
||||
*/
|
||||
static public function getVisitEcommerceStatusFromId($id)
|
||||
protected function getSegmentValuesFromVisitorLog($segmentName, $table)
|
||||
{
|
||||
if (!isset(self::$visitEcommerceStatus[$id])) {
|
||||
throw new \Exception("Unexpected ECommerce status value ");
|
||||
}
|
||||
return self::$visitEcommerceStatus[$id];
|
||||
}
|
||||
// Cleanup data to return the top suggested (non empty) labels for this segment
|
||||
$values = $table->getColumn($segmentName);
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static public function getVisitEcommerceStatus($status)
|
||||
{
|
||||
$id = array_search($status, self::$visitEcommerceStatus);
|
||||
if ($id === false) {
|
||||
throw new \Exception("Invalid 'visitEcommerceStatus' segment value $status");
|
||||
}
|
||||
return $id;
|
||||
|
||||
// Select also flattened keys (custom variables "page" scope, page URLs for one visit, page titles for one visit)
|
||||
$valuesBis = $table->getColumnsStartingWith($segmentName . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP);
|
||||
$values = array_merge($values, $valuesBis);
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function sortSegments($row1, $row2)
|
||||
{
|
||||
$customVarCategory = Piwik::translate('CustomVariables_CustomVariables');
|
||||
|
||||
$columns = array('type', 'category', 'name', 'segment');
|
||||
foreach ($columns as $column) {
|
||||
// Keep segments ordered alphabetically inside categories..
|
||||
$type = -1;
|
||||
if ($column == 'name') $type = 1;
|
||||
|
||||
$compare = $type * strcmp($row1[$column], $row2[$column]);
|
||||
|
||||
// hack so that custom variables "page" are grouped together in the doc
|
||||
if ($row1['category'] == Piwik::translate('CustomVariables_CustomVariables')
|
||||
if ($row1['category'] == $customVarCategory
|
||||
&& $row1['category'] == $row2['category']
|
||||
) {
|
||||
$compare = strcmp($row1['segment'], $row2['segment']);
|
||||
|
|
@ -312,12 +254,12 @@ class API extends \Piwik\Plugin\API
|
|||
return $compare;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the url to application logo (~280x110px)
|
||||
*
|
||||
* @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
|
||||
* @return string
|
||||
* @deprecated since Piwik 2.15.1
|
||||
*/
|
||||
public function getLogoUrl($pathOnly = false)
|
||||
{
|
||||
|
|
@ -330,6 +272,7 @@ class API extends \Piwik\Plugin\API
|
|||
*
|
||||
* @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
|
||||
* @return string
|
||||
* @deprecated since Piwik 2.15.1
|
||||
*/
|
||||
public function getHeaderLogoUrl($pathOnly = false)
|
||||
{
|
||||
|
|
@ -350,7 +293,6 @@ class API extends \Piwik\Plugin\API
|
|||
return $logo->getSVGLogoUrl($pathOnly);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is an SVG Logo available.
|
||||
* @ignore
|
||||
|
|
@ -362,7 +304,6 @@ class API extends \Piwik\Plugin\API
|
|||
return $logo->hasSVGLogo();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads reports metadata, then return the requested one,
|
||||
* matching optional API parameters.
|
||||
|
|
@ -370,7 +311,14 @@ class API extends \Piwik\Plugin\API
|
|||
public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false,
|
||||
$period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
|
||||
{
|
||||
Translate::reloadLanguage($language);
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
if ($language) {
|
||||
/** @var Translator $translator */
|
||||
$translator = StaticContainer::get('Piwik\Translation\Translator');
|
||||
$translator->setCurrentLanguage($language);
|
||||
}
|
||||
|
||||
$reporter = new ProcessedReport();
|
||||
$metadata = $reporter->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc, $showSubtableReports);
|
||||
return $metadata;
|
||||
|
|
@ -390,6 +338,8 @@ class API extends \Piwik\Plugin\API
|
|||
public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false,
|
||||
$showSubtableReports = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSites);
|
||||
|
||||
$reporter = new ProcessedReport();
|
||||
$metadata = $reporter->getReportMetadata($idSites, $period, $date, $hideMetricsDoc, $showSubtableReports);
|
||||
return $metadata;
|
||||
|
|
@ -397,11 +347,14 @@ class API extends \Piwik\Plugin\API
|
|||
|
||||
public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
|
||||
$apiParameters = false, $idGoal = false, $language = false,
|
||||
$showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
|
||||
$showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false,
|
||||
$format_metrics = null, $idDimension = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$reporter = new ProcessedReport();
|
||||
$processed = $reporter->getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment,
|
||||
$apiParameters, $idGoal, $language, $showTimer, $hideMetricsDoc, $idSubtable, $showRawMetrics);
|
||||
$apiParameters, $idGoal, $language, $showTimer, $hideMetricsDoc, $idSubtable, $showRawMetrics, $format_metrics, $idDimension);
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
|
@ -411,6 +364,8 @@ class API extends \Piwik\Plugin\API
|
|||
*/
|
||||
public function get($idSite, $period, $date, $segment = false, $columns = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$columns = Piwik::getArrayFromApiParameter($columns);
|
||||
|
||||
// build columns map for faster checks later on
|
||||
|
|
@ -430,7 +385,8 @@ class API extends \Piwik\Plugin\API
|
|||
&& !empty($reportMeta['metrics'])
|
||||
) {
|
||||
$plugin = $reportMeta['module'];
|
||||
foreach ($reportMeta['metrics'] as $column => $columnTranslation) {
|
||||
$allMetrics = array_merge($reportMeta['metrics'], @$reportMeta['processedMetrics'] ?: array());
|
||||
foreach ($allMetrics as $column => $columnTranslation) {
|
||||
// a metric from this report has been requested
|
||||
if (isset($columnsMap[$column])
|
||||
// or by default, return all metrics
|
||||
|
|
@ -450,60 +406,29 @@ class API extends \Piwik\Plugin\API
|
|||
$className = Request::getClassNameAPI($plugin);
|
||||
$params['columns'] = implode(',', $columns);
|
||||
$dataTable = Proxy::getInstance()->call($className, 'get', $params);
|
||||
|
||||
// make sure the table has all columns
|
||||
$array = ($dataTable instanceof DataTable\Map ? $dataTable->getDataTables() : array($dataTable));
|
||||
foreach ($array as $table) {
|
||||
// we don't support idSites=all&date=DATE1,DATE2
|
||||
if ($table instanceof DataTable) {
|
||||
$firstRow = $table->getFirstRow();
|
||||
if (!$firstRow) {
|
||||
$firstRow = new Row;
|
||||
$table->addRow($firstRow);
|
||||
}
|
||||
foreach ($columns as $column) {
|
||||
if ($firstRow->getColumn($column) === false) {
|
||||
$firstRow->setColumn($column, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dataTable->filter(function (DataTable $table) {
|
||||
$table->clearQueuedFilters();
|
||||
});
|
||||
|
||||
// merge reports
|
||||
if ($mergedDataTable === false) {
|
||||
$mergedDataTable = $dataTable;
|
||||
} else {
|
||||
$this->mergeDataTables($mergedDataTable, $dataTable);
|
||||
$merger = new MergeDataTables();
|
||||
$merger->mergeDataTables($mergedDataTable, $dataTable);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($columnsMap)
|
||||
&& !empty($mergedDataTable)
|
||||
) {
|
||||
$mergedDataTable->queueFilter('ColumnDelete', array(false, array_keys($columnsMap)));
|
||||
}
|
||||
|
||||
return $mergedDataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the columns of two data tables.
|
||||
* Manipulates the first table.
|
||||
*/
|
||||
private function mergeDataTables($table1, $table2)
|
||||
{
|
||||
// handle table arrays
|
||||
if ($table1 instanceof DataTable\Map && $table2 instanceof DataTable\Map) {
|
||||
$subTables2 = $table2->getDataTables();
|
||||
foreach ($table1->getDataTables() as $index => $subTable1) {
|
||||
$subTable2 = $subTables2[$index];
|
||||
$this->mergeDataTables($subTable1, $subTable2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$firstRow1 = $table1->getFirstRow();
|
||||
$firstRow2 = $table2->getFirstRow();
|
||||
if ($firstRow2 instanceof Row) {
|
||||
foreach ($firstRow2->getColumns() as $metric => $value) {
|
||||
$firstRow1->setColumn($metric, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an API report to query (eg. "Referrers.getKeywords", and a Label (eg. "free%20software"),
|
||||
* this function will query the API for the previous days/weeks/etc. and will return
|
||||
|
|
@ -521,15 +446,21 @@ class API extends \Piwik\Plugin\API
|
|||
* @param bool|int $idGoal
|
||||
* @param bool|string $legendAppendMetric
|
||||
* @param bool|string $labelUseAbsoluteUrl
|
||||
* @param bool|int $idDimension
|
||||
* @return array
|
||||
*/
|
||||
public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true)
|
||||
public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true, $idDimension = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$rowEvolution = new RowEvolution();
|
||||
return $rowEvolution->getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label, $segment, $column,
|
||||
$language, $idGoal, $legendAppendMetric, $labelUseAbsoluteUrl);
|
||||
$language, $idGoal, $legendAppendMetric, $labelUseAbsoluteUrl, $idDimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getLastDate($date, $period)
|
||||
{
|
||||
$lastDate = Range::getLastDate($date, $period);
|
||||
|
|
@ -554,12 +485,38 @@ class API extends \Piwik\Plugin\API
|
|||
|
||||
$result = array();
|
||||
foreach ($urls as $url) {
|
||||
$req = new Request($url . '&format=php&serialize=0');
|
||||
$params = Request::getRequestArrayFromString($url . '&format=php&serialize=0');
|
||||
|
||||
if (isset($params['urls']) && $params['urls'] == $urls) {
|
||||
// by default 'urls' is added to $params as Request::getRequestArrayFromString adds all $_GET/$_POST
|
||||
// default parameters
|
||||
unset($params['urls']);
|
||||
}
|
||||
|
||||
if (!empty($params['segment']) && strpos($url, 'segment=') > -1) {
|
||||
// only unsanitize input when segment is actually present in URL, not when it was used from
|
||||
// $defaultRequest in Request::getRequestArrayFromString from $_GET/$_POST
|
||||
$params['segment'] = urlencode(Common::unsanitizeInputValue($params['segment']));
|
||||
}
|
||||
|
||||
$req = new Request($params);
|
||||
$result[] = $req->process();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if plugin is activated, false otherwise
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @return bool
|
||||
*/
|
||||
public function isPluginActivated($pluginName)
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
return \Piwik\Plugin\Manager::getInstance()->isPluginActivated($pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a segment, will return a list of the most used values for this particular segment.
|
||||
* @param $segmentName
|
||||
|
|
@ -569,8 +526,63 @@ class API extends \Piwik\Plugin\API
|
|||
*/
|
||||
public function getSuggestedValuesForSegment($segmentName, $idSite)
|
||||
{
|
||||
if (empty(Config::getInstance()->General['enable_segment_suggested_values'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$maxSuggestionsToReturn = 30;
|
||||
$segment = $this->findSegment($segmentName, $idSite);
|
||||
|
||||
// if segment has suggested values callback then return result from it instead
|
||||
$suggestedValuesCallbackRequiresTable = false;
|
||||
if (isset($segment['suggestedValuesCallback'])) {
|
||||
$suggestedValuesCallbackRequiresTable = $this->doesSuggestedValuesCallbackNeedData(
|
||||
$segment['suggestedValuesCallback']);
|
||||
|
||||
if (!$suggestedValuesCallbackRequiresTable) {
|
||||
return call_user_func($segment['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn);
|
||||
}
|
||||
}
|
||||
|
||||
// if period=range is disabled, do not proceed
|
||||
if (!Period\Factory::isPeriodEnabledForAPI('range')) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!empty($segment['unionOfSegments'])) {
|
||||
$values = array();
|
||||
foreach ($segment['unionOfSegments'] as $unionSegmentName) {
|
||||
$unionSegment = $this->findSegment($unionSegmentName, $idSite);
|
||||
|
||||
try {
|
||||
$result = $this->getSuggestedValuesForSegmentName($idSite, $unionSegment, $maxSuggestionsToReturn);
|
||||
if (!empty($result)) {
|
||||
$values = array_merge($result, $values);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// we ignore if there was no data found for $unionSegmentName
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($values)) {
|
||||
throw new \Exception("There was no data to suggest for $segmentName");
|
||||
}
|
||||
|
||||
} else {
|
||||
$values = $this->getSuggestedValuesForSegmentName($idSite, $segment, $maxSuggestionsToReturn);
|
||||
}
|
||||
|
||||
$values = $this->getMostFrequentValues($values);
|
||||
$values = array_slice($values, 0, $maxSuggestionsToReturn);
|
||||
$values = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $values);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function findSegment($segmentName, $idSite)
|
||||
{
|
||||
$segmentsMetadata = $this->getSegmentsMetadata($idSite, $_hideImplementationData = false);
|
||||
|
||||
$segmentFound = false;
|
||||
|
|
@ -580,11 +592,17 @@ class API extends \Piwik\Plugin\API
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($segmentFound)) {
|
||||
throw new \Exception("Requested segment not found.");
|
||||
throw new \Exception("Requested segment $segmentName not found.");
|
||||
}
|
||||
|
||||
$startDate = Date::now()->subDay(60)->toString();
|
||||
return $segmentFound;
|
||||
}
|
||||
|
||||
private function getSuggestedValuesForSegmentName($idSite, $segment, $maxSuggestionsToReturn)
|
||||
{
|
||||
$startDate = Date::now()->subDay(60)->toString();
|
||||
$requestLastVisits = "method=Live.getLastVisitsDetails
|
||||
&idSite=$idSite
|
||||
&period=range
|
||||
|
|
@ -593,40 +611,61 @@ class API extends \Piwik\Plugin\API
|
|||
&serialize=0
|
||||
&flat=1";
|
||||
|
||||
$segmentName = $segment['segment'];
|
||||
|
||||
// Select non empty fields only
|
||||
// Note: this optimization has only a very minor impact
|
||||
$requestLastVisits .= "&segment=$segmentName" . urlencode('!=');
|
||||
|
||||
// By default Live fetches all actions for all visitors, but we'd rather do this only when required
|
||||
if ($this->doesSegmentNeedActionsData($segmentName)) {
|
||||
$requestLastVisits .= "&filter_limit=500";
|
||||
$requestLastVisits .= "&filter_limit=400";
|
||||
} else {
|
||||
$requestLastVisits .= "&doNotFetchActions=1";
|
||||
$requestLastVisits .= "&filter_limit=1000";
|
||||
$requestLastVisits .= "&filter_limit=800";
|
||||
}
|
||||
|
||||
$request = new Request($requestLastVisits);
|
||||
$table = $request->process();
|
||||
|
||||
if (empty($table)) {
|
||||
throw new \Exception("There was no data to suggest for $segmentName");
|
||||
}
|
||||
|
||||
// Cleanup data to return the top suggested (non empty) labels for this segment
|
||||
$values = $table->getColumn($segmentName);
|
||||
|
||||
// Select also flattened keys (custom variables "page" scope, page URLs for one visit, page titles for one visit)
|
||||
$valuesBis = $table->getColumnsStartingWith($segmentName . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP);
|
||||
$values = array_merge($values, $valuesBis);
|
||||
|
||||
$values = $this->getMostFrequentValues($values);
|
||||
|
||||
$values = array_slice($values, 0, $maxSuggestionsToReturn);
|
||||
|
||||
$values = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $values);
|
||||
if (isset($segment['suggestedValuesCallback']) &&
|
||||
$this->doesSuggestedValuesCallbackNeedData($segment['suggestedValuesCallback'])) {
|
||||
$values = call_user_func($segment['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn, $table);
|
||||
} else {
|
||||
$values = $this->getSegmentValuesFromVisitorLog($segmentName, $table);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* A glossary of all reports and their definition
|
||||
*
|
||||
* @param $idSite
|
||||
* @return array
|
||||
*/
|
||||
public function getGlossaryReports($idSite)
|
||||
{
|
||||
$glossary = StaticContainer::get('Piwik\Plugins\API\Glossary');
|
||||
return $glossary->reportsGlossary($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* A glossary of all metrics and their definition
|
||||
*
|
||||
* @param $idSite
|
||||
* @return array
|
||||
*/
|
||||
public function getGlossaryMetrics($idSite)
|
||||
{
|
||||
$glossary = StaticContainer::get('Piwik\Plugins\API\Glossary');
|
||||
return $glossary->metricsGlossary($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $segmentName
|
||||
* @return bool
|
||||
|
|
@ -636,10 +675,13 @@ class API extends \Piwik\Plugin\API
|
|||
// If you update this, also update flattenVisitorDetailsArray
|
||||
$segmentsNeedActionsInfo = array('visitConvertedGoalId',
|
||||
'pageUrl', 'pageTitle', 'siteSearchKeyword',
|
||||
'entryPageTitle', 'entryPageUrl', 'exitPageTitle', 'exitPageUrl');
|
||||
'entryPageTitle', 'entryPageUrl', 'exitPageTitle', 'exitPageUrl',
|
||||
'outlinkUrl', 'downloadUrl'
|
||||
);
|
||||
$isCustomVariablePage = stripos($segmentName, 'customVariablePage') !== false;
|
||||
$isEventSegment = stripos($segmentName, 'event') !== false;
|
||||
$doesSegmentNeedActionsInfo = in_array($segmentName, $segmentsNeedActionsInfo) || $isCustomVariablePage || $isEventSegment;
|
||||
$isContentSegment = stripos($segmentName, 'content') !== false;
|
||||
$doesSegmentNeedActionsInfo = in_array($segmentName, $segmentsNeedActionsInfo) || $isCustomVariablePage || $isEventSegment || $isContentSegment;
|
||||
return $doesSegmentNeedActionsInfo;
|
||||
}
|
||||
|
||||
|
|
@ -666,6 +708,23 @@ class API extends \Piwik\Plugin\API
|
|||
$values = array_keys($values);
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function doesSuggestedValuesCallbackNeedData($suggestedValuesCallback)
|
||||
{
|
||||
if (is_string($suggestedValuesCallback)
|
||||
&& strpos($suggestedValuesCallback, '::') !== false
|
||||
) {
|
||||
$suggestedValuesCallback = explode('::', $suggestedValuesCallback);
|
||||
}
|
||||
|
||||
if (is_array($suggestedValuesCallback)) {
|
||||
$methodMetadata = new \ReflectionMethod($suggestedValuesCallback[0], $suggestedValuesCallback[1]);
|
||||
} else {
|
||||
$methodMetadata = new \ReflectionFunction($suggestedValuesCallback);
|
||||
}
|
||||
|
||||
return $methodMetadata->getNumberOfParameters() >= 3;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -679,43 +738,15 @@ class Plugin extends \Piwik\Plugin
|
|||
}
|
||||
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
* @see Piwik\Plugin::registerEvents
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
public function registerEvents()
|
||||
{
|
||||
return array(
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'Menu.Top.addItems' => 'addTopMenu',
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles'
|
||||
);
|
||||
}
|
||||
|
||||
public function addTopMenu()
|
||||
{
|
||||
$apiUrlParams = array('module' => 'API', 'action' => 'listAllAPI', 'segment' => false);
|
||||
$tooltip = Piwik::translate('API_TopLinkTooltip');
|
||||
|
||||
MenuTop::addEntry('General_API', $apiUrlParams, true, 7, $isHTML = false, $tooltip);
|
||||
|
||||
$this->addTopMenuMobileApp();
|
||||
}
|
||||
|
||||
protected function addTopMenuMobileApp()
|
||||
{
|
||||
if (empty($_SERVER['HTTP_USER_AGENT'])) {
|
||||
return;
|
||||
}
|
||||
if (!class_exists("DeviceDetector")) {
|
||||
throw new \Exception("DeviceDetector could not be found, maybe you are using Piwik from git and need to have update Composer. <br>php composer.phar update");
|
||||
}
|
||||
|
||||
$ua = new \DeviceDetector($_SERVER['HTTP_USER_AGENT']);
|
||||
$ua->parse();
|
||||
$os = $ua->getOs('short_name');
|
||||
if ($os && in_array($os, array('AND', 'IOS'))) {
|
||||
MenuTop::addEntry('Piwik Mobile App', array('module' => 'Proxy', 'action' => 'redirect', 'url' => 'http://piwik.org/mobile/'), true, 4);
|
||||
}
|
||||
}
|
||||
|
||||
public function getStylesheetFiles(&$stylesheets)
|
||||
{
|
||||
$stylesheets[] = "plugins/API/stylesheets/listAllAPI.less";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -14,6 +14,8 @@ use Piwik\API\Request;
|
|||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Report;
|
||||
use Piwik\Url;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
|
|
@ -23,12 +25,22 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
{
|
||||
function index()
|
||||
{
|
||||
$token = 'token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string');
|
||||
|
||||
// when calling the API through http, we limit the number of returned results
|
||||
if (!isset($_GET['filter_limit'])) {
|
||||
$_GET['filter_limit'] = Config::getInstance()->General['API_datatable_default_limit'];
|
||||
$token .= '&api_datatable_default_limit=' . $_GET['filter_limit'];
|
||||
}
|
||||
$request = new Request('token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string'));
|
||||
return $request->process();
|
||||
|
||||
$request = new Request($token);
|
||||
$response = $request->process();
|
||||
|
||||
if (is_array($response)) {
|
||||
$response = var_export($response, true);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function listAllMethods()
|
||||
|
|
@ -58,7 +70,7 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
foreach ($segments as $segment) {
|
||||
// Eg. Event Value is a metric, not in the Visit metric category,
|
||||
// we make sure it is displayed along with the Events dimensions
|
||||
if($segment['type'] == 'metric' && $segment['category'] != Piwik::translate('General_Visit')) {
|
||||
if ($segment['type'] == 'metric' && $segment['category'] != Piwik::translate('General_Visit')) {
|
||||
$segment['type'] = 'dimension';
|
||||
}
|
||||
|
||||
|
|
@ -125,4 +137,14 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
</table>
|
||||
";
|
||||
}
|
||||
|
||||
public function glossary()
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
|
||||
return $this->renderTemplate('glossary', array(
|
||||
'reports' => Request::processRequest('API', array('method' => 'API.getGlossaryReports')),
|
||||
'metrics' => Request::processRequest('API', array('method' => 'API.getGlossaryMetrics')),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
www/analytics/plugins/API/DataTable/MergeDataTables.php
Normal file
55
www/analytics/plugins/API/DataTable/MergeDataTables.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\DataTable;
|
||||
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
|
||||
class MergeDataTables
|
||||
{
|
||||
|
||||
/**
|
||||
* Merge the columns of two data tables.
|
||||
* Manipulates the first table.
|
||||
*
|
||||
* @param DataTable|DataTable\Map $table1 The table to eventually filter.
|
||||
* @param DataTable|DataTable\Map $table2 Whether to delete rows with no visits or not.
|
||||
*/
|
||||
public function mergeDataTables($table1, $table2)
|
||||
{
|
||||
// handle table arrays
|
||||
if ($table1 instanceof DataTable\Map && $table2 instanceof DataTable\Map) {
|
||||
$subTables2 = $table2->getDataTables();
|
||||
foreach ($table1->getDataTables() as $index => $subTable1) {
|
||||
if (!array_key_exists($index, $subTables2)) {
|
||||
// occurs when archiving starts on dayN and continues into dayN+1, see https://github.com/piwik/piwik/issues/5168#issuecomment-50959925
|
||||
continue;
|
||||
}
|
||||
$subTable2 = $subTables2[$index];
|
||||
$this->mergeDataTables($subTable1, $subTable2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$firstRow2 = $table2->getFirstRow();
|
||||
if (!($firstRow2 instanceof Row)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$firstRow1 = $table1->getFirstRow();
|
||||
if (empty($firstRow1)) {
|
||||
$firstRow1 = $table1->addRow(new Row());
|
||||
}
|
||||
|
||||
foreach ($firstRow2->getColumns() as $metric => $value) {
|
||||
$firstRow1->setColumn($metric, $value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
108
www/analytics/plugins/API/Glossary.php
Normal file
108
www/analytics/plugins/API/Glossary.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\API;
|
||||
|
||||
class Glossary
|
||||
{
|
||||
protected $metadata = array();
|
||||
|
||||
public function __construct(API $api)
|
||||
{
|
||||
$this->api = $api;
|
||||
}
|
||||
|
||||
public function reportsGlossary($idSite)
|
||||
{
|
||||
$metadata = $this->api->getReportMetadata($idSite);
|
||||
|
||||
$reports = array();
|
||||
foreach ($metadata as $report) {
|
||||
if (isset($report['documentation'])) {
|
||||
$reports[] = array(
|
||||
'name' => sprintf("%s (%s)", $report['name'], $report['category']),
|
||||
'documentation' => $report['documentation']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
usort($reports, function ($a, $b) {
|
||||
return strcmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
return $reports;
|
||||
}
|
||||
|
||||
public function metricsGlossary($idSite)
|
||||
{
|
||||
$metadata = $this->api->getReportMetadata($idSite);
|
||||
|
||||
$metrics = array();
|
||||
foreach ($metadata as $report) {
|
||||
if (!isset($report['metricsDocumentation'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($report['metricsDocumentation'] as $metricId => $metricDocumentation) {
|
||||
|
||||
$metricKey = $metricId;
|
||||
|
||||
if(empty($report['metrics'][$metricId])
|
||||
&& empty($report['processedMetrics'][$metricId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metricName = isset($report['metrics'][$metricId]) ? $report['metrics'][$metricId] : $report['processedMetrics'][$metricId];
|
||||
|
||||
|
||||
// Already one metric with same name, but different documentation...
|
||||
if (isset($metrics[$metricKey])
|
||||
&& $metrics[$metricKey]['documentation'] !== $metricDocumentation) {
|
||||
|
||||
// Don't show nb_hits in glossary since it duplicates others, eg. nb_downloads,
|
||||
if($metricKey == 'nb_hits') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metricName = sprintf("%s (%s)", $metricName, $report['category']);
|
||||
$metricKey = $metricName;
|
||||
|
||||
if (isset($metrics[$metricKey]) && $metrics[$metricKey]['documentation'] !== $metricDocumentation) {
|
||||
throw new \Exception(sprintf("Metric %s has two different documentations: \n(1) %s \n(2) %s",
|
||||
$metricKey,
|
||||
$metrics[$metricKey]['documentation'],
|
||||
$metricDocumentation)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!isset($report['metrics'][$metricId])
|
||||
&& !isset($report['processedMetrics'][$metricId])
|
||||
) {
|
||||
// $metricId metric name not found in $report['dimension'] report
|
||||
// it will be set in another one
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$metrics[$metricKey] = array(
|
||||
'name' => $metricName,
|
||||
'id' => $metricId,
|
||||
'documentation' => $metricDocumentation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
usort($metrics, function ($a, $b) {
|
||||
return strcmp($a['name'], $b['name']);
|
||||
});
|
||||
return $metrics;
|
||||
}
|
||||
}
|
||||
67
www/analytics/plugins/API/Menu.php
Normal file
67
www/analytics/plugins/API/Menu.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API;
|
||||
|
||||
use Piwik\DeviceDetectorCache;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Menu\MenuUser;
|
||||
use Piwik\Piwik;
|
||||
use DeviceDetector\Parser\OperatingSystem;
|
||||
|
||||
class Menu extends \Piwik\Plugin\Menu
|
||||
{
|
||||
const DD_SHORT_NAME_ANDROID = 'AND';
|
||||
const DD_SHORT_NAME_IOS = 'IOS';
|
||||
|
||||
public function configureTopMenu(MenuTop $menu)
|
||||
{
|
||||
$this->addTopMenuMobileApp($menu);
|
||||
}
|
||||
|
||||
public function configureUserMenu(MenuUser $menu)
|
||||
{
|
||||
$menu->addPlatformItem('General_API',
|
||||
$this->urlForAction('listAllAPI', array('segment' => false)),
|
||||
6,
|
||||
Piwik::translate('API_TopLinkTooltip')
|
||||
);
|
||||
|
||||
if(Piwik::isUserIsAnonymous()) {
|
||||
$menu->addPlatformItem('API_Glossary',
|
||||
$this->urlForAction('glossary', array('segment' => false)),
|
||||
50
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function addTopMenuMobileApp(MenuTop $menu)
|
||||
{
|
||||
if (empty($_SERVER['HTTP_USER_AGENT'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!class_exists("DeviceDetector\\DeviceDetector")) {
|
||||
throw new \Exception("DeviceDetector could not be found, maybe you are using Piwik from git and need to update Composer. Execute this command: php composer.phar update");
|
||||
}
|
||||
|
||||
$ua = new OperatingSystem($_SERVER['HTTP_USER_AGENT']);
|
||||
$ua->setCache(new DeviceDetectorCache(86400));
|
||||
$parsedOS = $ua->parse();
|
||||
|
||||
if (!empty($parsedOS['short_name']) && in_array($parsedOS['short_name'], array(self::DD_SHORT_NAME_ANDROID, self::DD_SHORT_NAME_IOS))) {
|
||||
|
||||
$url = $this->urlForModuleAction('Proxy', 'redirect', array('url' => 'http://piwik.org/mobile/'));
|
||||
|
||||
if ($url) {
|
||||
$menu->addItem('Piwik Mobile App', null, $url, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -11,22 +11,24 @@ namespace Piwik\Plugins\API;
|
|||
use Exception;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Archive\DataTableFactory;
|
||||
use Piwik\CacheId;
|
||||
use Piwik\Cache as PiwikCache;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable\Simple;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\Metrics\Formatter;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Report;
|
||||
use Piwik\Site;
|
||||
use Piwik\Timer;
|
||||
use Piwik\Url;
|
||||
|
||||
class ProcessedReport
|
||||
{
|
||||
|
||||
/**
|
||||
* Loads reports metadata, then return the requested one,
|
||||
* matching optional API parameters.
|
||||
|
|
@ -38,8 +40,10 @@ class ProcessedReport
|
|||
|
||||
foreach ($reportsMetadata as $report) {
|
||||
// See ArchiveProcessor/Aggregator.php - unique visitors are not processed for period != day
|
||||
// todo: should use SettingsPiwik::isUniqueVisitorsEnabled instead
|
||||
if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) {
|
||||
unset($report['metrics']['nb_uniq_visitors']);
|
||||
unset($report['metrics']['nb_users']);
|
||||
}
|
||||
if ($report['module'] == $apiModule
|
||||
&& $report['action'] == $apiAction
|
||||
|
|
@ -148,31 +152,45 @@ class ProcessedReport
|
|||
Piwik::checkUserHasViewAccess($idSites);
|
||||
}
|
||||
|
||||
// as they cache key contains a lot of information there would be an even better cache result by caching parts of
|
||||
// this huge method separately but that makes it also more complicated. leaving it like this for now.
|
||||
$key = $this->buildReportMetadataCacheKey($idSites, $period, $date, $hideMetricsDoc, $showSubtableReports);
|
||||
$key = CacheId::pluginAware($key);
|
||||
$cache = PiwikCache::getTransientCache();
|
||||
|
||||
if ($cache->contains($key)) {
|
||||
return $cache->fetch($key);
|
||||
}
|
||||
|
||||
$parameters = array('idSites' => $idSites, 'period' => $period, 'date' => $date);
|
||||
|
||||
$availableReports = array();
|
||||
|
||||
foreach (Report::getAllReports() as $report) {
|
||||
$report->configureReportMetadata($availableReports, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when gathering metadata for all available reports.
|
||||
*
|
||||
*
|
||||
* Plugins that define new reports should use this event to make them available in via
|
||||
* the metadata API. By doing so, the report will become available in scheduled reports
|
||||
* as well as in the Piwik Mobile App. In fact, any third party app that uses the metadata
|
||||
* API will automatically have access to the new report.
|
||||
*
|
||||
*
|
||||
* @param string &$availableReports The list of available reports. Append to this list
|
||||
* to make a report available.
|
||||
*
|
||||
*
|
||||
* Every element of this array must contain the following
|
||||
* information:
|
||||
*
|
||||
*
|
||||
* - **category**: A translated string describing the report's category.
|
||||
* - **name**: The translated display title of the report.
|
||||
* - **module**: The plugin of the report.
|
||||
* - **action**: The API method that serves the report.
|
||||
*
|
||||
*
|
||||
* The following information is optional:
|
||||
*
|
||||
*
|
||||
* - **dimension**: The report's [dimension](/guides/all-about-analytics-data#dimensions) if any.
|
||||
* - **metrics**: An array mapping metric names with their display names.
|
||||
* - **metricsDocumentation**: An array mapping metric names with their
|
||||
|
|
@ -183,25 +201,31 @@ class ProcessedReport
|
|||
* metrics.
|
||||
* - **order**: The order of the report in the list of reports
|
||||
* with the same category.
|
||||
*
|
||||
*
|
||||
* @param array $parameters Contains the values of the sites and period we are
|
||||
* getting reports for. Some reports depend on this data.
|
||||
* For example, Goals reports depend on the site IDs being
|
||||
* requested. Contains the following information:
|
||||
*
|
||||
*
|
||||
* - **idSites**: The array of site IDs we are getting reports for.
|
||||
* - **period**: The period type, eg, `'day'`, `'week'`, `'month'`,
|
||||
* `'year'`, `'range'`.
|
||||
* - **date**: A string date within the period or a date range, eg,
|
||||
* `'2013-01-01'` or `'2012-01-01,2013-01-01'`.
|
||||
*
|
||||
*
|
||||
* TODO: put dimensions section in all about analytics data
|
||||
* @deprecated since 2.5.0 Use Report Classes instead.
|
||||
* @ignore
|
||||
*/
|
||||
Piwik::postEvent('API.getReportMetadata', array(&$availableReports, $parameters));
|
||||
|
||||
// TODO we can remove this one once we remove API.getReportMetadata event (except hideMetricsDoc)
|
||||
foreach ($availableReports as &$availableReport) {
|
||||
// can be removed once we remove hook API.getReportMetadata
|
||||
if (!isset($availableReport['metrics'])) {
|
||||
$availableReport['metrics'] = Metrics::getDefaultMetrics();
|
||||
}
|
||||
// can be removed once we remove hook API.getReportMetadata
|
||||
if (!isset($availableReport['processedMetrics'])) {
|
||||
$availableReport['processedMetrics'] = Metrics::getDefaultProcessedMetrics();
|
||||
}
|
||||
|
|
@ -211,24 +235,25 @@ class ProcessedReport
|
|||
unset($availableReport['metricsDocumentation']);
|
||||
} else if (!isset($availableReport['metricsDocumentation'])) {
|
||||
// set metric documentation to default if it's not set
|
||||
// can be removed once we remove hook API.getReportMetadata
|
||||
$availableReport['metricsDocumentation'] = Metrics::getDefaultMetricsDocumentation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered after all available reports are collected.
|
||||
*
|
||||
*
|
||||
* This event can be used to modify the report metadata of reports in other plugins. You
|
||||
* could, for example, add custom metrics to every report or remove reports from the list
|
||||
* of available reports.
|
||||
*
|
||||
*
|
||||
* @param array &$availableReports List of all report metadata. Read the {@hook API.getReportMetadata}
|
||||
* docs to see what this array contains.
|
||||
* @param array $parameters Contains the values of the sites and period we are
|
||||
* getting reports for. Some report depend on this data.
|
||||
* For example, Goals reports depend on the site IDs being
|
||||
* request. Contains the following information:
|
||||
*
|
||||
*
|
||||
* - **idSites**: The array of site IDs we are getting reports for.
|
||||
* - **period**: The period type, eg, `'day'`, `'week'`, `'month'`,
|
||||
* `'year'`, `'range'`.
|
||||
|
|
@ -238,16 +263,17 @@ class ProcessedReport
|
|||
Piwik::postEvent('API.getReportMetadata.end', array(&$availableReports, $parameters));
|
||||
|
||||
// Sort results to ensure consistent order
|
||||
usort($availableReports, array($this, 'sort'));
|
||||
|
||||
// Add the magic API.get report metadata aggregating all plugins API.get API calls automatically
|
||||
$this->addApiGetMetdata($availableReports);
|
||||
usort($availableReports, array('self', 'sortReports'));
|
||||
|
||||
$knownMetrics = array_merge(Metrics::getDefaultMetrics(), Metrics::getDefaultProcessedMetrics());
|
||||
$columnsToKeep = $this->getColumnsToKeep();
|
||||
$columnsToRemove = $this->getColumnsToRemove();
|
||||
|
||||
foreach ($availableReports as &$availableReport) {
|
||||
// Ensure all metrics have a translation
|
||||
$metrics = $availableReport['metrics'];
|
||||
$cleanedMetrics = array();
|
||||
// TODO we can remove this once we remove the getReportMetadata event, leaving it here for backwards compatibility
|
||||
foreach ($metrics as $metricId => $metricTranslation) {
|
||||
// When simply the column name was given, ie 'metric' => array( 'nb_visits' )
|
||||
// $metricTranslation is in this case nb_visits. We look for a known translation.
|
||||
|
|
@ -262,13 +288,13 @@ class ProcessedReport
|
|||
$availableReport['metrics'] = $cleanedMetrics;
|
||||
|
||||
// if hide/show columns specified, hide/show metrics & docs
|
||||
$availableReport['metrics'] = $this->hideShowMetrics($availableReport['metrics']);
|
||||
$availableReport['metrics'] = $this->hideShowMetricsWithParams($availableReport['metrics'], $columnsToRemove, $columnsToKeep);
|
||||
if (isset($availableReport['processedMetrics'])) {
|
||||
$availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']);
|
||||
$availableReport['processedMetrics'] = $this->hideShowMetricsWithParams($availableReport['processedMetrics'], $columnsToRemove, $columnsToKeep);
|
||||
}
|
||||
if (isset($availableReport['metricsDocumentation'])) {
|
||||
$availableReport['metricsDocumentation'] =
|
||||
$this->hideShowMetrics($availableReport['metricsDocumentation']);
|
||||
$this->hideShowMetricsWithParams($availableReport['metricsDocumentation'], $columnsToRemove, $columnsToKeep);
|
||||
}
|
||||
|
||||
// Remove array elements that are false (to clean up API output)
|
||||
|
|
@ -278,6 +304,7 @@ class ProcessedReport
|
|||
}
|
||||
}
|
||||
// when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum
|
||||
// TODO we should remove this once we remove the getReportMetadata event, leaving it here for backwards compatibility
|
||||
if (isset($availableReport['metricsGoal'])) {
|
||||
unset($availableReport['processedMetrics']['conversion_rate']);
|
||||
unset($availableReport['metricsGoal']['conversion_rate']);
|
||||
|
|
@ -306,93 +333,79 @@ class ProcessedReport
|
|||
}
|
||||
}
|
||||
|
||||
return array_values($availableReports); // make sure array has contiguous key values
|
||||
$actualReports = array_values($availableReports);
|
||||
$cache->save($key, $actualReports);
|
||||
|
||||
return $actualReports; // make sure array has contiguous key values
|
||||
}
|
||||
|
||||
/**
|
||||
* API metadata are sorted by category/name,
|
||||
* with a little tweak to replicate the standard Piwik category ordering
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
* @return int
|
||||
*/
|
||||
private function sort($a, $b)
|
||||
private static function sortReports($a, $b)
|
||||
{
|
||||
static $order = null;
|
||||
if (is_null($order)) {
|
||||
$order = array(
|
||||
Piwik::translate('General_MultiSitesSummary'),
|
||||
Piwik::translate('VisitsSummary_VisitsSummary'),
|
||||
Piwik::translate('Goals_Ecommerce'),
|
||||
Piwik::translate('General_Actions'),
|
||||
Piwik::translate('Events_Events'),
|
||||
Piwik::translate('Actions_SubmenuSitesearch'),
|
||||
Piwik::translate('Referrers_Referrers'),
|
||||
Piwik::translate('Goals_Goals'),
|
||||
Piwik::translate('General_Visitors'),
|
||||
Piwik::translate('DevicesDetection_DevicesDetection'),
|
||||
Piwik::translate('UserSettings_VisitorSettings'),
|
||||
);
|
||||
}
|
||||
return ($category = strcmp(array_search($a['category'], $order), array_search($b['category'], $order))) == 0
|
||||
? (@$a['order'] < @$b['order'] ? -1 : 1)
|
||||
: $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the metadata for the API.get report
|
||||
* In other plugins, this would hook on 'API.getReportMetadata'
|
||||
*/
|
||||
private function addApiGetMetdata(&$availableReports)
|
||||
{
|
||||
$metadata = array(
|
||||
'category' => Piwik::translate('General_API'),
|
||||
'name' => Piwik::translate('General_MainMetrics'),
|
||||
'module' => 'API',
|
||||
'action' => 'get',
|
||||
'metrics' => array(),
|
||||
'processedMetrics' => array(),
|
||||
'metricsDocumentation' => array(),
|
||||
'order' => 1
|
||||
);
|
||||
|
||||
$indexesToMerge = array('metrics', 'processedMetrics', 'metricsDocumentation');
|
||||
|
||||
foreach ($availableReports as $report) {
|
||||
if ($report['action'] == 'get') {
|
||||
foreach ($indexesToMerge as $index) {
|
||||
if (isset($report[$index])
|
||||
&& is_array($report[$index])
|
||||
) {
|
||||
$metadata[$index] = array_merge($metadata[$index], $report[$index]);
|
||||
}
|
||||
}
|
||||
$order = array();
|
||||
foreach (Report::$orderOfReports as $category) {
|
||||
$order[] = Piwik::translate($category);
|
||||
}
|
||||
}
|
||||
|
||||
$availableReports[] = $metadata;
|
||||
$posA = array_search($a['category'], $order);
|
||||
$posB = array_search($b['category'], $order);
|
||||
|
||||
if ($posA === false && $posB === false) {
|
||||
return strcmp($a['category'], $b['category']);
|
||||
} elseif ($posA === false) {
|
||||
return 1;
|
||||
} elseif ($posB === false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$category = strcmp($posA, $posB);
|
||||
|
||||
if ($category == 0) {
|
||||
return (@$a['order'] < @$b['order'] ? -1 : 1);
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
|
||||
$apiParameters = false, $idGoal = false, $language = false,
|
||||
$showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
|
||||
$showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false,
|
||||
$formatMetrics = null, $idDimension = false)
|
||||
{
|
||||
$timer = new Timer();
|
||||
if (empty($apiParameters)) {
|
||||
$apiParameters = array();
|
||||
}
|
||||
|
||||
if (!empty($idGoal)
|
||||
&& empty($apiParameters['idGoal'])
|
||||
) {
|
||||
$apiParameters['idGoal'] = $idGoal;
|
||||
}
|
||||
|
||||
if (!empty($idDimension)
|
||||
&& empty($apiParameters['idDimension'])
|
||||
) {
|
||||
$apiParameters['idDimension'] = (int) $idDimension;
|
||||
}
|
||||
|
||||
// Is this report found in the Metadata available reports?
|
||||
$reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language,
|
||||
$period, $date, $hideMetricsDoc, $showSubtableReports = true);
|
||||
if (empty($reportMetadata)) {
|
||||
throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n");
|
||||
}
|
||||
|
||||
$reportMetadata = reset($reportMetadata);
|
||||
|
||||
// Generate Api call URL passing custom parameters
|
||||
|
|
@ -406,8 +419,18 @@ class ProcessedReport
|
|||
'language' => $language,
|
||||
'idSubtable' => $idSubtable,
|
||||
));
|
||||
|
||||
if (!empty($segment)) $parameters['segment'] = $segment;
|
||||
|
||||
if (!empty($reportMetadata['processedMetrics'])
|
||||
&& !empty($reportMetadata['metrics']['nb_visits'])
|
||||
&& @$reportMetadata['category'] != Piwik::translate('Goals_Ecommerce')
|
||||
&& $apiModule !== 'MultiSites'
|
||||
) {
|
||||
$deleteRowsWithNoVisits = empty($reportMetadata['constantRowsCount']) ? '1' : '0';
|
||||
$parameters['filter_add_columns_when_show_all_columns'] = $deleteRowsWithNoVisits;
|
||||
}
|
||||
|
||||
$url = Url::getQueryStringFromParameters($parameters);
|
||||
$request = new Request($url);
|
||||
try {
|
||||
|
|
@ -417,13 +440,14 @@ class ProcessedReport
|
|||
throw new Exception("API returned an error: " . $e->getMessage() . " at " . basename($e->getFile()) . ":" . $e->getLine() . "\n");
|
||||
}
|
||||
|
||||
list($newReport, $columns, $rowsMetadata, $totals) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, $showRawMetrics);
|
||||
foreach ($columns as $columnId => &$name) {
|
||||
list($newReport, $columns, $rowsMetadata, $totals) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, $showRawMetrics, $formatMetrics);
|
||||
|
||||
foreach ($columns as &$name) {
|
||||
$name = ucfirst($name);
|
||||
}
|
||||
$website = new Site($idSite);
|
||||
|
||||
$period = Period::factory($period, $date);
|
||||
$period = Period\Factory::build($period, $date);
|
||||
$period = $period->getLocalizedLongString();
|
||||
|
||||
$return = array(
|
||||
|
|
@ -454,44 +478,44 @@ class ProcessedReport
|
|||
* @param \Piwik\DataTable\Map|\Piwik\DataTable\Simple $dataTable
|
||||
* @param array $reportMetadata
|
||||
* @param bool $showRawMetrics
|
||||
* @param bool|null $formatMetrics
|
||||
* @return array Simple|Set $newReport with human readable format & array $columns list of translated column names & Simple|Set $rowsMetadata
|
||||
*/
|
||||
private function handleTableReport($idSite, $dataTable, &$reportMetadata, $showRawMetrics = false)
|
||||
private function handleTableReport($idSite, $dataTable, &$reportMetadata, $showRawMetrics = false, $formatMetrics = null)
|
||||
{
|
||||
$hasDimension = isset($reportMetadata['dimension']);
|
||||
$columns = $reportMetadata['metrics'];
|
||||
$columns = @$reportMetadata['metrics'] ?: array();
|
||||
|
||||
if ($hasDimension) {
|
||||
$columns = array_merge(
|
||||
array('label' => $reportMetadata['dimension']),
|
||||
$columns
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($reportMetadata['processedMetrics'])) {
|
||||
$processedMetricsAdded = Metrics::getDefaultProcessedMetrics();
|
||||
foreach ($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) {
|
||||
// this processed metric can be displayed for this report
|
||||
if (isset($reportMetadata['processedMetrics'][$processedMetricId])) {
|
||||
$columns[$processedMetricId] = $processedMetricTranslation;
|
||||
}
|
||||
if (isset($reportMetadata['processedMetrics']) && is_array($reportMetadata['processedMetrics'])) {
|
||||
$processedMetricsAdded = Metrics::getDefaultProcessedMetrics();
|
||||
foreach ($reportMetadata['processedMetrics'] as $processedMetricId => $processedMetricTranslation) {
|
||||
// this processed metric can be displayed for this report
|
||||
|
||||
if ($processedMetricTranslation && $processedMetricId !== $processedMetricTranslation) {
|
||||
$columns[$processedMetricId] = $processedMetricTranslation;
|
||||
} elseif (isset($processedMetricsAdded[$processedMetricId])) {
|
||||
// for instance in case 'nb_visits' => 'nb_visits' we will translate it
|
||||
$columns[$processedMetricId] = $processedMetricsAdded[$processedMetricId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display the global Goal metrics
|
||||
if (isset($reportMetadata['metricsGoal'])) {
|
||||
$metricsGoalDisplay = array('revenue');
|
||||
// Add processed metrics to be displayed for this report
|
||||
foreach ($metricsGoalDisplay as $goalMetricId) {
|
||||
if (isset($reportMetadata['metricsGoal'][$goalMetricId])) {
|
||||
$columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId];
|
||||
}
|
||||
// Display the global Goal metrics
|
||||
if (isset($reportMetadata['metricsGoal'])) {
|
||||
$metricsGoalDisplay = array('revenue');
|
||||
// Add processed metrics to be displayed for this report
|
||||
foreach ($metricsGoalDisplay as $goalMetricId) {
|
||||
if (isset($reportMetadata['metricsGoal'][$goalMetricId])) {
|
||||
$columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($reportMetadata['processedMetrics'])) {
|
||||
// Add processed metrics
|
||||
$dataTable->filter('AddColumnsProcessedMetrics', array($deleteRowsWithNoVisit = false));
|
||||
}
|
||||
}
|
||||
|
||||
$columns = $this->hideShowMetrics($columns);
|
||||
|
|
@ -508,10 +532,10 @@ class ProcessedReport
|
|||
$rowsMetadata->setKeyName("prettyDate");
|
||||
|
||||
// Process each Simple entry
|
||||
foreach ($dataTable->getDataTables() as $label => $simpleDataTable) {
|
||||
foreach ($dataTable->getDataTables() as $simpleDataTable) {
|
||||
$this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable);
|
||||
|
||||
list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics);
|
||||
list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics, $formatMetrics);
|
||||
$enhancedSimpleDataTable->setAllTableMetadata($simpleDataTable->getAllTableMetadata());
|
||||
|
||||
$period = $simpleDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLocalizedLongString();
|
||||
|
|
@ -522,7 +546,7 @@ class ProcessedReport
|
|||
}
|
||||
} else {
|
||||
$this->removeEmptyColumns($columns, $reportMetadata, $dataTable);
|
||||
list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics);
|
||||
list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics, $formatMetrics);
|
||||
|
||||
$totals = $this->aggregateReportTotalValues($dataTable, $totals);
|
||||
}
|
||||
|
|
@ -547,14 +571,17 @@ class ProcessedReport
|
|||
return;
|
||||
}
|
||||
|
||||
$columns = $this->hideShowMetrics($columns, $emptyColumns);
|
||||
$columnsToRemove = $this->getColumnsToRemove();
|
||||
$columnsToKeep = $this->getColumnsToKeep();
|
||||
|
||||
$columns = $this->hideShowMetricsWithParams($columns, $columnsToRemove, $columnsToKeep, $emptyColumns);
|
||||
|
||||
if (isset($reportMetadata['metrics'])) {
|
||||
$reportMetadata['metrics'] = $this->hideShowMetrics($reportMetadata['metrics'], $emptyColumns);
|
||||
$reportMetadata['metrics'] = $this->hideShowMetricsWithParams($reportMetadata['metrics'], $columnsToRemove, $columnsToKeep, $emptyColumns);
|
||||
}
|
||||
|
||||
if (isset($reportMetadata['metricsDocumentation'])) {
|
||||
$reportMetadata['metricsDocumentation'] = $this->hideShowMetrics($reportMetadata['metricsDocumentation'], $emptyColumns);
|
||||
$reportMetadata['metricsDocumentation'] = $this->hideShowMetricsWithParams($reportMetadata['metricsDocumentation'], $columnsToRemove, $columnsToKeep, $emptyColumns);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -574,9 +601,21 @@ class ProcessedReport
|
|||
}
|
||||
|
||||
// remove columns if hideColumns query parameters exist
|
||||
$columnsToRemove = Common::getRequestVar('hideColumns', '');
|
||||
if ($columnsToRemove != '') {
|
||||
$columnsToRemove = explode(',', $columnsToRemove);
|
||||
$columnsToRemove = $this->getColumnsToRemove();
|
||||
|
||||
// remove columns if showColumns query parameters exist
|
||||
$columnsToKeep = $this->getColumnsToKeep();
|
||||
|
||||
return $this->hideShowMetricsWithParams($columns, $columnsToRemove, $columnsToKeep, $emptyColumns);
|
||||
}
|
||||
|
||||
private function hideShowMetricsWithParams($columns, $columnsToRemove, $columnsToKeep, $emptyColumns = array())
|
||||
{
|
||||
if (!is_array($columns)) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
if (null !== $columnsToRemove) {
|
||||
foreach ($columnsToRemove as $name) {
|
||||
// if a column to remove is in the column list, remove it
|
||||
if (isset($columns[$name])) {
|
||||
|
|
@ -585,12 +624,7 @@ class ProcessedReport
|
|||
}
|
||||
}
|
||||
|
||||
// remove columns if showColumns query parameters exist
|
||||
$columnsToKeep = Common::getRequestVar('showColumns', '');
|
||||
if ($columnsToKeep != '') {
|
||||
$columnsToKeep = explode(',', $columnsToKeep);
|
||||
$columnsToKeep[] = 'label';
|
||||
|
||||
if (null !== $columnsToKeep) {
|
||||
foreach ($columns as $name => $ignore) {
|
||||
// if the current column should not be kept, remove it
|
||||
$idx = array_search($name, $columnsToKeep);
|
||||
|
|
@ -626,10 +660,10 @@ class ProcessedReport
|
|||
* @param array $metadataColumns
|
||||
* @param boolean $hasDimension
|
||||
* @param bool $returnRawMetrics If set to true, the original metrics will be returned
|
||||
*
|
||||
* @param bool|null $formatMetrics
|
||||
* @return array DataTable $enhancedDataTable filtered metrics with human readable format & Simple $rowsMetadata
|
||||
*/
|
||||
private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false)
|
||||
private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false, $formatMetrics = null)
|
||||
{
|
||||
// new DataTable to store metadata
|
||||
$rowsMetadata = new DataTable();
|
||||
|
|
@ -641,28 +675,50 @@ class ProcessedReport
|
|||
$enhancedDataTable = new Simple();
|
||||
}
|
||||
|
||||
// add missing metrics
|
||||
foreach ($simpleDataTable->getRows() as $row) {
|
||||
$rowMetrics = $row->getColumns();
|
||||
foreach ($metadataColumns as $id => $name) {
|
||||
if (!isset($rowMetrics[$id])) {
|
||||
$row->addColumn($id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
$formatter = new Formatter();
|
||||
|
||||
foreach ($simpleDataTable->getRows() as $row) {
|
||||
$rowMetrics = $row->getColumns();
|
||||
|
||||
// add missing metrics
|
||||
foreach ($metadataColumns as $id => $name) {
|
||||
if (!isset($rowMetrics[$id])) {
|
||||
$row->setColumn($id, 0);
|
||||
$rowMetrics[$id] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$enhancedRow = new Row();
|
||||
$enhancedDataTable->addRow($enhancedRow);
|
||||
$rowMetrics = $row->getColumns();
|
||||
|
||||
foreach ($rowMetrics as $columnName => $columnValue) {
|
||||
// filter metrics according to metadata definition
|
||||
if (isset($metadataColumns[$columnName])) {
|
||||
// generate 'human readable' metric values
|
||||
$prettyValue = MetricsFormatter::getPrettyValue($idSite, $columnName, $columnValue, $htmlAllowed = false);
|
||||
|
||||
// if we handle MultiSites.getAll we do not always have the same idSite but different ones for
|
||||
// each site, see https://github.com/piwik/piwik/issues/5006
|
||||
$idSiteForRow = $idSite;
|
||||
$idSiteMetadata = $row->getMetadata('idsite');
|
||||
if ($idSiteMetadata && is_numeric($idSiteMetadata)) {
|
||||
$idSiteForRow = (int) $idSiteMetadata;
|
||||
}
|
||||
|
||||
// format metrics manually here to maintain API.getProcessedReport BC if format_metrics query parameter is
|
||||
// not supplied. TODO: should be removed for 3.0. should only rely on format_metrics query parameter.
|
||||
if ($formatMetrics === null
|
||||
|| $formatMetrics == 'bc'
|
||||
) {
|
||||
$prettyValue = self::getPrettyValue($formatter, $idSiteForRow, $columnName, $columnValue, $htmlAllowed = false);
|
||||
} else {
|
||||
$prettyValue = $columnValue;
|
||||
}
|
||||
$enhancedRow->addColumn($columnName, $prettyValue);
|
||||
} // For example the Maps Widget requires the raw metrics to do advanced datavis
|
||||
elseif ($returnRawMetrics) {
|
||||
else if ($returnRawMetrics) {
|
||||
if (!isset($columnValue)) {
|
||||
$columnValue = 0;
|
||||
}
|
||||
$enhancedRow->addColumn($columnName, $columnValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -715,4 +771,108 @@ class ProcessedReport
|
|||
|
||||
return $totals;
|
||||
}
|
||||
}
|
||||
|
||||
private function getColumnsToRemove()
|
||||
{
|
||||
$columnsToRemove = Common::getRequestVar('hideColumns', '');
|
||||
|
||||
if ($columnsToRemove != '') {
|
||||
return explode(',', $columnsToRemove);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getColumnsToKeep()
|
||||
{
|
||||
$columnsToKeep = Common::getRequestVar('showColumns', '');
|
||||
|
||||
if ($columnsToKeep != '') {
|
||||
$columnsToKeep = explode(',', $columnsToKeep);
|
||||
$columnsToKeep[] = 'label';
|
||||
|
||||
return $columnsToKeep;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buildReportMetadataCacheKey($idSites, $period, $date, $hideMetricsDoc, $showSubtableReports)
|
||||
{
|
||||
if (isset($_GET) && isset($_POST) && is_array($_GET) && is_array($_POST)) {
|
||||
$request = $_GET + $_POST;
|
||||
} elseif (isset($_GET) && is_array($_GET)) {
|
||||
$request = $_GET;
|
||||
} elseif (isset($_POST) && is_array($_POST)) {
|
||||
$request = $_POST;
|
||||
} else {
|
||||
$request = array();
|
||||
}
|
||||
|
||||
$key = '';
|
||||
foreach ($request as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
$key .= $k . $this->getImplodedArray($v) . ',';
|
||||
} else {
|
||||
$key .= $k . $v . ',';
|
||||
}
|
||||
}
|
||||
|
||||
$key .= implode(',', $idSites) . ($period === false ? 0 : $period) . ($date === false ? 0 : $date);
|
||||
$key .= (int)$hideMetricsDoc . (int)$showSubtableReports . Piwik::getCurrentUserLogin();
|
||||
return 'reportMetadata' . md5($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $v
|
||||
* @return string
|
||||
*/
|
||||
private function getImplodedArray($v)
|
||||
{
|
||||
return implode(', ', array_map(function ($entry) {
|
||||
if (is_array($entry)) {
|
||||
return implode(":", $entry);
|
||||
}
|
||||
return $entry;
|
||||
}, $v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prettifies a metric value based on the column name.
|
||||
*
|
||||
* @param int $idSite The ID of the site the metric is for (used if the column value is an amount of money).
|
||||
* @param string $columnName The metric name.
|
||||
* @param mixed $value The metric value.
|
||||
* @param bool $isHtml If true, replaces all spaces with `' '`.
|
||||
* @return string
|
||||
*/
|
||||
public static function getPrettyValue(Formatter $formatter, $idSite, $columnName, $value)
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Display time in human readable
|
||||
if (strpos($columnName, 'time_generation') !== false) {
|
||||
return $formatter->getPrettyTimeFromSeconds($value, true);
|
||||
}
|
||||
if (strpos($columnName, 'time') !== false) {
|
||||
return $formatter->getPrettyTimeFromSeconds($value);
|
||||
}
|
||||
|
||||
// Add revenue symbol to revenues
|
||||
$isMoneyMetric = strpos($columnName, 'revenue') !== false || strpos($columnName, 'price') !== false;
|
||||
if ($isMoneyMetric && strpos($columnName, 'evolution') === false) {
|
||||
return $formatter->getPrettyMoney($value, $idSite);
|
||||
}
|
||||
|
||||
// Add % symbol to rates
|
||||
if (strpos($columnName, '_rate') !== false) {
|
||||
if (strpos($value, "%") === false) {
|
||||
return $value . "%";
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
31
www/analytics/plugins/API/Renderer/Console.php
Normal file
31
www/analytics/plugins/API/Renderer/Console.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
|
||||
class Console extends ApiRenderer
|
||||
{
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
self::sendHeader();
|
||||
|
||||
return 'Error: ' . $message;
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
}
|
||||
60
www/analytics/plugins/API/Renderer/Csv.php
Normal file
60
www/analytics/plugins/API/Renderer/Csv.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\ProxyHttp;
|
||||
|
||||
class Csv extends ApiRenderer
|
||||
{
|
||||
|
||||
public function renderSuccess($message)
|
||||
{
|
||||
Common::sendHeader("Content-Disposition: attachment; filename=piwik-report-export.csv");
|
||||
return "message\n" . $message;
|
||||
}
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/html; charset=utf-8', true);
|
||||
return 'Error: ' . $message;
|
||||
}
|
||||
|
||||
public function renderDataTable($dataTable)
|
||||
{
|
||||
$convertToUnicode = Common::getRequestVar('convertToUnicode', true, 'int', $this->request);
|
||||
$idSite = Common::getRequestVar('idSite', false, 'int', $this->request);
|
||||
|
||||
/** @var \Piwik\DataTable\Renderer\Csv $tableRenderer */
|
||||
$tableRenderer = $this->buildDataTableRenderer($dataTable);
|
||||
$tableRenderer->setConvertToUnicode($convertToUnicode);
|
||||
|
||||
$method = Common::getRequestVar('method', '', 'string', $this->request);
|
||||
|
||||
$tableRenderer->setApiMethod($method);
|
||||
$tableRenderer->setIdSite($idSite);
|
||||
$tableRenderer->setTranslateColumnNames(Common::getRequestVar('translateColumnNames', false, 'int', $this->request));
|
||||
|
||||
return $tableRenderer->render();
|
||||
}
|
||||
|
||||
public function renderArray($array)
|
||||
{
|
||||
return $this->renderDataTable($array);
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
Common::sendHeader("Content-Type: application/vnd.ms-excel", true);
|
||||
ProxyHttp::overrideCacheControlHeaders();
|
||||
}
|
||||
}
|
||||
51
www/analytics/plugins/API/Renderer/Html.php
Normal file
51
www/analytics/plugins/API/Renderer/Html.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
|
||||
class Html extends ApiRenderer
|
||||
{
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/plain; charset=utf-8', true);
|
||||
|
||||
return nl2br($message);
|
||||
}
|
||||
|
||||
public function renderDataTable($dataTable)
|
||||
{
|
||||
/** @var \Piwik\DataTable\Renderer\Html $tableRenderer */
|
||||
$tableRenderer = $this->buildDataTableRenderer($dataTable);
|
||||
$tableRenderer->setTableId($this->request['method']);
|
||||
|
||||
$method = Common::getRequestVar('method', '', 'string', $this->request);
|
||||
|
||||
$tableRenderer->setApiMethod($method);
|
||||
$tableRenderer->setIdSite(Common::getRequestVar('idSite', false, 'int', $this->request));
|
||||
$tableRenderer->setTranslateColumnNames(Common::getRequestVar('translateColumnNames', false, 'int', $this->request));
|
||||
|
||||
return $tableRenderer->render();
|
||||
}
|
||||
|
||||
public function renderArray($array)
|
||||
{
|
||||
return $this->renderDataTable($array);
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/html; charset=utf-8', true);
|
||||
}
|
||||
|
||||
}
|
||||
108
www/analytics/plugins/API/Renderer/Json.php
Normal file
108
www/analytics/plugins/API/Renderer/Json.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\ProxyHttp;
|
||||
|
||||
/**
|
||||
* API output renderer for JSON.
|
||||
*
|
||||
* **NOTE: This is the old JSON format. It includes bugs that are fixed in the JSON2 API output
|
||||
* format. Please use that format instead of this.**
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class Json extends ApiRenderer
|
||||
{
|
||||
public function renderSuccess($message)
|
||||
{
|
||||
$result = json_encode(array('result' => 'success', 'message' => $message));
|
||||
return $this->applyJsonpIfNeeded($result);
|
||||
}
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
$exceptionMessage = str_replace(array("\r\n", "\n"), "", $message);
|
||||
|
||||
$result = json_encode(array('result' => 'error', 'message' => $exceptionMessage));
|
||||
|
||||
return $this->applyJsonpIfNeeded($result);
|
||||
}
|
||||
|
||||
public function renderDataTable($dataTable)
|
||||
{
|
||||
$result = parent::renderDataTable($dataTable);
|
||||
|
||||
return $this->applyJsonpIfNeeded($result);
|
||||
}
|
||||
|
||||
public function renderArray($array)
|
||||
{
|
||||
if (Piwik::isMultiDimensionalArray($array)) {
|
||||
$jsonRenderer = Renderer::factory('json');
|
||||
$jsonRenderer->setTable($array);
|
||||
$result = $jsonRenderer->render();
|
||||
return $this->applyJsonpIfNeeded($result);
|
||||
}
|
||||
|
||||
return $this->renderDataTable($array);
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
if ($this->isJsonp()) {
|
||||
Common::sendHeader('Content-Type: application/javascript; charset=utf-8');
|
||||
} else {
|
||||
Renderer\Json::sendHeaderJSON();
|
||||
}
|
||||
|
||||
ProxyHttp::overrideCacheControlHeaders();
|
||||
}
|
||||
|
||||
private function isJsonp()
|
||||
{
|
||||
$callback = $this->getJsonpCallback();
|
||||
|
||||
if (false === $callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('/^[0-9a-zA-Z_.]*$/D', $callback) > 0;
|
||||
}
|
||||
|
||||
private function getJsonpCallback()
|
||||
{
|
||||
$jsonCallback = Common::getRequestVar('callback', false, null, $this->request);
|
||||
|
||||
if ($jsonCallback === false) {
|
||||
$jsonCallback = Common::getRequestVar('jsoncallback', false, null, $this->request);
|
||||
}
|
||||
|
||||
return $jsonCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
private function applyJsonpIfNeeded($str)
|
||||
{
|
||||
if ($this->isJsonp()) {
|
||||
$jsonCallback = $this->getJsonpCallback();
|
||||
$str = $jsonCallback . "(" . $str . ")";
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
33
www/analytics/plugins/API/Renderer/Json2.php
Normal file
33
www/analytics/plugins/API/Renderer/Json2.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\Piwik;
|
||||
|
||||
/**
|
||||
* Correct API output renderer for JSON. Includes bug fixes for bugs in the old JSON API
|
||||
* format.
|
||||
*/
|
||||
class Json2 extends Json
|
||||
{
|
||||
public function renderArray($array)
|
||||
{
|
||||
$result = parent::renderArray($array);
|
||||
|
||||
// if $array is a simple associative array, remove the JSON root array that is added by renderDataTable
|
||||
if (!empty($array)
|
||||
&& Piwik::isAssociativeArray($array)
|
||||
&& !Piwik::isMultiDimensionalArray($array)
|
||||
) {
|
||||
$result = substr($result, 1, strlen($result) - 2);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
77
www/analytics/plugins/API/Renderer/Original.php
Normal file
77
www/analytics/plugins/API/Renderer/Original.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
|
||||
class Original extends ApiRenderer
|
||||
{
|
||||
public function renderSuccess($message)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
public function renderDataTable($dataTable)
|
||||
{
|
||||
return $this->serializeIfNeeded($dataTable);
|
||||
}
|
||||
|
||||
public function renderArray($array)
|
||||
{
|
||||
return $this->serializeIfNeeded($array);
|
||||
}
|
||||
|
||||
public function renderScalar($scalar)
|
||||
{
|
||||
return $scalar;
|
||||
}
|
||||
|
||||
public function renderObject($object)
|
||||
{
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function renderResource($resource)
|
||||
{
|
||||
return $resource;
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
if ($this->shouldSerialize()) {
|
||||
Common::sendHeader('Content-Type: text/plain; charset=utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user requested to serialize the output data (&serialize=1 in the request)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldSerialize()
|
||||
{
|
||||
$serialize = Common::getRequestVar('serialize', 0, 'int', $this->request);
|
||||
|
||||
return !empty($serialize);
|
||||
}
|
||||
|
||||
private function serializeIfNeeded($response)
|
||||
{
|
||||
if ($this->shouldSerialize()) {
|
||||
return serialize($response);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
82
www/analytics/plugins/API/Renderer/Php.php
Normal file
82
www/analytics/plugins/API/Renderer/Php.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Piwik;
|
||||
|
||||
class Php extends ApiRenderer
|
||||
{
|
||||
public function renderSuccess($message)
|
||||
{
|
||||
$success = array('result' => 'success', 'message' => $message);
|
||||
|
||||
return $this->serializeIfNeeded($success);
|
||||
}
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
$message = array('result' => 'error', 'message' => $message);
|
||||
|
||||
return $this->serializeIfNeeded($message);
|
||||
}
|
||||
|
||||
public function renderDataTable($dataTable)
|
||||
{
|
||||
/** @var \Piwik\DataTable\Renderer\Php $tableRenderer */
|
||||
$tableRenderer = $this->buildDataTableRenderer($dataTable);
|
||||
|
||||
$tableRenderer->setSerialize($this->shouldSerialize(1));
|
||||
$tableRenderer->setPrettyDisplay(Common::getRequestVar('prettyDisplay', false, 'int', $this->request));
|
||||
|
||||
return $tableRenderer->render();
|
||||
}
|
||||
|
||||
public function renderArray($array)
|
||||
{
|
||||
if (!Piwik::isMultiDimensionalArray($array)) {
|
||||
return $this->renderDataTable($array);
|
||||
}
|
||||
|
||||
if ($this->shouldSerialize(1)) {
|
||||
return serialize($array);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user requested to serialize the output data (&serialize=1 in the request)
|
||||
*
|
||||
* @param mixed $defaultSerializeValue Default value in case the user hasn't specified a value
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldSerialize($defaultSerializeValue)
|
||||
{
|
||||
$serialize = Common::getRequestVar('serialize', $defaultSerializeValue, 'int', $this->request);
|
||||
|
||||
return !empty($serialize);
|
||||
}
|
||||
|
||||
private function serializeIfNeeded($response)
|
||||
{
|
||||
if ($this->shouldSerialize(1)) {
|
||||
return serialize($response);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
51
www/analytics/plugins/API/Renderer/Rss.php
Normal file
51
www/analytics/plugins/API/Renderer/Rss.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
|
||||
class Rss extends ApiRenderer
|
||||
{
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
self::sendHeader('plain');
|
||||
|
||||
return 'Error: ' . $message;
|
||||
}
|
||||
|
||||
public function renderDataTable($dataTable)
|
||||
{
|
||||
/** @var \Piwik\DataTable\Renderer\Rss $tableRenderer */
|
||||
$tableRenderer = $this->buildDataTableRenderer($dataTable);
|
||||
|
||||
$method = Common::getRequestVar('method', '', 'string', $this->request);
|
||||
|
||||
$tableRenderer->setApiMethod($method);
|
||||
$tableRenderer->setIdSite(Common::getRequestVar('idSite', false, 'int', $this->request));
|
||||
$tableRenderer->setTranslateColumnNames(Common::getRequestVar('translateColumnNames', false, 'int', $this->request));
|
||||
|
||||
return $tableRenderer->render();
|
||||
}
|
||||
|
||||
public function renderArray($array)
|
||||
{
|
||||
return $this->renderDataTable($array);
|
||||
}
|
||||
|
||||
public function sendHeader($type = "xml")
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/' . $type . '; charset=utf-8');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
24
www/analytics/plugins/API/Renderer/Tsv.php
Normal file
24
www/analytics/plugins/API/Renderer/Tsv.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
|
||||
class Tsv extends Csv
|
||||
{
|
||||
|
||||
public function renderSuccess($message)
|
||||
{
|
||||
Common::sendHeader("Content-Disposition: attachment; filename=piwik-report-export.csv");
|
||||
return "message\t" . $message;
|
||||
}
|
||||
|
||||
}
|
||||
40
www/analytics/plugins/API/Renderer/Xml.php
Normal file
40
www/analytics/plugins/API/Renderer/Xml.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Renderer;
|
||||
|
||||
use Piwik\API\ApiRenderer;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
|
||||
class Xml extends ApiRenderer
|
||||
{
|
||||
|
||||
public function renderSuccess($message)
|
||||
{
|
||||
return "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" .
|
||||
"<result>\n" .
|
||||
"\t<success message=\"" . $message . "\" />\n" .
|
||||
"</result>";
|
||||
}
|
||||
|
||||
public function renderException($message, \Exception $exception)
|
||||
{
|
||||
return '<?xml version="1.0" encoding="utf-8" ?>' . "\n" .
|
||||
"<result>\n" .
|
||||
"\t<error message=\"" . $message . "\" />\n" .
|
||||
"</result>";
|
||||
}
|
||||
|
||||
public function sendHeader()
|
||||
{
|
||||
Common::sendHeader('Content-Type: text/xml; charset=utf-8');
|
||||
}
|
||||
|
||||
}
|
||||
109
www/analytics/plugins/API/Reports/Get.php
Normal file
109
www/analytics/plugins/API/Reports/Get.php
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API\Reports;
|
||||
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Report;
|
||||
|
||||
class Get extends Report
|
||||
{
|
||||
/**
|
||||
* List of Plugin.Get reports that are merged in this one.
|
||||
*
|
||||
* @var Report[]
|
||||
*/
|
||||
private $reportsToMerge = array();
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->reportsToMerge = $this->getReportsToMerge();
|
||||
|
||||
$this->module = 'API';
|
||||
$this->action = 'get';
|
||||
|
||||
$this->category = 'API';
|
||||
$this->name = Piwik::translate('General_MainMetrics');
|
||||
$this->documentation = '';
|
||||
|
||||
$this->processedMetrics = array();
|
||||
foreach ($this->reportsToMerge as $report) {
|
||||
if (!is_array($report->processedMetrics)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->processedMetrics = array_merge($this->processedMetrics, $report->processedMetrics);
|
||||
}
|
||||
|
||||
$this->metrics = array();
|
||||
foreach ($this->reportsToMerge as $report) {
|
||||
if (!is_array($report->metrics)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->metrics = array_merge($this->metrics, $report->metrics);
|
||||
}
|
||||
|
||||
$this->order = 6;
|
||||
}
|
||||
|
||||
public function getMetrics()
|
||||
{
|
||||
$metrics = array();
|
||||
foreach ($this->reportsToMerge as $report) {
|
||||
$metrics = array_merge($metrics, $report->getMetrics());
|
||||
}
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
public function getProcessedMetrics()
|
||||
{
|
||||
$processedMetrics = array();
|
||||
foreach ($this->reportsToMerge as $report) {
|
||||
$reportMetrics = $report->getProcessedMetrics();
|
||||
if (is_array($reportMetrics)) {
|
||||
$processedMetrics = array_merge($processedMetrics, $reportMetrics);
|
||||
}
|
||||
}
|
||||
return $processedMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Report[]
|
||||
*/
|
||||
private function getReportsToMerge()
|
||||
{
|
||||
$result = array();
|
||||
foreach (Report::getAllReportClasses() as $reportClass) {
|
||||
if ($reportClass == 'Piwik\\Plugins\\API\\Reports\\Get') {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Report $report */
|
||||
$report = new $reportClass();
|
||||
|
||||
if ($report->getModule() == 'API'
|
||||
|| $report->getAction() != 'get'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metrics = $report->getMetrics();
|
||||
if (!empty($report->parameters)
|
||||
|| empty($metrics)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = $report;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,17 +10,17 @@ namespace Piwik\Plugins\API;
|
|||
|
||||
use Exception;
|
||||
use Piwik\API\DataTableManipulator\LabelFilter;
|
||||
use Piwik\API\DataTablePostProcessor;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\API\ResponseBuilder;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Filter\CalculateEvolutionFilter;
|
||||
use Piwik\DataTable\Filter\SafeDecodeLabel;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Url;
|
||||
use Piwik\Site;
|
||||
use Piwik\Url;
|
||||
|
||||
/**
|
||||
* This class generates a Row evolution dataset, from input request
|
||||
|
|
@ -36,7 +36,7 @@ class RowEvolution
|
|||
'getPageUrl'
|
||||
);
|
||||
|
||||
public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true)
|
||||
public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true, $idDimension = false)
|
||||
{
|
||||
// validation of requested $period & $date
|
||||
if ($period == 'range') {
|
||||
|
|
@ -48,12 +48,12 @@ class RowEvolution
|
|||
throw new Exception("Row evolutions can not be processed with this combination of \'date\' and \'period\' parameters.");
|
||||
}
|
||||
|
||||
$label = ResponseBuilder::unsanitizeLabelParameter($label);
|
||||
$label = DataTablePostProcessor::unsanitizeLabelParameter($label);
|
||||
$labels = Piwik::getArrayFromApiParameter($label);
|
||||
|
||||
$metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal);
|
||||
$metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal, $idDimension);
|
||||
|
||||
$dataTable = $this->loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal);
|
||||
$dataTable = $this->loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal, $idDimension);
|
||||
|
||||
if (empty($labels)) {
|
||||
$labels = $this->getLabelsFromDataTable($dataTable, $labels);
|
||||
|
|
@ -144,7 +144,7 @@ class RowEvolution
|
|||
|
||||
$logo = $actualLabel = false;
|
||||
$urlFound = false;
|
||||
foreach ($dataTable->getDataTables() as $date => $subTable) {
|
||||
foreach ($dataTable->getDataTables() as $subTable) {
|
||||
/** @var $subTable DataTable */
|
||||
$subTable->applyQueuedFilters();
|
||||
if ($subTable->getRowsCount() > 0) {
|
||||
|
|
@ -204,10 +204,13 @@ class RowEvolution
|
|||
$replaceRegex = "/\\s*" . preg_quote(LabelFilter::SEPARATOR_RECURSIVE_LABEL) . "\\s*/";
|
||||
$cleanLabel = preg_replace($replaceRegex, '/', $label);
|
||||
|
||||
return $mainUrlHost . '/' . $cleanLabel . '/';
|
||||
$result = $mainUrlHost . '/' . $cleanLabel . '/';
|
||||
} else {
|
||||
return str_replace(LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label);
|
||||
$result = str_replace(LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label);
|
||||
}
|
||||
|
||||
// remove @ terminal operator occurances
|
||||
return str_replace(LabelFilter::TERMINAL_OPERATOR, '', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -245,7 +248,7 @@ class RowEvolution
|
|||
* @throws Exception
|
||||
* @return DataTable\Map|DataTable
|
||||
*/
|
||||
private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false)
|
||||
private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false, $idDimension = false)
|
||||
{
|
||||
if (!is_array($label)) {
|
||||
$label = array($label);
|
||||
|
|
@ -262,6 +265,7 @@ class RowEvolution
|
|||
'serialize' => '0',
|
||||
'segment' => $segment,
|
||||
'idGoal' => $idGoal,
|
||||
'idDimension' => $idDimension,
|
||||
|
||||
// data for row evolution should NOT be limited
|
||||
'filter_limit' => -1,
|
||||
|
|
@ -276,9 +280,8 @@ class RowEvolution
|
|||
// note: some reports should not be filtered with AddColumnProcessedMetrics
|
||||
// specifically, reports without the Metrics::INDEX_NB_VISITS metric such as Goals.getVisitsUntilConversion & Goal.getDaysToConversion
|
||||
// this is because the AddColumnProcessedMetrics filter removes all datable rows lacking this metric
|
||||
if( isset($metadata['metrics']['nb_visits'])
|
||||
&& !empty($label)) {
|
||||
$parameters['filter_add_columns_when_show_all_columns'] = '1';
|
||||
if (isset($metadata['metrics']['nb_visits'])) {
|
||||
$parameters['filter_add_columns_when_show_all_columns'] = '0';
|
||||
}
|
||||
|
||||
$url = Url::getQueryStringFromParameters($parameters);
|
||||
|
|
@ -307,12 +310,15 @@ class RowEvolution
|
|||
* @throws Exception
|
||||
* @return array
|
||||
*/
|
||||
private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false)
|
||||
private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false, $idDimension = false)
|
||||
{
|
||||
$apiParameters = array();
|
||||
if (!empty($idGoal) && $idGoal > 0) {
|
||||
$apiParameters = array('idGoal' => $idGoal);
|
||||
}
|
||||
if (!empty($idDimension) && $idDimension > 0) {
|
||||
$apiParameters = array('idDimension' => (int) $idDimension);
|
||||
}
|
||||
$reportMetadata = API::getInstance()->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language,
|
||||
$period, $date, $hideMetricsDoc = false, $showSubtableReports = true);
|
||||
|
||||
|
|
@ -328,6 +334,10 @@ class RowEvolution
|
|||
$metrics = $metrics + $reportMetadata['processedMetrics'];
|
||||
}
|
||||
|
||||
if (empty($reportMetadata['dimension'])) {
|
||||
throw new Exception(sprintf('Reports like %s.%s which do not have a dimension are not supported by row evolution', $apiModule, $apiAction));
|
||||
}
|
||||
|
||||
$dimension = $reportMetadata['dimension'];
|
||||
|
||||
return compact('metrics', 'dimension');
|
||||
|
|
@ -353,9 +363,16 @@ class RowEvolution
|
|||
unset($metadata['logos']);
|
||||
|
||||
$subDataTables = $dataTable->getDataTables();
|
||||
if (empty($subDataTables)) {
|
||||
throw new \Exception("Unexpected state: row evolution API call returned empty DataTable\\Map.");
|
||||
}
|
||||
|
||||
$firstDataTable = reset($subDataTables);
|
||||
$this->checkDataTableInstance($firstDataTable);
|
||||
$firstDataTableRow = $firstDataTable->getFirstRow();
|
||||
|
||||
$lastDataTable = end($subDataTables);
|
||||
$this->checkDataTableInstance($lastDataTable);
|
||||
$lastDataTableRow = $lastDataTable->getFirstRow();
|
||||
|
||||
// Process min/max values
|
||||
|
|
@ -423,7 +440,7 @@ class RowEvolution
|
|||
$labelRow, $apiModule, $apiAction, $labelUseAbsoluteUrl);
|
||||
|
||||
$prettyLabel = $labelRow->getColumn('label_html');
|
||||
if($prettyLabel !== false) {
|
||||
if ($prettyLabel !== false) {
|
||||
$actualLabels[$labelIdx] = $prettyLabel;
|
||||
}
|
||||
|
||||
|
|
@ -526,4 +543,11 @@ class RowEvolution
|
|||
$label = SafeDecodeLabel::decodeLabelSafe($label);
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function checkDataTableInstance($lastDataTable)
|
||||
{
|
||||
if (!($lastDataTable instanceof DataTable)) {
|
||||
throw new \Exception("Unexpected state: row evolution returned DataTable\\Map w/ incorrect child table type: " . get_class($lastDataTable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
www/analytics/plugins/API/lang/am.json
Normal file
5
www/analytics/plugins/API/lang/am.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"API": {
|
||||
"LoadedAPIs": "ማስጋባት ተሳክቷል %s ኤፒአይዎች"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue