add piwik installation
This commit is contained in:
parent
90aa4ef157
commit
8c5d4f0c31
3197 changed files with 563902 additions and 0 deletions
137
www/analytics/core/API/DataTableManipulator/Flattener.php
Normal file
137
www/analytics/core/API/DataTableManipulator/Flattener.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?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\API\DataTableManipulator;
|
||||
|
||||
use Piwik\API\DataTableManipulator;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* This class is responsible for flattening data tables.
|
||||
*
|
||||
* It loads subtables and combines them into a single table by concatenating the labels.
|
||||
* This manipulator is triggered by using flat=1 in the API request.
|
||||
*/
|
||||
class Flattener extends DataTableManipulator
|
||||
{
|
||||
|
||||
private $includeAggregateRows = false;
|
||||
|
||||
/**
|
||||
* If the flattener is used after calling this method, aggregate rows will
|
||||
* be included in the result. This can be useful when they contain data that
|
||||
* the leafs don't have (e.g. conversion stats in some cases).
|
||||
*/
|
||||
public function includeAggregateRows()
|
||||
{
|
||||
$this->includeAggregateRows = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Separator for building recursive labels (or paths)
|
||||
* @var string
|
||||
*/
|
||||
public $recursiveLabelSeparator = ' - ';
|
||||
|
||||
/**
|
||||
* @param DataTable $dataTable
|
||||
* @return DataTable|DataTable\Map
|
||||
*/
|
||||
public function flatten($dataTable)
|
||||
{
|
||||
if ($this->apiModule == 'Actions' || $this->apiMethod == 'getWebsites') {
|
||||
$this->recursiveLabelSeparator = '/';
|
||||
}
|
||||
|
||||
return $this->manipulate($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method called from self::manipulate.
|
||||
* Flatten each data table.
|
||||
*
|
||||
* @param DataTable $dataTable
|
||||
* @return DataTable
|
||||
*/
|
||||
protected function manipulateDataTable($dataTable)
|
||||
{
|
||||
// apply filters now since subtables have their filters applied before generic filters. if we don't do this
|
||||
// now, we'll try to apply filters to rows that have already been manipulated. this results in errors like
|
||||
// 'column ... already exists'.
|
||||
$keepFilters = true;
|
||||
if (Common::getRequestVar('disable_queued_filters', 0, 'int', $this->request) == 0) {
|
||||
$dataTable->applyQueuedFilters();
|
||||
$keepFilters = false;
|
||||
}
|
||||
|
||||
$newDataTable = $dataTable->getEmptyClone($keepFilters);
|
||||
foreach ($dataTable->getRows() as $row) {
|
||||
$this->flattenRow($row, $newDataTable);
|
||||
}
|
||||
return $newDataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Row $row
|
||||
* @param DataTable $dataTable
|
||||
* @param string $labelPrefix
|
||||
* @param bool $parentLogo
|
||||
*/
|
||||
private function flattenRow(Row $row, DataTable $dataTable,
|
||||
$labelPrefix = '', $parentLogo = false)
|
||||
{
|
||||
$label = $row->getColumn('label');
|
||||
if ($label !== false) {
|
||||
$label = trim($label);
|
||||
if (substr($label, 0, 1) == '/' && $this->recursiveLabelSeparator == '/') {
|
||||
$label = substr($label, 1);
|
||||
}
|
||||
$label = $labelPrefix . $label;
|
||||
$row->setColumn('label', $label);
|
||||
}
|
||||
|
||||
$logo = $row->getMetadata('logo');
|
||||
if ($logo === false && $parentLogo !== false) {
|
||||
$logo = $parentLogo;
|
||||
$row->setMetadata('logo', $logo);
|
||||
}
|
||||
|
||||
$subTable = $this->loadSubtable($dataTable, $row);
|
||||
$row->removeSubtable();
|
||||
|
||||
if ($subTable === null) {
|
||||
if ($this->includeAggregateRows) {
|
||||
$row->setMetadata('is_aggregate', 0);
|
||||
}
|
||||
$dataTable->addRow($row);
|
||||
} else {
|
||||
if ($this->includeAggregateRows) {
|
||||
$row->setMetadata('is_aggregate', 1);
|
||||
$dataTable->addRow($row);
|
||||
}
|
||||
$prefix = $label . $this->recursiveLabelSeparator;
|
||||
foreach ($subTable->getRows() as $row) {
|
||||
$this->flattenRow($row, $dataTable, $prefix, $logo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the flat parameter from the subtable request
|
||||
*
|
||||
* @param array $request
|
||||
*/
|
||||
protected function manipulateSubtableRequest($request)
|
||||
{
|
||||
unset($request['flat']);
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
167
www/analytics/core/API/DataTableManipulator/LabelFilter.php
Normal file
167
www/analytics/core/API/DataTableManipulator/LabelFilter.php
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<?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\API\DataTableManipulator;
|
||||
|
||||
use Piwik\API\DataTableManipulator;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* This class is responsible for handling the label parameter that can be
|
||||
* added to every API call. If the parameter is set, only the row with the matching
|
||||
* label is returned.
|
||||
*
|
||||
* The labels passed to this class should be urlencoded.
|
||||
* Some reports use recursive labels (e.g. action reports). Use > to join them.
|
||||
*/
|
||||
class LabelFilter extends DataTableManipulator
|
||||
{
|
||||
const SEPARATOR_RECURSIVE_LABEL = '>';
|
||||
|
||||
private $labels;
|
||||
private $addLabelIndex;
|
||||
const FLAG_IS_ROW_EVOLUTION = 'label_index';
|
||||
|
||||
/**
|
||||
* Filter a data table by label.
|
||||
* The filtered table is returned, which might be a new instance.
|
||||
*
|
||||
* $apiModule, $apiMethod and $request are needed load sub-datatables
|
||||
* for the recursive search. If the label is not recursive, these parameters
|
||||
* are not needed.
|
||||
*
|
||||
* @param string $labels the labels to search for
|
||||
* @param DataTable $dataTable the data table to be filtered
|
||||
* @param bool $addLabelIndex Whether to add label_index metadata describing which
|
||||
* label a row corresponds to.
|
||||
* @return DataTable
|
||||
*/
|
||||
public function filter($labels, $dataTable, $addLabelIndex = false)
|
||||
{
|
||||
if (!is_array($labels)) {
|
||||
$labels = array($labels);
|
||||
}
|
||||
|
||||
$this->labels = $labels;
|
||||
$this->addLabelIndex = (bool)$addLabelIndex;
|
||||
return $this->manipulate($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for the recursive descend
|
||||
*
|
||||
* @param array $labelParts
|
||||
* @param DataTable $dataTable
|
||||
* @return Row|bool
|
||||
*/
|
||||
private function doFilterRecursiveDescend($labelParts, $dataTable)
|
||||
{
|
||||
// search for the first part of the tree search
|
||||
$labelPart = array_shift($labelParts);
|
||||
|
||||
$row = false;
|
||||
foreach ($this->getLabelVariations($labelPart) as $labelPart) {
|
||||
$row = $dataTable->getRowFromLabel($labelPart);
|
||||
if ($row !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($row === false) {
|
||||
// not found
|
||||
return false;
|
||||
}
|
||||
|
||||
// end of tree search reached
|
||||
if (count($labelParts) == 0) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
$subTable = $this->loadSubtable($dataTable, $row);
|
||||
if ($subTable === null) {
|
||||
// no more subtables but label parts left => no match found
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->doFilterRecursiveDescend($labelParts, $subTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up request for ResponseBuilder to behave correctly
|
||||
*
|
||||
* @param $request
|
||||
*/
|
||||
protected function manipulateSubtableRequest($request)
|
||||
{
|
||||
unset($request['label']);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use variations of the label to make it easier to specify the desired label
|
||||
*
|
||||
* Note: The HTML Encoded version must be tried first, since in ResponseBuilder the $label is unsanitized
|
||||
* via Common::unsanitizeLabelParameter.
|
||||
*
|
||||
* @param string $label
|
||||
* @return array
|
||||
*/
|
||||
private function getLabelVariations($label)
|
||||
{
|
||||
static $pageTitleReports = array('getPageTitles', 'getEntryPageTitles', 'getExitPageTitles');
|
||||
|
||||
$variations = array();
|
||||
$label = urldecode($label);
|
||||
$label = trim($label);
|
||||
|
||||
$sanitizedLabel = Common::sanitizeInputValue($label);
|
||||
$variations[] = $sanitizedLabel;
|
||||
|
||||
if ($this->apiModule == 'Actions'
|
||||
&& in_array($this->apiMethod, $pageTitleReports)
|
||||
) {
|
||||
// special case: the Actions.getPageTitles report prefixes some labels with a blank.
|
||||
// the blank might be passed by the user but is removed in Request::getRequestArrayFromString.
|
||||
$variations[] = ' ' . $sanitizedLabel;
|
||||
$variations[] = ' ' . $label;
|
||||
}
|
||||
$variations[] = $label;
|
||||
|
||||
return $variations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a DataTable instance. See @filter for more info.
|
||||
*
|
||||
* @param DataTable\Simple|DataTable\Map $dataTable
|
||||
* @return mixed
|
||||
*/
|
||||
protected function manipulateDataTable($dataTable)
|
||||
{
|
||||
$result = $dataTable->getEmptyClone();
|
||||
foreach ($this->labels as $labelIndex => $label) {
|
||||
$row = null;
|
||||
foreach ($this->getLabelVariations($label) as $labelVariation) {
|
||||
$labelVariation = explode(self::SEPARATOR_RECURSIVE_LABEL, $labelVariation);
|
||||
|
||||
$row = $this->doFilterRecursiveDescend($labelVariation, $dataTable);
|
||||
if ($row) {
|
||||
if ($this->addLabelIndex) {
|
||||
$row->setMetadata(self::FLAG_IS_ROW_EVOLUTION, $labelIndex);
|
||||
}
|
||||
$result->addRow($row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<?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\API\DataTableManipulator;
|
||||
|
||||
use Piwik\API\DataTableManipulator;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Plugins\API\API;
|
||||
|
||||
/**
|
||||
* This class is responsible for setting the metadata property 'totals' on each dataTable if the report
|
||||
* has a dimension. 'Totals' means it tries to calculate the total report value for each metric. For each
|
||||
* the total number of visits, actions, ... for a given report / dataTable.
|
||||
*/
|
||||
class ReportTotalsCalculator extends DataTableManipulator
|
||||
{
|
||||
/**
|
||||
* Cached report metadata array.
|
||||
* @var array
|
||||
*/
|
||||
private static $reportMetadata = array();
|
||||
|
||||
/**
|
||||
* @param DataTable $table
|
||||
* @return \Piwik\DataTable|\Piwik\DataTable\Map
|
||||
*/
|
||||
public function calculate($table)
|
||||
{
|
||||
// apiModule and/or apiMethod is empty for instance in case when flat=1 is called. Basically whenever a
|
||||
// datamanipulator calls the API and wants the dataTable in return, see callApiAndReturnDataTable().
|
||||
// it is also not set for some settings API request etc.
|
||||
if (empty($this->apiModule) || empty($this->apiMethod)) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->manipulate($table);
|
||||
} catch(\Exception $e) {
|
||||
// eg. requests with idSubtable may trigger this exception
|
||||
// (where idSubtable was removed in
|
||||
// ?module=API&method=Events.getNameFromCategoryId&idSubtable=1&secondaryDimension=eventName&format=XML&idSite=1&period=day&date=yesterday&flat=0
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds ratio metrics if possible.
|
||||
*
|
||||
* @param DataTable $dataTable
|
||||
* @return DataTable
|
||||
*/
|
||||
protected function manipulateDataTable($dataTable)
|
||||
{
|
||||
$report = $this->findCurrentReport();
|
||||
|
||||
if (!empty($report) && empty($report['dimension'])) {
|
||||
// we currently do not calculate the total value for reports having no dimension
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
// Array [readableMetric] => [summed value]
|
||||
$totalValues = array();
|
||||
|
||||
$firstLevelTable = $this->makeSureToWorkOnFirstLevelDataTable($dataTable);
|
||||
$metricsToCalculate = Metrics::getMetricIdsToProcessReportTotal();
|
||||
|
||||
foreach ($metricsToCalculate as $metricId) {
|
||||
if (!$this->hasDataTableMetric($firstLevelTable, $metricId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($firstLevelTable->getRows() as $row) {
|
||||
$totalValues = $this->sumColumnValueToTotal($row, $metricId, $totalValues);
|
||||
}
|
||||
}
|
||||
|
||||
$dataTable->setMetadata('totals', $totalValues);
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
private function hasDataTableMetric(DataTable $dataTable, $metricId)
|
||||
{
|
||||
$firstRow = $dataTable->getFirstRow();
|
||||
|
||||
if (empty($firstRow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === $this->getColumn($firstRow, $metricId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns column from a given row.
|
||||
* Will work with 2 types of datatable
|
||||
* - raw datatables coming from the archive DB, which columns are int indexed
|
||||
* - datatables processed resulting of API calls, which columns have human readable english names
|
||||
*
|
||||
* @param Row|array $row
|
||||
* @param int $columnIdRaw see consts in Metrics::
|
||||
* @return mixed Value of column, false if not found
|
||||
*/
|
||||
private function getColumn($row, $columnIdRaw)
|
||||
{
|
||||
$columnIdReadable = Metrics::getReadableColumnName($columnIdRaw);
|
||||
|
||||
if ($row instanceof Row) {
|
||||
$raw = $row->getColumn($columnIdRaw);
|
||||
if ($raw !== false) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
return $row->getColumn($columnIdReadable);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function makeSureToWorkOnFirstLevelDataTable($table)
|
||||
{
|
||||
if (!array_key_exists('idSubtable', $this->request)) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
$firstLevelReport = $this->findFirstLevelReport();
|
||||
|
||||
if (empty($firstLevelReport)) {
|
||||
// it is not a subtable report
|
||||
$module = $this->apiModule;
|
||||
$action = $this->apiMethod;
|
||||
} else {
|
||||
$module = $firstLevelReport['module'];
|
||||
$action = $firstLevelReport['action'];
|
||||
}
|
||||
|
||||
$request = $this->request;
|
||||
|
||||
/** @var \Piwik\Period $period */
|
||||
$period = $table->getMetadata('period');
|
||||
|
||||
if (!empty($period)) {
|
||||
// we want a dataTable, not a dataTable\map
|
||||
if (Period::isMultiplePeriod($request['date'], $request['period']) || 'range' == $period->getLabel()) {
|
||||
$request['date'] = $period->getRangeString();
|
||||
$request['period'] = 'range';
|
||||
} else {
|
||||
$request['date'] = $period->getDateStart()->toString();
|
||||
$request['period'] = $period->getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->callApiAndReturnDataTable($module, $action, $request);
|
||||
}
|
||||
|
||||
private function sumColumnValueToTotal(Row $row, $metricId, $totalValues)
|
||||
{
|
||||
$value = $this->getColumn($row, $metricId);
|
||||
|
||||
if (false === $value) {
|
||||
|
||||
return $totalValues;
|
||||
}
|
||||
|
||||
$metricName = Metrics::getReadableColumnName($metricId);
|
||||
|
||||
if (array_key_exists($metricName, $totalValues)) {
|
||||
$totalValues[$metricName] += $value;
|
||||
} else {
|
||||
$totalValues[$metricName] = $value;
|
||||
}
|
||||
|
||||
return $totalValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to get all rows of the first level table.
|
||||
*
|
||||
* @param array $request
|
||||
*/
|
||||
protected function manipulateSubtableRequest($request)
|
||||
{
|
||||
$request['totals'] = 0;
|
||||
$request['expanded'] = 0;
|
||||
$request['filter_limit'] = -1;
|
||||
$request['filter_offset'] = 0;
|
||||
|
||||
$parametersToRemove = array('flat');
|
||||
|
||||
if (!array_key_exists('idSubtable', $this->request)) {
|
||||
$parametersToRemove[] = 'idSubtable';
|
||||
}
|
||||
|
||||
foreach ($parametersToRemove as $param) {
|
||||
if (array_key_exists($param, $request)) {
|
||||
unset($request[$param]);
|
||||
}
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function getReportMetadata()
|
||||
{
|
||||
if (!empty(static::$reportMetadata)) {
|
||||
return static::$reportMetadata;
|
||||
}
|
||||
|
||||
static::$reportMetadata = API::getInstance()->getReportMetadata();
|
||||
|
||||
return static::$reportMetadata;
|
||||
}
|
||||
|
||||
private function findCurrentReport()
|
||||
{
|
||||
foreach ($this->getReportMetadata() as $report) {
|
||||
if ($this->apiMethod == $report['action']
|
||||
&& $this->apiModule == $report['module']) {
|
||||
|
||||
return $report;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findFirstLevelReport()
|
||||
{
|
||||
foreach ($this->getReportMetadata() as $report) {
|
||||
if (!empty($report['actionToLoadSubTables'])
|
||||
&& $this->apiMethod == $report['actionToLoadSubTables']
|
||||
&& $this->apiModule == $report['module']
|
||||
) {
|
||||
|
||||
return $report;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue