merge
This commit is contained in:
commit
046a724272
4209 changed files with 1186656 additions and 0 deletions
723
www/analytics/plugins/API/API.php
Normal file
723
www/analytics/plugins/API/API.php
Normal file
|
|
@ -0,0 +1,723 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API;
|
||||
|
||||
use Piwik\API\Proxy;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Config;
|
||||
use Piwik\DataTable\Filter\ColumnDelete;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CoreAdminHome\CustomLogo;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
use Piwik\Translate;
|
||||
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
|
||||
* human readable and more complete outputs than normal API methods.
|
||||
*
|
||||
* Some of the information that is returned by the Metadata API:
|
||||
* <ul>
|
||||
* <li>the dynamically generated list of all API methods via "getReportMetadata"</li>
|
||||
* <li>the list of metrics that will be returned by each method, along with their human readable name, via "getDefaultMetrics" and "getDefaultProcessedMetrics"</li>
|
||||
* <li>the list of segments metadata supported by all functions that have a 'segment' parameter</li>
|
||||
* <li>the (truly magic) method "getProcessedReport" will return a human readable version of any other report, and include the processed metrics such as
|
||||
* conversion rate, time on site, etc. which are not directly available in other methods.</li>
|
||||
* <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>
|
||||
*
|
||||
* @method static \Piwik\Plugins\API\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
/**
|
||||
* Get Piwik version
|
||||
* @return string
|
||||
*/
|
||||
public function getPiwikVersion()
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
return Version::VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section [APISettings] if defined in config.ini.php
|
||||
* @return array
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
return Config::getInstance()->APISettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default translations for many core metrics.
|
||||
* This is used for exports with translated labels. The exports contain columns that
|
||||
* are not visible in the UI and not present in the API meta data. These columns are
|
||||
* translated here.
|
||||
* @return array
|
||||
*/
|
||||
static public function getDefaultMetricTranslations()
|
||||
{
|
||||
return Metrics::getDefaultMetricTranslations();
|
||||
}
|
||||
|
||||
public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true)
|
||||
{
|
||||
$segments = array();
|
||||
|
||||
/**
|
||||
* 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(
|
||||
* 'type' => 'dimension',
|
||||
* 'category' => Piwik::translate('General_Visit'),
|
||||
* 'name' => 'General_VisitorIP',
|
||||
* 'segment' => 'visitIp',
|
||||
* 'acceptedValues' => '13.54.122.1, etc.',
|
||||
* 'sqlSegment' => 'log_visit.location_ip',
|
||||
* 'sqlFilter' => array('Piwik\IP', 'P2N'),
|
||||
* '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
|
||||
* under **Visit (metrics)** in the Segment Editor.
|
||||
* - **category**: The segment category name. This can be an existing
|
||||
* segment category visible in the segment editor.
|
||||
* - **name**: The pretty name of the segment. Can be a translation token.
|
||||
* - **segment**: The segment name, eg, `'visitIp'` or `'searches'`.
|
||||
* - **acceptedValues**: A string describing one or two exacmple values, eg
|
||||
* `'13.54.122.1, etc.'`.
|
||||
* - **sqlSegment**: The table column this segment will segment by.
|
||||
* For example, `'log_visit.location_ip'` for the
|
||||
* **visitIp** segment.
|
||||
* - **sqlFilter**: A PHP callback to apply to segment values before
|
||||
* they are used in SQL.
|
||||
* - **permission**: True if the current user has view access to this
|
||||
* segment, false if otherwise.
|
||||
* @param array $idSites The list of site IDs we're getting the available segments
|
||||
* for. Some segments (such as Goal segments) depend on the
|
||||
* site.
|
||||
*/
|
||||
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']);
|
||||
|
||||
if ($_hideImplementationData) {
|
||||
unset($segment['sqlFilter']);
|
||||
unset($segment['sqlFilterValue']);
|
||||
unset($segment['sqlSegment']);
|
||||
}
|
||||
}
|
||||
|
||||
usort($segments, array($this, 'sortSegments'));
|
||||
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
|
||||
*/
|
||||
static public function getVisitEcommerceStatusFromId($id)
|
||||
{
|
||||
if (!isset(self::$visitEcommerceStatus[$id])) {
|
||||
throw new \Exception("Unexpected ECommerce status value ");
|
||||
}
|
||||
return self::$visitEcommerceStatus[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
private function sortSegments($row1, $row2)
|
||||
{
|
||||
$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')
|
||||
&& $row1['category'] == $row2['category']
|
||||
) {
|
||||
$compare = strcmp($row1['segment'], $row2['segment']);
|
||||
return $compare;
|
||||
}
|
||||
if ($compare != 0) {
|
||||
return $compare;
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
public function getLogoUrl($pathOnly = false)
|
||||
{
|
||||
$logo = new CustomLogo();
|
||||
return $logo->getLogoUrl($pathOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to header logo (~127x50px)
|
||||
*
|
||||
* @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
|
||||
* @return string
|
||||
*/
|
||||
public function getHeaderLogoUrl($pathOnly = false)
|
||||
{
|
||||
$logo = new CustomLogo();
|
||||
return $logo->getHeaderLogoUrl($pathOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to application SVG Logo
|
||||
*
|
||||
* @ignore
|
||||
* @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
|
||||
* @return string
|
||||
*/
|
||||
public function getSVGLogoUrl($pathOnly = false)
|
||||
{
|
||||
$logo = new CustomLogo();
|
||||
return $logo->getSVGLogoUrl($pathOnly);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is an SVG Logo available.
|
||||
* @ignore
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSVGLogo()
|
||||
{
|
||||
$logo = new CustomLogo();
|
||||
return $logo->hasSVGLogo();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads reports metadata, then return the requested one,
|
||||
* matching optional API parameters.
|
||||
*/
|
||||
public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false,
|
||||
$period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
|
||||
{
|
||||
Translate::reloadLanguage($language);
|
||||
$reporter = new ProcessedReport();
|
||||
$metadata = $reporter->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc, $showSubtableReports);
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a hook to ask plugins for available Reports.
|
||||
* Returns metadata information about each report (category, name, dimension, metrics, etc.)
|
||||
*
|
||||
* @param string $idSites Comma separated list of website Ids
|
||||
* @param bool|string $period
|
||||
* @param bool|Date $date
|
||||
* @param bool $hideMetricsDoc
|
||||
* @param bool $showSubtableReports
|
||||
* @return array
|
||||
*/
|
||||
public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false,
|
||||
$showSubtableReports = false)
|
||||
{
|
||||
$reporter = new ProcessedReport();
|
||||
$metadata = $reporter->getReportMetadata($idSites, $period, $date, $hideMetricsDoc, $showSubtableReports);
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
|
||||
$apiParameters = false, $idGoal = false, $language = false,
|
||||
$showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
|
||||
{
|
||||
$reporter = new ProcessedReport();
|
||||
$processed = $reporter->getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment,
|
||||
$apiParameters, $idGoal, $language, $showTimer, $hideMetricsDoc, $idSubtable, $showRawMetrics);
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a combined report of the *.get API methods.
|
||||
*/
|
||||
public function get($idSite, $period, $date, $segment = false, $columns = false)
|
||||
{
|
||||
$columns = Piwik::getArrayFromApiParameter($columns);
|
||||
|
||||
// build columns map for faster checks later on
|
||||
$columnsMap = array();
|
||||
foreach ($columns as $column) {
|
||||
$columnsMap[$column] = true;
|
||||
}
|
||||
|
||||
// find out which columns belong to which plugin
|
||||
$columnsByPlugin = array();
|
||||
$meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
|
||||
foreach ($meta as $reportMeta) {
|
||||
// scan all *.get reports
|
||||
if ($reportMeta['action'] == 'get'
|
||||
&& !isset($reportMeta['parameters'])
|
||||
&& $reportMeta['module'] != 'API'
|
||||
&& !empty($reportMeta['metrics'])
|
||||
) {
|
||||
$plugin = $reportMeta['module'];
|
||||
foreach ($reportMeta['metrics'] as $column => $columnTranslation) {
|
||||
// a metric from this report has been requested
|
||||
if (isset($columnsMap[$column])
|
||||
// or by default, return all metrics
|
||||
|| empty($columnsMap)
|
||||
) {
|
||||
$columnsByPlugin[$plugin][] = $column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
krsort($columnsByPlugin);
|
||||
|
||||
$mergedDataTable = false;
|
||||
$params = compact('idSite', 'period', 'date', 'segment', 'idGoal');
|
||||
foreach ($columnsByPlugin as $plugin => $columns) {
|
||||
// load the data
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// merge reports
|
||||
if ($mergedDataTable === false) {
|
||||
$mergedDataTable = $dataTable;
|
||||
} else {
|
||||
$this->mergeDataTables($mergedDataTable, $dataTable);
|
||||
}
|
||||
}
|
||||
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
|
||||
* a ready to use data structure containing the metrics for the requested Label, along with enriched information (min/max values, etc.)
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param Date $date
|
||||
* @param string $apiModule
|
||||
* @param string $apiAction
|
||||
* @param bool|string $label
|
||||
* @param bool|string $segment
|
||||
* @param bool|string $column
|
||||
* @param bool|string $language
|
||||
* @param bool|int $idGoal
|
||||
* @param bool|string $legendAppendMetric
|
||||
* @param bool|string $labelUseAbsoluteUrl
|
||||
* @return array
|
||||
*/
|
||||
public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true)
|
||||
{
|
||||
$rowEvolution = new RowEvolution();
|
||||
return $rowEvolution->getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label, $segment, $column,
|
||||
$language, $idGoal, $legendAppendMetric, $labelUseAbsoluteUrl);
|
||||
}
|
||||
|
||||
public function getLastDate($date, $period)
|
||||
{
|
||||
$lastDate = Range::getLastDate($date, $period);
|
||||
|
||||
return array_shift($lastDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs multiple API requests at once and returns every result.
|
||||
*
|
||||
* @param array $urls The array of API requests.
|
||||
* @return array
|
||||
*/
|
||||
public function getBulkRequest($urls)
|
||||
{
|
||||
if (empty($urls)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$urls = array_map('urldecode', $urls);
|
||||
$urls = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $urls);
|
||||
|
||||
$result = array();
|
||||
foreach ($urls as $url) {
|
||||
$req = new Request($url . '&format=php&serialize=0');
|
||||
$result[] = $req->process();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a segment, will return a list of the most used values for this particular segment.
|
||||
* @param $segmentName
|
||||
* @param $idSite
|
||||
* @throws \Exception
|
||||
* @return array
|
||||
*/
|
||||
public function getSuggestedValuesForSegment($segmentName, $idSite)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
$maxSuggestionsToReturn = 30;
|
||||
$segmentsMetadata = $this->getSegmentsMetadata($idSite, $_hideImplementationData = false);
|
||||
|
||||
$segmentFound = false;
|
||||
foreach ($segmentsMetadata as $segmentMetadata) {
|
||||
if ($segmentMetadata['segment'] == $segmentName) {
|
||||
$segmentFound = $segmentMetadata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($segmentFound)) {
|
||||
throw new \Exception("Requested segment not found.");
|
||||
}
|
||||
|
||||
$startDate = Date::now()->subDay(60)->toString();
|
||||
$requestLastVisits = "method=Live.getLastVisitsDetails
|
||||
&idSite=$idSite
|
||||
&period=range
|
||||
&date=$startDate,today
|
||||
&format=original
|
||||
&serialize=0
|
||||
&flat=1";
|
||||
|
||||
// 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";
|
||||
} else {
|
||||
$requestLastVisits .= "&doNotFetchActions=1";
|
||||
$requestLastVisits .= "&filter_limit=1000";
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $segmentName
|
||||
* @return bool
|
||||
*/
|
||||
protected function doesSegmentNeedActionsData($segmentName)
|
||||
{
|
||||
// If you update this, also update flattenVisitorDetailsArray
|
||||
$segmentsNeedActionsInfo = array('visitConvertedGoalId',
|
||||
'pageUrl', 'pageTitle', 'siteSearchKeyword',
|
||||
'entryPageTitle', 'entryPageUrl', 'exitPageTitle', 'exitPageUrl');
|
||||
$isCustomVariablePage = stripos($segmentName, 'customVariablePage') !== false;
|
||||
$isEventSegment = stripos($segmentName, 'event') !== false;
|
||||
$doesSegmentNeedActionsInfo = in_array($segmentName, $segmentsNeedActionsInfo) || $isCustomVariablePage || $isEventSegment;
|
||||
return $doesSegmentNeedActionsInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $values
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
private function getMostFrequentValues($values)
|
||||
{
|
||||
// remove false values (while keeping zeros)
|
||||
$values = array_filter($values, 'strlen');
|
||||
|
||||
// array_count_values requires strings or integer, convert floats to string (mysqli)
|
||||
foreach ($values as &$value) {
|
||||
if (is_numeric($value)) {
|
||||
$value = (string)round($value, 3);
|
||||
}
|
||||
}
|
||||
// we have a list of all values. let's show the most frequently used first.
|
||||
$values = array_count_values($values);
|
||||
|
||||
arsort($values);
|
||||
$values = array_keys($values);
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
class Plugin extends \Piwik\Plugin
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// this class is named 'Plugin', manually set the 'API' plugin
|
||||
parent::__construct($pluginName = 'API');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
{
|
||||
return array(
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'Menu.Top.addItems' => 'addTopMenu',
|
||||
);
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
128
www/analytics/plugins/API/Controller.php
Normal file
128
www/analytics/plugins/API/Controller.php
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API;
|
||||
|
||||
use Piwik\API\DocumentationGenerator;
|
||||
use Piwik\API\Proxy;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Controller extends \Piwik\Plugin\Controller
|
||||
{
|
||||
function index()
|
||||
{
|
||||
// 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'];
|
||||
}
|
||||
$request = new Request('token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string'));
|
||||
return $request->process();
|
||||
}
|
||||
|
||||
public function listAllMethods()
|
||||
{
|
||||
$ApiDocumentation = new DocumentationGenerator();
|
||||
return $ApiDocumentation->getAllInterfaceString($outputExampleUrls = true, $prefixUrls = Common::getRequestVar('prefixUrl', ''));
|
||||
}
|
||||
|
||||
public function listAllAPI()
|
||||
{
|
||||
$view = new View("@API/listAllAPI");
|
||||
$this->setGeneralVariablesView($view);
|
||||
|
||||
$ApiDocumentation = new DocumentationGenerator();
|
||||
$view->countLoadedAPI = Proxy::getInstance()->getCountRegisteredClasses();
|
||||
$view->list_api_methods_with_links = $ApiDocumentation->getAllInterfaceString();
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
public function listSegments()
|
||||
{
|
||||
$segments = API::getInstance()->getSegmentsMetadata($this->idSite);
|
||||
|
||||
$tableDimensions = $tableMetrics = '';
|
||||
$customVariables = 0;
|
||||
$lastCategory = array();
|
||||
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')) {
|
||||
$segment['type'] = 'dimension';
|
||||
}
|
||||
|
||||
$onlyDisplay = array('customVariableName1', 'customVariableName2',
|
||||
'customVariableValue1', 'customVariableValue2',
|
||||
'customVariablePageName1', 'customVariablePageValue1');
|
||||
|
||||
$customVariableWillBeDisplayed = in_array($segment['segment'], $onlyDisplay);
|
||||
// Don't display more than 4 custom variables name/value rows
|
||||
if ($segment['category'] == 'Custom Variables'
|
||||
&& !$customVariableWillBeDisplayed
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$thisCategory = $segment['category'];
|
||||
$output = '';
|
||||
if (empty($lastCategory[$segment['type']])
|
||||
|| $lastCategory[$segment['type']] != $thisCategory
|
||||
) {
|
||||
$output .= '<tr><td class="segmentCategory" colspan="2"><b>' . $thisCategory . '</b></td></tr>';
|
||||
}
|
||||
|
||||
$lastCategory[$segment['type']] = $thisCategory;
|
||||
|
||||
$exampleValues = isset($segment['acceptedValues'])
|
||||
? 'Example values: <code>' . $segment['acceptedValues'] . '</code>'
|
||||
: '';
|
||||
$restrictedToAdmin = isset($segment['permission']) ? '<br/>Note: This segment can only be used by an Admin user' : '';
|
||||
$output .= '<tr>
|
||||
<td class="segmentString">' . $segment['segment'] . '</td>
|
||||
<td class="segmentName">' . $segment['name'] . $restrictedToAdmin . '<br/>' . $exampleValues . ' </td>
|
||||
</tr>';
|
||||
|
||||
// Show only 2 custom variables and display message for rest
|
||||
if ($customVariableWillBeDisplayed) {
|
||||
$customVariables++;
|
||||
if ($customVariables == count($onlyDisplay)) {
|
||||
$output .= '<tr><td colspan="2"> There are 5 custom variables available, so you can segment across any segment name and value range.
|
||||
<br/>For example, <code>customVariableName1==Type;customVariableValue1==Customer</code>
|
||||
<br/>Returns all visitors that have the Custom Variable "Type" set to "Customer".
|
||||
<br/>Custom Variables of scope "page" can be queried separately. For example, to query the Custom Variable of scope "page",
|
||||
<br/>stored in index 1, you would use the segment <code>customVariablePageName1==ArticleLanguage;customVariablePageValue1==FR</code>
|
||||
</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($segment['type'] == 'dimension') {
|
||||
$tableDimensions .= $output;
|
||||
} else {
|
||||
$tableMetrics .= $output;
|
||||
}
|
||||
}
|
||||
|
||||
return "
|
||||
<strong>Dimensions</strong>
|
||||
<table>
|
||||
$tableDimensions
|
||||
</table>
|
||||
<br/>
|
||||
<strong>Metrics</strong>
|
||||
<table>
|
||||
$tableMetrics
|
||||
</table>
|
||||
";
|
||||
}
|
||||
}
|
||||
718
www/analytics/plugins/API/ProcessedReport.php
Normal file
718
www/analytics/plugins/API/ProcessedReport.php
Normal file
|
|
@ -0,0 +1,718 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API;
|
||||
|
||||
use Exception;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Archive\DataTableFactory;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable\Simple;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Site;
|
||||
use Piwik\Timer;
|
||||
use Piwik\Url;
|
||||
|
||||
class ProcessedReport
|
||||
{
|
||||
|
||||
/**
|
||||
* Loads reports metadata, then return the requested one,
|
||||
* matching optional API parameters.
|
||||
*/
|
||||
public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false,
|
||||
$period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
|
||||
{
|
||||
$reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports);
|
||||
|
||||
foreach ($reportsMetadata as $report) {
|
||||
// See ArchiveProcessor/Aggregator.php - unique visitors are not processed for period != day
|
||||
if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) {
|
||||
unset($report['metrics']['nb_uniq_visitors']);
|
||||
}
|
||||
if ($report['module'] == $apiModule
|
||||
&& $report['action'] == $apiAction
|
||||
) {
|
||||
// No custom parameters
|
||||
if (empty($apiParameters)
|
||||
&& empty($report['parameters'])
|
||||
) {
|
||||
return array($report);
|
||||
}
|
||||
if (empty($report['parameters'])) {
|
||||
continue;
|
||||
}
|
||||
$diff = array_diff($report['parameters'], $apiParameters);
|
||||
if (empty($diff)) {
|
||||
return array($report);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verfies whether the given report exists for the given site.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param string $apiMethodUniqueId For example 'MultiSites_getAll'
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidReportForSite($idSite, $apiMethodUniqueId)
|
||||
{
|
||||
$report = $this->getReportMetadataByUniqueId($idSite, $apiMethodUniqueId);
|
||||
|
||||
return !empty($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verfies whether the given metric belongs to the given report.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param string $metric For example 'nb_visits'
|
||||
* @param string $apiMethodUniqueId For example 'MultiSites_getAll'
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidMetricForReport($metric, $idSite, $apiMethodUniqueId)
|
||||
{
|
||||
$translation = $this->translateMetric($metric, $idSite, $apiMethodUniqueId);
|
||||
|
||||
return !empty($translation);
|
||||
}
|
||||
|
||||
public function getReportMetadataByUniqueId($idSite, $apiMethodUniqueId)
|
||||
{
|
||||
$metadata = $this->getReportMetadata(array($idSite));
|
||||
|
||||
foreach ($metadata as $report) {
|
||||
if ($report['uniqueId'] == $apiMethodUniqueId) {
|
||||
return $report;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given metric in case the report exists and in case the metric acutally belongs to the report.
|
||||
*
|
||||
* @param string $metric For example 'nb_visits'
|
||||
* @param int $idSite
|
||||
* @param string $apiMethodUniqueId For example 'MultiSites_getAll'
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function translateMetric($metric, $idSite, $apiMethodUniqueId)
|
||||
{
|
||||
$report = $this->getReportMetadataByUniqueId($idSite, $apiMethodUniqueId);
|
||||
|
||||
if (empty($report)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$properties = array('metrics', 'processedMetrics', 'processedMetricsGoal');
|
||||
|
||||
foreach ($properties as $prop) {
|
||||
if (!empty($report[$prop]) && is_array($report[$prop]) && array_key_exists($metric, $report[$prop])) {
|
||||
return $report[$prop][$metric];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a hook to ask plugins for available Reports.
|
||||
* Returns metadata information about each report (category, name, dimension, metrics, etc.)
|
||||
*
|
||||
* @param string $idSites Comma separated list of website Ids
|
||||
* @param bool|string $period
|
||||
* @param bool|Date $date
|
||||
* @param bool $hideMetricsDoc
|
||||
* @param bool $showSubtableReports
|
||||
* @return array
|
||||
*/
|
||||
public function getReportMetadata($idSites, $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
|
||||
{
|
||||
$idSites = Site::getIdSitesFromIdSitesString($idSites);
|
||||
if (!empty($idSites)) {
|
||||
Piwik::checkUserHasViewAccess($idSites);
|
||||
}
|
||||
|
||||
$parameters = array('idSites' => $idSites, 'period' => $period, 'date' => $date);
|
||||
|
||||
$availableReports = array();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* translated documentation.
|
||||
* - **processedMetrics**: The array of metrics in the report that are
|
||||
* calculated using existing metrics. Can be set to
|
||||
* `false` if the report contains no processed
|
||||
* 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
|
||||
*/
|
||||
Piwik::postEvent('API.getReportMetadata', array(&$availableReports, $parameters));
|
||||
foreach ($availableReports as &$availableReport) {
|
||||
if (!isset($availableReport['metrics'])) {
|
||||
$availableReport['metrics'] = Metrics::getDefaultMetrics();
|
||||
}
|
||||
if (!isset($availableReport['processedMetrics'])) {
|
||||
$availableReport['processedMetrics'] = Metrics::getDefaultProcessedMetrics();
|
||||
}
|
||||
|
||||
if ($hideMetricsDoc) // remove metric documentation if it's not wanted
|
||||
{
|
||||
unset($availableReport['metricsDocumentation']);
|
||||
} else if (!isset($availableReport['metricsDocumentation'])) {
|
||||
// set metric documentation to default if it's not set
|
||||
$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'`.
|
||||
* - **date**: A string date within the period or a date range, eg,
|
||||
* `'2013-01-01'` or `'2012-01-01,2013-01-01'`.
|
||||
*/
|
||||
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);
|
||||
|
||||
$knownMetrics = array_merge(Metrics::getDefaultMetrics(), Metrics::getDefaultProcessedMetrics());
|
||||
foreach ($availableReports as &$availableReport) {
|
||||
// Ensure all metrics have a translation
|
||||
$metrics = $availableReport['metrics'];
|
||||
$cleanedMetrics = array();
|
||||
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.
|
||||
if (is_numeric($metricId)
|
||||
&& isset($knownMetrics[$metricTranslation])
|
||||
) {
|
||||
$metricId = $metricTranslation;
|
||||
$metricTranslation = $knownMetrics[$metricTranslation];
|
||||
}
|
||||
$cleanedMetrics[$metricId] = $metricTranslation;
|
||||
}
|
||||
$availableReport['metrics'] = $cleanedMetrics;
|
||||
|
||||
// if hide/show columns specified, hide/show metrics & docs
|
||||
$availableReport['metrics'] = $this->hideShowMetrics($availableReport['metrics']);
|
||||
if (isset($availableReport['processedMetrics'])) {
|
||||
$availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']);
|
||||
}
|
||||
if (isset($availableReport['metricsDocumentation'])) {
|
||||
$availableReport['metricsDocumentation'] =
|
||||
$this->hideShowMetrics($availableReport['metricsDocumentation']);
|
||||
}
|
||||
|
||||
// Remove array elements that are false (to clean up API output)
|
||||
foreach ($availableReport as $attributeName => $attributeValue) {
|
||||
if (empty($attributeValue)) {
|
||||
unset($availableReport[$attributeName]);
|
||||
}
|
||||
}
|
||||
// when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum
|
||||
if (isset($availableReport['metricsGoal'])) {
|
||||
unset($availableReport['processedMetrics']['conversion_rate']);
|
||||
unset($availableReport['metricsGoal']['conversion_rate']);
|
||||
}
|
||||
|
||||
// Processing a uniqueId for each report,
|
||||
// can be used by UIs as a key to match a given report
|
||||
$uniqueId = $availableReport['module'] . '_' . $availableReport['action'];
|
||||
if (!empty($availableReport['parameters'])) {
|
||||
foreach ($availableReport['parameters'] as $key => $value) {
|
||||
$uniqueId .= '_' . $key . '--' . $value;
|
||||
}
|
||||
}
|
||||
$availableReport['uniqueId'] = $uniqueId;
|
||||
|
||||
// Order is used to order reports internally, but not meant to be used outside
|
||||
unset($availableReport['order']);
|
||||
}
|
||||
|
||||
// remove subtable reports
|
||||
if (!$showSubtableReports) {
|
||||
foreach ($availableReports as $idx => $report) {
|
||||
if (isset($report['isSubtableReport']) && $report['isSubtableReport']) {
|
||||
unset($availableReports[$idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($availableReports); // 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
|
||||
* @return int
|
||||
*/
|
||||
private function sort($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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$availableReports[] = $metadata;
|
||||
}
|
||||
|
||||
public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
|
||||
$apiParameters = false, $idGoal = false, $language = false,
|
||||
$showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
|
||||
{
|
||||
$timer = new Timer();
|
||||
if (empty($apiParameters)) {
|
||||
$apiParameters = array();
|
||||
}
|
||||
if (!empty($idGoal)
|
||||
&& empty($apiParameters['idGoal'])
|
||||
) {
|
||||
$apiParameters['idGoal'] = $idGoal;
|
||||
}
|
||||
// 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
|
||||
$parameters = array_merge($apiParameters, array(
|
||||
'method' => $apiModule . '.' . $apiAction,
|
||||
'idSite' => $idSite,
|
||||
'period' => $period,
|
||||
'date' => $date,
|
||||
'format' => 'original',
|
||||
'serialize' => '0',
|
||||
'language' => $language,
|
||||
'idSubtable' => $idSubtable,
|
||||
));
|
||||
if (!empty($segment)) $parameters['segment'] = $segment;
|
||||
|
||||
$url = Url::getQueryStringFromParameters($parameters);
|
||||
$request = new Request($url);
|
||||
try {
|
||||
/** @var DataTable */
|
||||
$dataTable = $request->process();
|
||||
} catch (Exception $e) {
|
||||
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) {
|
||||
$name = ucfirst($name);
|
||||
}
|
||||
$website = new Site($idSite);
|
||||
|
||||
$period = Period::factory($period, $date);
|
||||
$period = $period->getLocalizedLongString();
|
||||
|
||||
$return = array(
|
||||
'website' => $website->getName(),
|
||||
'prettyDate' => $period,
|
||||
'metadata' => $reportMetadata,
|
||||
'columns' => $columns,
|
||||
'reportData' => $newReport,
|
||||
'reportMetadata' => $rowsMetadata,
|
||||
'reportTotal' => $totals
|
||||
);
|
||||
if ($showTimer) {
|
||||
$return['timerMillis'] = $timer->getTimeMs(0);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance a $dataTable using metadata :
|
||||
*
|
||||
* - remove metrics based on $reportMetadata['metrics']
|
||||
* - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics']
|
||||
* - format metric values to a 'human readable' format
|
||||
* - extract row metadata to a separate Simple|Set : $rowsMetadata
|
||||
* - translate metric names to a separate array : $columns
|
||||
*
|
||||
* @param int $idSite enables monetary value formatting based on site currency
|
||||
* @param \Piwik\DataTable\Map|\Piwik\DataTable\Simple $dataTable
|
||||
* @param array $reportMetadata
|
||||
* @param bool $showRawMetrics
|
||||
* @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)
|
||||
{
|
||||
$hasDimension = isset($reportMetadata['dimension']);
|
||||
$columns = $reportMetadata['metrics'];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
$totals = array();
|
||||
|
||||
// $dataTable is an instance of Set when multiple periods requested
|
||||
if ($dataTable instanceof DataTable\Map) {
|
||||
// Need a new Set to store the 'human readable' values
|
||||
$newReport = new DataTable\Map();
|
||||
$newReport->setKeyName("prettyDate");
|
||||
|
||||
// Need a new Set to store report metadata
|
||||
$rowsMetadata = new DataTable\Map();
|
||||
$rowsMetadata->setKeyName("prettyDate");
|
||||
|
||||
// Process each Simple entry
|
||||
foreach ($dataTable->getDataTables() as $label => $simpleDataTable) {
|
||||
$this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable);
|
||||
|
||||
list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics);
|
||||
$enhancedSimpleDataTable->setAllTableMetadata($simpleDataTable->getAllTableMetadata());
|
||||
|
||||
$period = $simpleDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLocalizedLongString();
|
||||
$newReport->addTable($enhancedSimpleDataTable, $period);
|
||||
$rowsMetadata->addTable($rowMetadata, $period);
|
||||
|
||||
$totals = $this->aggregateReportTotalValues($simpleDataTable, $totals);
|
||||
}
|
||||
} else {
|
||||
$this->removeEmptyColumns($columns, $reportMetadata, $dataTable);
|
||||
list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics);
|
||||
|
||||
$totals = $this->aggregateReportTotalValues($dataTable, $totals);
|
||||
}
|
||||
|
||||
return array(
|
||||
$newReport,
|
||||
$columns,
|
||||
$rowsMetadata,
|
||||
$totals
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes metrics from the list of columns and the report meta data if they are marked empty
|
||||
* in the data table meta data.
|
||||
*/
|
||||
private function removeEmptyColumns(&$columns, &$reportMetadata, $dataTable)
|
||||
{
|
||||
$emptyColumns = $dataTable->getMetadata(DataTable::EMPTY_COLUMNS_METADATA_NAME);
|
||||
|
||||
if (!is_array($emptyColumns)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$columns = $this->hideShowMetrics($columns, $emptyColumns);
|
||||
|
||||
if (isset($reportMetadata['metrics'])) {
|
||||
$reportMetadata['metrics'] = $this->hideShowMetrics($reportMetadata['metrics'], $emptyColumns);
|
||||
}
|
||||
|
||||
if (isset($reportMetadata['metricsDocumentation'])) {
|
||||
$reportMetadata['metricsDocumentation'] = $this->hideShowMetrics($reportMetadata['metricsDocumentation'], $emptyColumns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes column names from an array based on the values in the hideColumns,
|
||||
* showColumns query parameters. This is a hack that provides the ColumnDelete
|
||||
* filter functionality in processed reports.
|
||||
*
|
||||
* @param array $columns List of metrics shown in a processed report.
|
||||
* @param array $emptyColumns Empty columns from the data table meta data.
|
||||
* @return array Filtered list of metrics.
|
||||
*/
|
||||
private function hideShowMetrics($columns, $emptyColumns = array())
|
||||
{
|
||||
if (!is_array($columns)) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
// remove columns if hideColumns query parameters exist
|
||||
$columnsToRemove = Common::getRequestVar('hideColumns', '');
|
||||
if ($columnsToRemove != '') {
|
||||
$columnsToRemove = explode(',', $columnsToRemove);
|
||||
foreach ($columnsToRemove as $name) {
|
||||
// if a column to remove is in the column list, remove it
|
||||
if (isset($columns[$name])) {
|
||||
unset($columns[$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove columns if showColumns query parameters exist
|
||||
$columnsToKeep = Common::getRequestVar('showColumns', '');
|
||||
if ($columnsToKeep != '') {
|
||||
$columnsToKeep = explode(',', $columnsToKeep);
|
||||
$columnsToKeep[] = 'label';
|
||||
|
||||
foreach ($columns as $name => $ignore) {
|
||||
// if the current column should not be kept, remove it
|
||||
$idx = array_search($name, $columnsToKeep);
|
||||
if ($idx === false) // if $name is not in $columnsToKeep
|
||||
{
|
||||
unset($columns[$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty columns
|
||||
if (is_array($emptyColumns)) {
|
||||
foreach ($emptyColumns as $column) {
|
||||
if (isset($columns[$column])) {
|
||||
unset($columns[$column]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance $simpleDataTable using metadata :
|
||||
*
|
||||
* - remove metrics based on $reportMetadata['metrics']
|
||||
* - add 0 valued metrics if $simpleDataTable doesn't provide all $reportMetadata['metrics']
|
||||
* - format metric values to a 'human readable' format
|
||||
* - extract row metadata to a separate Simple $rowsMetadata
|
||||
*
|
||||
* @param int $idSite enables monetary value formatting based on site currency
|
||||
* @param Simple $simpleDataTable
|
||||
* @param array $metadataColumns
|
||||
* @param boolean $hasDimension
|
||||
* @param bool $returnRawMetrics If set to true, the original metrics will be returned
|
||||
*
|
||||
* @return array DataTable $enhancedDataTable filtered metrics with human readable format & Simple $rowsMetadata
|
||||
*/
|
||||
private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false)
|
||||
{
|
||||
// new DataTable to store metadata
|
||||
$rowsMetadata = new DataTable();
|
||||
|
||||
// new DataTable to store 'human readable' values
|
||||
if ($hasDimension) {
|
||||
$enhancedDataTable = new DataTable();
|
||||
} else {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($simpleDataTable->getRows() as $row) {
|
||||
$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);
|
||||
$enhancedRow->addColumn($columnName, $prettyValue);
|
||||
} // For example the Maps Widget requires the raw metrics to do advanced datavis
|
||||
elseif ($returnRawMetrics) {
|
||||
$enhancedRow->addColumn($columnName, $columnValue);
|
||||
}
|
||||
}
|
||||
|
||||
// If report has a dimension, extract metadata into a distinct DataTable
|
||||
if ($hasDimension) {
|
||||
$rowMetadata = $row->getMetadata();
|
||||
$idSubDataTable = $row->getIdSubDataTable();
|
||||
|
||||
// Create a row metadata only if there are metadata to insert
|
||||
if (count($rowMetadata) > 0 || !is_null($idSubDataTable)) {
|
||||
$metadataRow = new Row();
|
||||
$rowsMetadata->addRow($metadataRow);
|
||||
|
||||
foreach ($rowMetadata as $metadataKey => $metadataValue) {
|
||||
$metadataRow->addColumn($metadataKey, $metadataValue);
|
||||
}
|
||||
|
||||
if (!is_null($idSubDataTable)) {
|
||||
$metadataRow->addColumn('idsubdatatable', $idSubDataTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$enhancedDataTable,
|
||||
$rowsMetadata
|
||||
);
|
||||
}
|
||||
|
||||
private function aggregateReportTotalValues($simpleDataTable, $totals)
|
||||
{
|
||||
$metadataTotals = $simpleDataTable->getMetadata('totals');
|
||||
|
||||
if (empty($metadataTotals)) {
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
$simpleTotals = $this->hideShowMetrics($metadataTotals);
|
||||
|
||||
foreach ($simpleTotals as $metric => $value) {
|
||||
if (!array_key_exists($metric, $totals)) {
|
||||
$totals[$metric] = $value;
|
||||
} else {
|
||||
$totals[$metric] += $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
}
|
||||
529
www/analytics/plugins/API/RowEvolution.php
Normal file
529
www/analytics/plugins/API/RowEvolution.php
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\API;
|
||||
|
||||
use Exception;
|
||||
use Piwik\API\DataTableManipulator\LabelFilter;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\API\ResponseBuilder;
|
||||
use Piwik\Common;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This class generates a Row evolution dataset, from input request
|
||||
*
|
||||
*/
|
||||
class RowEvolution
|
||||
{
|
||||
private static $actionsUrlReports = array(
|
||||
'getPageUrls',
|
||||
'getPageUrlsFollowingSiteSearch',
|
||||
'getEntryPageUrls',
|
||||
'getExitPageUrls',
|
||||
'getPageUrl'
|
||||
);
|
||||
|
||||
public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true)
|
||||
{
|
||||
// validation of requested $period & $date
|
||||
if ($period == 'range') {
|
||||
// load days in the range
|
||||
$period = 'day';
|
||||
}
|
||||
|
||||
if (!Period::isMultiplePeriod($date, $period)) {
|
||||
throw new Exception("Row evolutions can not be processed with this combination of \'date\' and \'period\' parameters.");
|
||||
}
|
||||
|
||||
$label = ResponseBuilder::unsanitizeLabelParameter($label);
|
||||
$labels = Piwik::getArrayFromApiParameter($label);
|
||||
|
||||
$metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal);
|
||||
|
||||
$dataTable = $this->loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal);
|
||||
|
||||
if (empty($labels)) {
|
||||
$labels = $this->getLabelsFromDataTable($dataTable, $labels);
|
||||
$dataTable = $this->enrichRowAddMetadataLabelIndex($labels, $dataTable);
|
||||
}
|
||||
if (count($labels) != 1) {
|
||||
$data = $this->getMultiRowEvolution(
|
||||
$dataTable,
|
||||
$metadata,
|
||||
$apiModule,
|
||||
$apiAction,
|
||||
$labels,
|
||||
$column,
|
||||
$legendAppendMetric,
|
||||
$labelUseAbsoluteUrl
|
||||
);
|
||||
} else {
|
||||
$data = $this->getSingleRowEvolution(
|
||||
$idSite,
|
||||
$dataTable,
|
||||
$metadata,
|
||||
$apiModule,
|
||||
$apiAction,
|
||||
$labels[0],
|
||||
$labelUseAbsoluteUrl
|
||||
);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $labels
|
||||
* @param DataTable\Map $dataTable
|
||||
* @return mixed
|
||||
*/
|
||||
protected function enrichRowAddMetadataLabelIndex($labels, $dataTable)
|
||||
{
|
||||
// set label index metadata
|
||||
$labelsToIndex = array_flip($labels);
|
||||
foreach ($dataTable->getDataTables() as $table) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
$label = $row->getColumn('label');
|
||||
if (isset($labelsToIndex[$label])) {
|
||||
$row->setMetadata(LabelFilter::FLAG_IS_ROW_EVOLUTION, $labelsToIndex[$label]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable\Map $dataTable
|
||||
* @param array $labels
|
||||
* @return array
|
||||
*/
|
||||
protected function getLabelsFromDataTable($dataTable, $labels)
|
||||
{
|
||||
// if no labels specified, use all possible labels as list
|
||||
foreach ($dataTable->getDataTables() as $table) {
|
||||
$labels = array_merge($labels, $table->getColumn('label'));
|
||||
}
|
||||
$labels = array_values(array_unique($labels));
|
||||
|
||||
// if the filter_limit query param is set, treat it as a request to limit
|
||||
// the number of labels used
|
||||
$limit = Common::getRequestVar('filter_limit', false);
|
||||
if ($limit != false
|
||||
&& $limit >= 0
|
||||
) {
|
||||
$labels = array_slice($labels, 0, $limit);
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row evolution for a single label
|
||||
* @param DataTable\Map $dataTable
|
||||
* @param array $metadata
|
||||
* @param string $apiModule
|
||||
* @param string $apiAction
|
||||
* @param string $label
|
||||
* @param bool $labelUseAbsoluteUrl
|
||||
* @return array containing report data, metadata, label, logo
|
||||
*/
|
||||
private function getSingleRowEvolution($idSite, $dataTable, $metadata, $apiModule, $apiAction, $label, $labelUseAbsoluteUrl = true)
|
||||
{
|
||||
$metricNames = array_keys($metadata['metrics']);
|
||||
|
||||
$logo = $actualLabel = false;
|
||||
$urlFound = false;
|
||||
foreach ($dataTable->getDataTables() as $date => $subTable) {
|
||||
/** @var $subTable DataTable */
|
||||
$subTable->applyQueuedFilters();
|
||||
if ($subTable->getRowsCount() > 0) {
|
||||
/** @var $row Row */
|
||||
$row = $subTable->getFirstRow();
|
||||
|
||||
if (!$actualLabel) {
|
||||
$logo = $row->getMetadata('logo');
|
||||
|
||||
$actualLabel = $this->getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $labelUseAbsoluteUrl);
|
||||
$urlFound = $actualLabel !== false;
|
||||
if (empty($actualLabel)) {
|
||||
$actualLabel = $row->getColumn('label');
|
||||
}
|
||||
}
|
||||
|
||||
// remove all columns that are not in the available metrics.
|
||||
// this removes the label as well (which is desired for two reasons: (1) it was passed
|
||||
// in the request, (2) it would cause the evolution graph to show the label in the legend).
|
||||
foreach ($row->getColumns() as $column => $value) {
|
||||
if (!in_array($column, $metricNames) && $column != 'label_html') {
|
||||
$row->deleteColumn($column);
|
||||
}
|
||||
}
|
||||
$row->deleteMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
$this->enhanceRowEvolutionMetaData($metadata, $dataTable);
|
||||
|
||||
// if we have a recursive label and no url, use the path
|
||||
if (!$urlFound) {
|
||||
$actualLabel = $this->formatQueryLabelForDisplay($idSite, $apiModule, $apiAction, $label);
|
||||
}
|
||||
|
||||
$return = array(
|
||||
'label' => SafeDecodeLabel::decodeLabelSafe($actualLabel),
|
||||
'reportData' => $dataTable,
|
||||
'metadata' => $metadata
|
||||
);
|
||||
if (!empty($logo)) {
|
||||
$return['logo'] = $logo;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function formatQueryLabelForDisplay($idSite, $apiModule, $apiAction, $label)
|
||||
{
|
||||
// rows with subtables do not contain URL metadata. this hack makes sure the label titles in row
|
||||
// evolution popovers look like URLs.
|
||||
if ($apiModule == 'Actions'
|
||||
&& in_array($apiAction, self::$actionsUrlReports)
|
||||
) {
|
||||
$mainUrl = Site::getMainUrlFor($idSite);
|
||||
$mainUrlHost = @parse_url($mainUrl, PHP_URL_HOST);
|
||||
|
||||
$replaceRegex = "/\\s*" . preg_quote(LabelFilter::SEPARATOR_RECURSIVE_LABEL) . "\\s*/";
|
||||
$cleanLabel = preg_replace($replaceRegex, '/', $label);
|
||||
|
||||
return $mainUrlHost . '/' . $cleanLabel . '/';
|
||||
} else {
|
||||
return str_replace(LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Row $row
|
||||
* @param string $apiModule
|
||||
* @param string $apiAction
|
||||
* @param bool $labelUseAbsoluteUrl
|
||||
* @return bool|string
|
||||
*/
|
||||
private function getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $labelUseAbsoluteUrl)
|
||||
{
|
||||
$url = $row->getMetadata('url');
|
||||
if ($url
|
||||
&& ($apiModule == 'Actions'
|
||||
|| ($apiModule == 'Referrers'
|
||||
&& $apiAction == 'getWebsites'))
|
||||
&& $labelUseAbsoluteUrl
|
||||
) {
|
||||
$actualLabel = preg_replace(';^http(s)?://(www.)?;i', '', $url);
|
||||
return $actualLabel;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $metadata see getRowEvolutionMetaData()
|
||||
* @param int $idSite
|
||||
* @param string $period
|
||||
* @param string $date
|
||||
* @param string $apiModule
|
||||
* @param string $apiAction
|
||||
* @param string|bool $label
|
||||
* @param string|bool $segment
|
||||
* @param int|bool $idGoal
|
||||
* @throws Exception
|
||||
* @return DataTable\Map|DataTable
|
||||
*/
|
||||
private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false)
|
||||
{
|
||||
if (!is_array($label)) {
|
||||
$label = array($label);
|
||||
}
|
||||
$label = array_map('rawurlencode', $label);
|
||||
|
||||
$parameters = array(
|
||||
'method' => $apiModule . '.' . $apiAction,
|
||||
'label' => $label,
|
||||
'idSite' => $idSite,
|
||||
'period' => $period,
|
||||
'date' => $date,
|
||||
'format' => 'original',
|
||||
'serialize' => '0',
|
||||
'segment' => $segment,
|
||||
'idGoal' => $idGoal,
|
||||
|
||||
// data for row evolution should NOT be limited
|
||||
'filter_limit' => -1,
|
||||
|
||||
// if more than one label is used, we add metadata to ensure we know which
|
||||
// row corresponds with which label (since the labels can change, and rows
|
||||
// can be sorted in a different order)
|
||||
'labelFilterAddLabelIndex' => count($label) > 1 ? 1 : 0,
|
||||
);
|
||||
|
||||
// add "processed metrics" like actions per visit or bounce rate
|
||||
// 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';
|
||||
}
|
||||
|
||||
$url = Url::getQueryStringFromParameters($parameters);
|
||||
|
||||
$request = new Request($url);
|
||||
|
||||
try {
|
||||
$dataTable = $request->process();
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("API returned an error: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given API report, returns a simpler version
|
||||
* of the metadata (will return only the metrics and the dimension name)
|
||||
* @param $idSite
|
||||
* @param $period
|
||||
* @param $date
|
||||
* @param $apiModule
|
||||
* @param $apiAction
|
||||
* @param $language
|
||||
* @param $idGoal
|
||||
* @throws Exception
|
||||
* @return array
|
||||
*/
|
||||
private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false)
|
||||
{
|
||||
$apiParameters = array();
|
||||
if (!empty($idGoal) && $idGoal > 0) {
|
||||
$apiParameters = array('idGoal' => $idGoal);
|
||||
}
|
||||
$reportMetadata = API::getInstance()->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language,
|
||||
$period, $date, $hideMetricsDoc = false, $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);
|
||||
|
||||
$metrics = $reportMetadata['metrics'];
|
||||
if (isset($reportMetadata['processedMetrics']) && is_array($reportMetadata['processedMetrics'])) {
|
||||
$metrics = $metrics + $reportMetadata['processedMetrics'];
|
||||
}
|
||||
|
||||
$dimension = $reportMetadata['dimension'];
|
||||
|
||||
return compact('metrics', 'dimension');
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the Row evolution dataTable, and the associated metadata,
|
||||
* enriches the metadata with min/max values, and % change between the first period and the last one
|
||||
* @param array $metadata
|
||||
* @param DataTable\Map $dataTable
|
||||
*/
|
||||
private function enhanceRowEvolutionMetaData(&$metadata, $dataTable)
|
||||
{
|
||||
// prepare result array for metrics
|
||||
$metricsResult = array();
|
||||
foreach ($metadata['metrics'] as $metric => $name) {
|
||||
$metricsResult[$metric] = array('name' => $name);
|
||||
|
||||
if (!empty($metadata['logos'][$metric])) {
|
||||
$metricsResult[$metric]['logo'] = $metadata['logos'][$metric];
|
||||
}
|
||||
}
|
||||
unset($metadata['logos']);
|
||||
|
||||
$subDataTables = $dataTable->getDataTables();
|
||||
$firstDataTable = reset($subDataTables);
|
||||
$firstDataTableRow = $firstDataTable->getFirstRow();
|
||||
$lastDataTable = end($subDataTables);
|
||||
$lastDataTableRow = $lastDataTable->getFirstRow();
|
||||
|
||||
// Process min/max values
|
||||
$firstNonZeroFound = array();
|
||||
foreach ($subDataTables as $subDataTable) {
|
||||
// $subDataTable is the report for one period, it has only one row
|
||||
$firstRow = $subDataTable->getFirstRow();
|
||||
foreach ($metadata['metrics'] as $metric => $label) {
|
||||
$value = $firstRow ? floatval($firstRow->getColumn($metric)) : 0;
|
||||
if ($value > 0) {
|
||||
$firstNonZeroFound[$metric] = true;
|
||||
} else if (!isset($firstNonZeroFound[$metric])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($metricsResult[$metric]['min'])
|
||||
|| $metricsResult[$metric]['min'] > $value
|
||||
) {
|
||||
$metricsResult[$metric]['min'] = $value;
|
||||
}
|
||||
if (!isset($metricsResult[$metric]['max'])
|
||||
|| $metricsResult[$metric]['max'] < $value
|
||||
) {
|
||||
$metricsResult[$metric]['max'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process % change between first/last values
|
||||
foreach ($metadata['metrics'] as $metric => $label) {
|
||||
$first = $firstDataTableRow ? floatval($firstDataTableRow->getColumn($metric)) : 0;
|
||||
$last = $lastDataTableRow ? floatval($lastDataTableRow->getColumn($metric)) : 0;
|
||||
|
||||
// do not calculate evolution if the first value is 0 (to avoid divide-by-zero)
|
||||
if ($first == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$change = CalculateEvolutionFilter::calculate($last, $first, $quotientPrecision = 0);
|
||||
$change = CalculateEvolutionFilter::prependPlusSignToNumber($change);
|
||||
$metricsResult[$metric]['change'] = $change;
|
||||
}
|
||||
|
||||
$metadata['metrics'] = $metricsResult;
|
||||
}
|
||||
|
||||
/** Get row evolution for a multiple labels */
|
||||
private function getMultiRowEvolution(DataTable\Map $dataTable, $metadata, $apiModule, $apiAction, $labels, $column,
|
||||
$legendAppendMetric = true,
|
||||
$labelUseAbsoluteUrl = true)
|
||||
{
|
||||
if (!isset($metadata['metrics'][$column])) {
|
||||
// invalid column => use the first one that's available
|
||||
$metrics = array_keys($metadata['metrics']);
|
||||
$column = reset($metrics);
|
||||
}
|
||||
|
||||
// get the processed label and logo (if any) for every requested label
|
||||
$actualLabels = $logos = array();
|
||||
foreach ($labels as $labelIdx => $label) {
|
||||
foreach ($dataTable->getDataTables() as $table) {
|
||||
$labelRow = $this->getRowEvolutionRowFromLabelIdx($table, $labelIdx);
|
||||
|
||||
if ($labelRow) {
|
||||
$actualLabels[$labelIdx] = $this->getRowUrlForEvolutionLabel(
|
||||
$labelRow, $apiModule, $apiAction, $labelUseAbsoluteUrl);
|
||||
|
||||
$prettyLabel = $labelRow->getColumn('label_html');
|
||||
if($prettyLabel !== false) {
|
||||
$actualLabels[$labelIdx] = $prettyLabel;
|
||||
}
|
||||
|
||||
$logos[$labelIdx] = $labelRow->getMetadata('logo');
|
||||
|
||||
if (!empty($actualLabels[$labelIdx])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($actualLabels[$labelIdx])) {
|
||||
$cleanLabel = $this->cleanOriginalLabel($label);
|
||||
$actualLabels[$labelIdx] = $cleanLabel;
|
||||
}
|
||||
}
|
||||
|
||||
// convert rows to be array($column.'_'.$labelIdx => $value) as opposed to
|
||||
// array('label' => $label, 'column' => $value).
|
||||
$dataTableMulti = $dataTable->getEmptyClone();
|
||||
foreach ($dataTable->getDataTables() as $tableLabel => $table) {
|
||||
$newRow = new Row();
|
||||
|
||||
foreach ($labels as $labelIdx => $label) {
|
||||
$row = $this->getRowEvolutionRowFromLabelIdx($table, $labelIdx);
|
||||
|
||||
$value = 0;
|
||||
if ($row) {
|
||||
$value = $row->getColumn($column);
|
||||
$value = floatVal(str_replace(',', '.', $value));
|
||||
}
|
||||
|
||||
if ($value == '') {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
$newLabel = $column . '_' . (int)$labelIdx;
|
||||
$newRow->addColumn($newLabel, $value);
|
||||
}
|
||||
|
||||
$newTable = $table->getEmptyClone();
|
||||
if (!empty($labels)) { // only add a row if the row has data (no labels === no data)
|
||||
$newTable->addRow($newRow);
|
||||
}
|
||||
|
||||
$dataTableMulti->addTable($newTable, $tableLabel);
|
||||
}
|
||||
|
||||
// the available metrics for the report are returned as metadata / columns
|
||||
$metadata['columns'] = $metadata['metrics'];
|
||||
|
||||
// metadata / metrics should document the rows that are compared
|
||||
// this way, UI code can be reused
|
||||
$metadata['metrics'] = array();
|
||||
foreach ($actualLabels as $labelIndex => $label) {
|
||||
if ($legendAppendMetric) {
|
||||
$label .= ' (' . $metadata['columns'][$column] . ')';
|
||||
}
|
||||
$metricName = $column . '_' . $labelIndex;
|
||||
$metadata['metrics'][$metricName] = $label;
|
||||
|
||||
if (!empty($logos[$labelIndex])) {
|
||||
$metadata['logos'][$metricName] = $logos[$labelIndex];
|
||||
}
|
||||
}
|
||||
|
||||
$this->enhanceRowEvolutionMetaData($metadata, $dataTableMulti);
|
||||
|
||||
return array(
|
||||
'column' => $column,
|
||||
'reportData' => $dataTableMulti,
|
||||
'metadata' => $metadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the row in a datatable by its LabelFilter::FLAG_IS_ROW_EVOLUTION metadata.
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @param int $labelIdx
|
||||
* @return Row|false
|
||||
*/
|
||||
private function getRowEvolutionRowFromLabelIdx($table, $labelIdx)
|
||||
{
|
||||
$labelIdx = (int)$labelIdx;
|
||||
foreach ($table->getRows() as $row) {
|
||||
if ($row->getMetadata(LabelFilter::FLAG_IS_ROW_EVOLUTION) === $labelIdx) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a prettier, more comprehensible version of a row evolution label for display.
|
||||
*/
|
||||
private function cleanOriginalLabel($label)
|
||||
{
|
||||
$label = str_replace(LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label);
|
||||
$label = SafeDecodeLabel::decodeLabelSafe($label);
|
||||
return $label;
|
||||
}
|
||||
}
|
||||
48
www/analytics/plugins/API/stylesheets/listAllAPI.less
Normal file
48
www/analytics/plugins/API/stylesheets/listAllAPI.less
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#token_auth {
|
||||
background-color: #E8FFE9;
|
||||
border: 1px solid #00CC3A;
|
||||
margin: 0 0 16px 8px;
|
||||
padding: 12px;
|
||||
line-height: 4em;
|
||||
}
|
||||
|
||||
.example, .example A {
|
||||
color: #9E9E9E;
|
||||
}
|
||||
|
||||
.page_api {
|
||||
padding: 0 15px 0 15px;
|
||||
}
|
||||
|
||||
.page_api h2 {
|
||||
border-bottom: 1px solid #DADADA;
|
||||
margin: 10px -15px 15px 0;
|
||||
padding: 0 0 5px 0;
|
||||
font-size: 24px;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.page_api p {
|
||||
line-height: 140%;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.apiFirstLine {
|
||||
font-weight: bold;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.page_api ul {
|
||||
list-style: disc outside none;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.apiDescription {
|
||||
line-height: 1.5em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.apiMethod {
|
||||
margin-bottom: 5px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
32
www/analytics/plugins/API/templates/listAllAPI.twig
Normal file
32
www/analytics/plugins/API/templates/listAllAPI.twig
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{% extends 'dashboard.twig' %}
|
||||
{% set showMenu=false %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include "@CoreHome/_siteSelectHeader.twig" %}
|
||||
|
||||
<div class="page_api pageWrap">
|
||||
|
||||
<div class="top_controls">
|
||||
{% include "@CoreHome/_periodSelect.twig" %}
|
||||
</div>
|
||||
|
||||
<h2>{{ 'API_QuickDocumentationTitle'|translate }}</h2>
|
||||
|
||||
<p>{{ 'API_PluginDescription'|translate }}</p>
|
||||
|
||||
|
||||
<p>
|
||||
<strong>{{ 'API_MoreInformation'|translate("<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/analytics-api'>","</a>","<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/analytics-api/reference'>","</a>")|raw }}</strong>
|
||||
</p>
|
||||
|
||||
<h2>{{ 'API_UserAuthentication'|translate }}</h2>
|
||||
|
||||
<p>
|
||||
{{ 'API_UsingTokenAuth'|translate('<b>','</b>',"")|raw }}<br/>
|
||||
<span id='token_auth'>&token_auth=<strong>{{ token_auth }}</strong></span><br/>
|
||||
{{ 'API_KeepTokenSecret'|translate('<b>','</b>')|raw }}
|
||||
{{ list_api_methods_with_links|raw }}
|
||||
<br/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue