add piwik installation
This commit is contained in:
parent
90aa4ef157
commit
8c5d4f0c31
44 changed files with 563902 additions and 0 deletions
81
www/analytics/core/DataTable/BaseFilter.php
Normal file
81
www/analytics/core/DataTable/BaseFilter.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?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\DataTable;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* A filter is set of logic that manipulates a DataTable. Existing filters do things like,
|
||||
*
|
||||
* - remove rows
|
||||
* - change column values (change string to lowercase, truncate, etc.)
|
||||
* - add/remove columns or metadata (compute percentage values, add an 'icon' metadata based on the label, etc.)
|
||||
* - add/remove/edit subtable associated with rows
|
||||
* - etc.
|
||||
*
|
||||
* Filters are called with a DataTable instance and extra parameters that are specified
|
||||
* in {@link Piwik\DataTable::filter()} and {@link Piwik\DataTable::queueFilter()}.
|
||||
*
|
||||
* To see examples of Filters look at the existing ones in the Piwik\DataTable\BaseFilter
|
||||
* namespace.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class BaseFilter
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $enableRecursive = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function __construct(DataTable $table)
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulates a {@link DataTable} in some way.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
abstract public function filter($table);
|
||||
|
||||
/**
|
||||
* Enables/Disables recursive filtering. Whether this property is actually used
|
||||
* is up to the derived BaseFilter class.
|
||||
*
|
||||
* @param bool $enable
|
||||
*/
|
||||
public function enableRecursive($enable)
|
||||
{
|
||||
$this->enableRecursive = (bool)$enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a row's subtable, if one exists and is loaded in memory.
|
||||
*
|
||||
* @param Row $row The row whose subtable should be filter.
|
||||
*/
|
||||
public function filterSubTable(Row $row)
|
||||
{
|
||||
if (!$this->enableRecursive) {
|
||||
return;
|
||||
}
|
||||
if ($row->isSubtableLoaded()) {
|
||||
$subTable = Manager::getInstance()->getTable($row->getIdSubDataTable());
|
||||
$this->filter($subTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
www/analytics/core/DataTable/Bridges.php
Normal file
28
www/analytics/core/DataTable/Bridges.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This contains the bridge classes which were used prior to Piwik 2.0
|
||||
* The serialized reports contains these classes below, which were not using namespaces yet
|
||||
*/
|
||||
namespace {
|
||||
|
||||
use Piwik\DataTable\Row\DataTableSummaryRow;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
class Piwik_DataTable_Row_DataTableSummary extends DataTableSummaryRow
|
||||
{
|
||||
}
|
||||
|
||||
class Piwik_DataTable_Row extends Row
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
30
www/analytics/core/DataTable/DataTableInterface.php
Normal file
30
www/analytics/core/DataTable/DataTableInterface.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?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\DataTable;
|
||||
|
||||
/**
|
||||
* The DataTable Interface
|
||||
*
|
||||
*/
|
||||
interface DataTableInterface
|
||||
{
|
||||
public function getRowsCount();
|
||||
public function queueFilter($className, $parameters = array());
|
||||
public function applyQueuedFilters();
|
||||
public function filter($className, $parameters = array());
|
||||
public function getFirstRow();
|
||||
public function __toString();
|
||||
public function enableRecursiveSort();
|
||||
public function renameColumn($oldName, $newName);
|
||||
public function deleteColumns($columns, $deleteRecursiveInSubtables = false);
|
||||
public function deleteRow($id);
|
||||
public function deleteColumn($name);
|
||||
public function getColumn($name);
|
||||
public function getColumns();
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
|
||||
/**
|
||||
* Adds processed metrics columns to a {@link DataTable} using metrics that already exist.
|
||||
*
|
||||
* Columns added are:
|
||||
*
|
||||
* - **conversion_rate**: percent value of `nb_visits_converted / nb_visits
|
||||
* - **nb_actions_per_visit**: `nb_actions / nb_visits`
|
||||
* - **avg_time_on_site**: in number of seconds, `round(visit_length / nb_visits)`. Not
|
||||
* pretty formatted.
|
||||
* - **bounce_rate**: percent value of `bounce_count / nb_visits`
|
||||
*
|
||||
* Adding the **filter_add_columns_when_show_all_columns** query parameter to
|
||||
* an API request will trigger the execution of this Filter.
|
||||
*
|
||||
* _Note: This filter must be called before {@link ReplaceColumnNames} is called._
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddColumnsProcessedMetrics');
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddColumnsProcessedMetrics extends BaseFilter
|
||||
{
|
||||
protected $invalidDivision = 0;
|
||||
protected $roundPrecision = 2;
|
||||
protected $deleteRowsWithNoVisit = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The table to eventually filter.
|
||||
* @param bool $deleteRowsWithNoVisit Whether to delete rows with no visits or not.
|
||||
*/
|
||||
public function __construct($table, $deleteRowsWithNoVisit = true)
|
||||
{
|
||||
$this->deleteRowsWithNoVisit = $deleteRowsWithNoVisit;
|
||||
parent::__construct($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the processed metrics. See {@link AddColumnsProcessedMetrics} for
|
||||
* more information.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$rowsIdToDelete = array();
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$nbVisits = $this->getColumn($row, Metrics::INDEX_NB_VISITS);
|
||||
$nbActions = $this->getColumn($row, Metrics::INDEX_NB_ACTIONS);
|
||||
if ($nbVisits == 0
|
||||
&& $nbActions == 0
|
||||
&& $this->deleteRowsWithNoVisit
|
||||
) {
|
||||
// case of keyword/website/campaign with a conversion for this day,
|
||||
// but no visit, we don't show it
|
||||
$rowsIdToDelete[] = $key;
|
||||
continue;
|
||||
}
|
||||
|
||||
$nbVisitsConverted = (int)$this->getColumn($row, Metrics::INDEX_NB_VISITS_CONVERTED);
|
||||
if ($nbVisitsConverted > 0) {
|
||||
$conversionRate = round(100 * $nbVisitsConverted / $nbVisits, $this->roundPrecision);
|
||||
try {
|
||||
$row->addColumn('conversion_rate', $conversionRate . "%");
|
||||
} catch (\Exception $e) {
|
||||
// conversion_rate can be defined upstream apparently? FIXME
|
||||
}
|
||||
}
|
||||
|
||||
if ($nbVisits == 0) {
|
||||
$actionsPerVisit = $averageTimeOnSite = $bounceRate = $this->invalidDivision;
|
||||
} else {
|
||||
// nb_actions / nb_visits => Actions/visit
|
||||
// sum_visit_length / nb_visits => Avg. Time on Site
|
||||
// bounce_count / nb_visits => Bounce Rate
|
||||
$actionsPerVisit = round($nbActions / $nbVisits, $this->roundPrecision);
|
||||
$visitLength = $this->getColumn($row, Metrics::INDEX_SUM_VISIT_LENGTH);
|
||||
$averageTimeOnSite = round($visitLength / $nbVisits, $rounding = 0);
|
||||
$bounceRate = round(100 * $this->getColumn($row, Metrics::INDEX_BOUNCE_COUNT) / $nbVisits, $this->roundPrecision);
|
||||
}
|
||||
try {
|
||||
$row->addColumn('nb_actions_per_visit', $actionsPerVisit);
|
||||
$row->addColumn('avg_time_on_site', $averageTimeOnSite);
|
||||
// It could be useful for API users to have raw sum length value.
|
||||
//$row->addMetadata('sum_visit_length', $visitLength);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
try {
|
||||
$row->addColumn('bounce_rate', $bounceRate . "%");
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
$table->deleteRows($rowsIdToDelete);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Archive::
|
||||
* @param bool|array $mappingIdToName
|
||||
* @return mixed Value of column, false if not found
|
||||
*/
|
||||
protected function getColumn($row, $columnIdRaw, $mappingIdToName = false)
|
||||
{
|
||||
if (empty($mappingIdToName)) {
|
||||
$mappingIdToName = Metrics::$mappingFromIdToName;
|
||||
}
|
||||
$columnIdReadable = $mappingIdToName[$columnIdRaw];
|
||||
if ($row instanceof Row) {
|
||||
$raw = $row->getColumn($columnIdRaw);
|
||||
if ($raw !== false) {
|
||||
return $raw;
|
||||
}
|
||||
return $row->getColumn($columnIdReadable);
|
||||
}
|
||||
if (isset($row[$columnIdRaw])) {
|
||||
return $row[$columnIdRaw];
|
||||
}
|
||||
if (isset($row[$columnIdReadable])) {
|
||||
return $row[$columnIdReadable];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Exception;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
|
||||
/**
|
||||
* Adds goal related metrics to a {@link DataTable} using metrics that already exist.
|
||||
*
|
||||
* Metrics added are:
|
||||
* - **revenue_per_visit**: total goal and ecommerce revenue / nb_visits
|
||||
* - **goal_%idGoal%_conversion_rate**: the conversion rate. There will be one of
|
||||
* these columns for each goal that exists
|
||||
* for the site.
|
||||
* - **goal_%idGoal%_nb_conversions**: the number of conversions. There will be one of
|
||||
* these columns for each goal that exists
|
||||
* for the site.
|
||||
* - **goal_%idGoal%_revenue_per_visit**: goal revenue / nb_visits. There will be one of
|
||||
* these columns for each goal that exists
|
||||
* for the site.
|
||||
* - **goal_%idGoal%_revenue**: goal revenue. There will be one of
|
||||
* these columns for each goal that exists
|
||||
* for the site.
|
||||
* - **goal_%idGoal%_avg_order_revenue**: goal revenue / number of orders or abandoned
|
||||
* carts. Only for ecommerce order and abandoned cart
|
||||
* reports.
|
||||
* - **goal_%idGoal%_items**: number of items. Only for ecommerce order and abandoned cart
|
||||
* reports.
|
||||
*
|
||||
* Adding the **filter_update_columns_when_show_all_goals** query parameter to
|
||||
* an API request will trigger the execution of this Filter.
|
||||
*
|
||||
* _Note: This filter must be called before {@link ReplaceColumnNames} is called._
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddColumnsProcessedMetricsGoal',
|
||||
* array($enable = true, $idGoal = Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics
|
||||
{
|
||||
/**
|
||||
* Process main goal metrics: conversion rate, revenue per visit
|
||||
*/
|
||||
const GOALS_MINIMAL_REPORT = -2;
|
||||
|
||||
/**
|
||||
* Process main goal metrics, and conversion rate per goal
|
||||
*/
|
||||
const GOALS_OVERVIEW = -1;
|
||||
|
||||
/**
|
||||
* Process all goal and per-goal metrics
|
||||
*/
|
||||
const GOALS_FULL_TABLE = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The table that will eventually filtered.
|
||||
* @param bool $enable Always set to true.
|
||||
* @param string $processOnlyIdGoal Defines what metrics to add (don't process metrics when you don't display them).
|
||||
* If self::GOALS_FULL_TABLE, all Goal metrics (and per goal metrics) will be processed.
|
||||
* If self::GOALS_OVERVIEW, only the main goal metrics will be added.
|
||||
* If an int > 0, then will process only metrics for this specific Goal.
|
||||
*/
|
||||
public function __construct($table, $enable = true, $processOnlyIdGoal)
|
||||
{
|
||||
$this->processOnlyIdGoal = $processOnlyIdGoal;
|
||||
$this->isEcommerce = $this->processOnlyIdGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER || $this->processOnlyIdGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART;
|
||||
parent::__construct($table);
|
||||
// Ensure that all rows with no visit but conversions will be displayed
|
||||
$this->deleteRowsWithNoVisit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the processed metrics. See {@link AddColumnsProcessedMetrics} for
|
||||
* more information.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
// Add standard processed metrics
|
||||
parent::filter($table);
|
||||
$roundingPrecision = GoalManager::REVENUE_PRECISION;
|
||||
$expectedColumns = array();
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$currentColumns = $row->getColumns();
|
||||
$newColumns = array();
|
||||
|
||||
// visits could be undefined when there is a conversion but no visit
|
||||
$nbVisits = (int)$this->getColumn($row, Metrics::INDEX_NB_VISITS);
|
||||
$conversions = (int)$this->getColumn($row, Metrics::INDEX_NB_CONVERSIONS);
|
||||
$goals = $this->getColumn($currentColumns, Metrics::INDEX_GOALS);
|
||||
if ($goals) {
|
||||
$revenue = 0;
|
||||
foreach ($goals as $goalId => $goalMetrics) {
|
||||
if ($goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
|
||||
continue;
|
||||
}
|
||||
if ($goalId >= GoalManager::IDGOAL_ORDER
|
||||
|| $goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER
|
||||
) {
|
||||
$revenue += (int)$this->getColumn($goalMetrics, Metrics::INDEX_GOAL_REVENUE, Metrics::$mappingFromIdToNameGoal);
|
||||
}
|
||||
}
|
||||
|
||||
if ($revenue == 0) {
|
||||
$revenue = (int)$this->getColumn($currentColumns, Metrics::INDEX_REVENUE);
|
||||
}
|
||||
if (!isset($currentColumns['revenue_per_visit'])) {
|
||||
// If no visit for this metric, but some conversions, we still want to display some kind of "revenue per visit"
|
||||
// even though it will actually be in this edge case "Revenue per conversion"
|
||||
$revenuePerVisit = $this->invalidDivision;
|
||||
if ($nbVisits > 0
|
||||
|| $conversions > 0
|
||||
) {
|
||||
$revenuePerVisit = round($revenue / ($nbVisits == 0 ? $conversions : $nbVisits), $roundingPrecision);
|
||||
}
|
||||
$newColumns['revenue_per_visit'] = $revenuePerVisit;
|
||||
}
|
||||
if ($this->processOnlyIdGoal == self::GOALS_MINIMAL_REPORT) {
|
||||
$row->addColumns($newColumns);
|
||||
continue;
|
||||
}
|
||||
// Display per goal metrics
|
||||
// - conversion rate
|
||||
// - conversions
|
||||
// - revenue per visit
|
||||
foreach ($goals as $goalId => $goalMetrics) {
|
||||
$goalId = str_replace("idgoal=", "", $goalId);
|
||||
if (($this->processOnlyIdGoal > self::GOALS_FULL_TABLE
|
||||
|| $this->isEcommerce)
|
||||
&& $this->processOnlyIdGoal != $goalId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$conversions = (int)$this->getColumn($goalMetrics, Metrics::INDEX_GOAL_NB_CONVERSIONS, Metrics::$mappingFromIdToNameGoal);
|
||||
|
||||
// Goal Conversion rate
|
||||
$name = 'goal_' . $goalId . '_conversion_rate';
|
||||
if ($nbVisits == 0) {
|
||||
$value = $this->invalidDivision;
|
||||
} else {
|
||||
$value = round(100 * $conversions / $nbVisits, $roundingPrecision);
|
||||
}
|
||||
$newColumns[$name] = $value . "%";
|
||||
$expectedColumns[$name] = true;
|
||||
|
||||
// When the table is displayed by clicking on the flag icon, we only display the columns
|
||||
// Visits, Conversions, Per goal conversion rate, Revenue
|
||||
if ($this->processOnlyIdGoal == self::GOALS_OVERVIEW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Goal Conversions
|
||||
$name = 'goal_' . $goalId . '_nb_conversions';
|
||||
$newColumns[$name] = $conversions;
|
||||
$expectedColumns[$name] = true;
|
||||
|
||||
// Goal Revenue per visit
|
||||
$name = 'goal_' . $goalId . '_revenue_per_visit';
|
||||
// See comment above for $revenuePerVisit
|
||||
$goalRevenue = (float)$this->getColumn($goalMetrics, Metrics::INDEX_GOAL_REVENUE, Metrics::$mappingFromIdToNameGoal);
|
||||
$revenuePerVisit = round($goalRevenue / ($nbVisits == 0 ? $conversions : $nbVisits), $roundingPrecision);
|
||||
$newColumns[$name] = $revenuePerVisit;
|
||||
$expectedColumns[$name] = true;
|
||||
|
||||
// Total revenue
|
||||
$name = 'goal_' . $goalId . '_revenue';
|
||||
$newColumns[$name] = $goalRevenue;
|
||||
$expectedColumns[$name] = true;
|
||||
|
||||
if ($this->isEcommerce) {
|
||||
|
||||
// AOV Average Order Value
|
||||
$name = 'goal_' . $goalId . '_avg_order_revenue';
|
||||
$newColumns[$name] = $goalRevenue / $conversions;
|
||||
$expectedColumns[$name] = true;
|
||||
|
||||
// Items qty
|
||||
$name = 'goal_' . $goalId . '_items';
|
||||
$newColumns[$name] = $this->getColumn($goalMetrics, Metrics::INDEX_GOAL_ECOMMERCE_ITEMS, Metrics::$mappingFromIdToNameGoal);
|
||||
$expectedColumns[$name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// conversion_rate can be defined upstream apparently? FIXME
|
||||
try {
|
||||
$row->addColumns($newColumns);
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
$expectedColumns['revenue_per_visit'] = true;
|
||||
|
||||
// make sure all goals values are set, 0 by default
|
||||
// if no value then sorting would put at the end
|
||||
$expectedColumns = array_keys($expectedColumns);
|
||||
$rows = $table->getRows();
|
||||
foreach ($rows as &$row) {
|
||||
foreach ($expectedColumns as $name) {
|
||||
if (false === $row->getColumn($name)) {
|
||||
$value = 0;
|
||||
if (strpos($name, 'conversion_rate') !== false) {
|
||||
$value = '0%';
|
||||
}
|
||||
$row->addColumn($name, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
www/analytics/core/DataTable/Filter/AddSummaryRow.php
Normal file
52
www/analytics/core/DataTable/Filter/AddSummaryRow.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row\DataTableSummaryRow;
|
||||
|
||||
/**
|
||||
* Adds a summary row to {@link DataTable}s that contains the sum of all other table rows.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddSummaryRow');
|
||||
*
|
||||
* // use a human readable label for the summary row (instead of '-1')
|
||||
* $dataTable->filter('AddSummaryRow', array($labelSummaryRow = Piwik::translate('General_Total')));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddSummaryRow extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The table that will be filtered.
|
||||
* @param int $labelSummaryRow The value of the label column for the new row.
|
||||
*/
|
||||
public function __construct($table, $labelSummaryRow = DataTable::LABEL_SUMMARY_ROW)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->labelSummaryRow = $labelSummaryRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the filter. See {@link AddSummaryRow}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$row = new DataTableSummaryRow($table);
|
||||
$row->setColumn('label', $this->labelSummaryRow);
|
||||
$table->addSummaryRow($row);
|
||||
}
|
||||
}
|
||||
168
www/analytics/core/DataTable/Filter/BeautifyRangeLabels.php
Normal file
168
www/analytics/core/DataTable/Filter/BeautifyRangeLabels.php
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Piwik;
|
||||
|
||||
/**
|
||||
* A {@link DataTable} filter that replaces range label columns with prettier,
|
||||
* human-friendlier versions.
|
||||
*
|
||||
* When reports that summarize data over a set of ranges (such as the
|
||||
* reports in the **VisitorInterest** plugin) are archived, they are
|
||||
* archived with labels that read as: '$min-$max' or '$min+'. These labels
|
||||
* have no units and can look like '1-1'.
|
||||
*
|
||||
* This filter can be used to clean up and add units to those range labels. To
|
||||
* do this, you supply a string to use when the range specifies only
|
||||
* one unit (ie '1-1') and another format string when the range specifies
|
||||
* more than one unit (ie '2-2', '3-5' or '6+').
|
||||
*
|
||||
* This filter can be extended to vary exactly how ranges are prettified based
|
||||
* on the range values found in the DataTable. To see an example of this,
|
||||
* take a look at the {@link BeautifyTimeRangeLabels} filter.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->queueFilter('BeautifyRangeLabels', array("1 visit", "%s visits"));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class BeautifyRangeLabels extends ColumnCallbackReplace
|
||||
{
|
||||
/**
|
||||
* The string to use when the range being beautified is between 1-1 units.
|
||||
* @var string
|
||||
*/
|
||||
protected $labelSingular;
|
||||
|
||||
/**
|
||||
* The format string to use when the range being beautified references more than
|
||||
* one unit.
|
||||
* @var string
|
||||
*/
|
||||
protected $labelPlural;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will be filtered.
|
||||
* @param string $labelSingular The string to use when the range being beautified
|
||||
* is equal to '1-1 units', eg `"1 visit"`.
|
||||
* @param string $labelPlural The string to use when the range being beautified
|
||||
* references more than one unit. This must be a format
|
||||
* string that takes one string parameter, eg, `"%s visits"`.
|
||||
*/
|
||||
public function __construct($table, $labelSingular, $labelPlural)
|
||||
{
|
||||
parent::__construct($table, 'label', array($this, 'beautify'), array());
|
||||
|
||||
$this->labelSingular = $labelSingular;
|
||||
$this->labelPlural = $labelPlural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies a range label and returns the pretty result. See {@link BeautifyRangeLabels}.
|
||||
*
|
||||
* @param string $value The range string. This must be in either a '$min-$max' format
|
||||
* a '$min+' format.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function beautify($value)
|
||||
{
|
||||
// if there's more than one element, handle as a range w/ an upper bound
|
||||
if (strpos($value, "-") !== false) {
|
||||
// get the range
|
||||
sscanf($value, "%d - %d", $lowerBound, $upperBound);
|
||||
|
||||
// if the lower bound is the same as the upper bound make sure the singular label
|
||||
// is used
|
||||
if ($lowerBound == $upperBound) {
|
||||
return $this->getSingleUnitLabel($value, $lowerBound);
|
||||
} else {
|
||||
return $this->getRangeLabel($value, $lowerBound, $upperBound);
|
||||
}
|
||||
} // if there's one element, handle as a range w/ no upper bound
|
||||
else {
|
||||
// get the lower bound
|
||||
sscanf($value, "%d", $lowerBound);
|
||||
|
||||
if ($lowerBound !== null) {
|
||||
$plusEncoded = urlencode('+');
|
||||
$plusLen = strlen($plusEncoded);
|
||||
$len = strlen($value);
|
||||
|
||||
// if the label doesn't end with a '+', append it
|
||||
if ($len < $plusLen || substr($value, $len - $plusLen) != $plusEncoded) {
|
||||
$value .= $plusEncoded;
|
||||
}
|
||||
|
||||
return $this->getUnboundedLabel($value, $lowerBound);
|
||||
} else {
|
||||
// if no lower bound can be found, this isn't a valid range. in this case
|
||||
// we assume its a translation key and try to translate it.
|
||||
return Piwik::translate(trim($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies and returns a range label whose range spans over one unit, ie
|
||||
* 1-1, 2-2 or 3-3.
|
||||
*
|
||||
* This function can be overridden in derived types to customize beautifcation
|
||||
* behavior based on the range values.
|
||||
*
|
||||
* @param string $oldLabel The original label value.
|
||||
* @param int $lowerBound The lower bound of the range.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function getSingleUnitLabel($oldLabel, $lowerBound)
|
||||
{
|
||||
if ($lowerBound == 1) {
|
||||
return $this->labelSingular;
|
||||
} else {
|
||||
return sprintf($this->labelPlural, $lowerBound);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies and returns a range label whose range is bounded and spans over
|
||||
* more than one unit, ie 1-5, 5-10 but NOT 11+.
|
||||
*
|
||||
* This function can be overridden in derived types to customize beautifcation
|
||||
* behavior based on the range values.
|
||||
*
|
||||
* @param string $oldLabel The original label value.
|
||||
* @param int $lowerBound The lower bound of the range.
|
||||
* @param int $upperBound The upper bound of the range.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function getRangeLabel($oldLabel, $lowerBound, $upperBound)
|
||||
{
|
||||
return sprintf($this->labelPlural, $oldLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies and returns a range label whose range is unbounded, ie
|
||||
* 5+, 10+, etc.
|
||||
*
|
||||
* This function can be overridden in derived types to customize beautifcation
|
||||
* behavior based on the range values.
|
||||
*
|
||||
* @param string $oldLabel The original label value.
|
||||
* @param int $lowerBound The lower bound of the range.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function getUnboundedLabel($oldLabel, $lowerBound)
|
||||
{
|
||||
return sprintf($this->labelPlural, $oldLabel);
|
||||
}
|
||||
}
|
||||
121
www/analytics/core/DataTable/Filter/BeautifyTimeRangeLabels.php
Normal file
121
www/analytics/core/DataTable/Filter/BeautifyTimeRangeLabels.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
|
||||
/**
|
||||
* A {@link DataTable} filter that replaces range labels whose values are in seconds with
|
||||
* prettier, human-friendlier versions.
|
||||
*
|
||||
* This filter customizes the behavior of the {@link BeautifyRangeLabels} filter
|
||||
* so range values that are less than one minute are displayed in seconds but
|
||||
* other ranges are displayed in minutes.
|
||||
*
|
||||
* **Basic usage**
|
||||
*
|
||||
* $dataTable->filter('BeautifyTimeRangeLabels', array("%1$s-%2$s min", "1 min", "%s min"));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class BeautifyTimeRangeLabels extends BeautifyRangeLabels
|
||||
{
|
||||
/**
|
||||
* A format string used to create pretty range labels when the range's
|
||||
* lower bound is between 0 and 60.
|
||||
*
|
||||
* This format string must take two numeric parameters, one for each
|
||||
* range bound.
|
||||
*/
|
||||
protected $labelSecondsPlural;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable this filter will run over.
|
||||
* @param string $labelSecondsPlural A string to use when beautifying range labels
|
||||
* whose lower bound is between 0 and 60. Must be
|
||||
* a format string that takes two numeric params.
|
||||
* @param string $labelMinutesSingular A string to use when replacing a range that
|
||||
* equals 60-60 (or 1 minute - 1 minute).
|
||||
* @param string $labelMinutesPlural A string to use when replacing a range that
|
||||
* spans multiple minutes. This must be a
|
||||
* format string that takes one string parameter.
|
||||
*/
|
||||
public function __construct($table, $labelSecondsPlural, $labelMinutesSingular, $labelMinutesPlural)
|
||||
{
|
||||
parent::__construct($table, $labelMinutesSingular, $labelMinutesPlural);
|
||||
|
||||
$this->labelSecondsPlural = $labelSecondsPlural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies and returns a range label whose range spans over one unit, ie
|
||||
* 1-1, 2-2 or 3-3.
|
||||
*
|
||||
* If the lower bound of the range is less than 60 the pretty range label
|
||||
* will be in seconds. Otherwise, it will be in minutes.
|
||||
*
|
||||
* @param string $oldLabel The original label value.
|
||||
* @param int $lowerBound The lower bound of the range.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function getSingleUnitLabel($oldLabel, $lowerBound)
|
||||
{
|
||||
if ($lowerBound < 60) {
|
||||
return sprintf($this->labelSecondsPlural, $lowerBound, $lowerBound);
|
||||
} else if ($lowerBound == 60) {
|
||||
return $this->labelSingular;
|
||||
} else {
|
||||
return sprintf($this->labelPlural, ceil($lowerBound / 60));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies and returns a range label whose range is bounded and spans over
|
||||
* more than one unit, ie 1-5, 5-10 but NOT 11+.
|
||||
*
|
||||
* If the lower bound of the range is less than 60 the pretty range label
|
||||
* will be in seconds. Otherwise, it will be in minutes.
|
||||
*
|
||||
* @param string $oldLabel The original label value.
|
||||
* @param int $lowerBound The lower bound of the range.
|
||||
* @param int $upperBound The upper bound of the range.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function getRangeLabel($oldLabel, $lowerBound, $upperBound)
|
||||
{
|
||||
if ($lowerBound < 60) {
|
||||
return sprintf($this->labelSecondsPlural, $lowerBound, $upperBound);
|
||||
} else {
|
||||
return sprintf($this->labelPlural, ceil($lowerBound / 60) . "-" . ceil($upperBound / 60));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautifies and returns a range label whose range is unbounded, ie
|
||||
* 5+, 10+, etc.
|
||||
*
|
||||
* If the lower bound of the range is less than 60 the pretty range label
|
||||
* will be in seconds. Otherwise, it will be in minutes.
|
||||
*
|
||||
* @param string $oldLabel The original label value.
|
||||
* @param int $lowerBound The lower bound of the range.
|
||||
* @return string The pretty range label.
|
||||
*/
|
||||
public function getUnboundedLabel($oldLabel, $lowerBound)
|
||||
{
|
||||
if ($lowerBound < 60) {
|
||||
return sprintf($this->labelSecondsPlural, $lowerBound);
|
||||
} else {
|
||||
// since we're using minutes, we use floor so 1801s+ will be 30m+ and not 31m+
|
||||
return sprintf($this->labelPlural, "" . floor($lowerBound / 60) . urlencode('+'));
|
||||
}
|
||||
}
|
||||
}
|
||||
187
www/analytics/core/DataTable/Filter/CalculateEvolutionFilter.php
Executable file
187
www/analytics/core/DataTable/Filter/CalculateEvolutionFilter.php
Executable file
|
|
@ -0,0 +1,187 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\Site;
|
||||
|
||||
/**
|
||||
* A {@link DataTable} filter that calculates the evolution of a metric and adds
|
||||
* it to each row as a percentage.
|
||||
*
|
||||
* **This filter cannot be used as an argument to {@link Piwik\DataTable::filter()}** since
|
||||
* it requires corresponding data from another DataTable. Instead,
|
||||
* you must manually perform a binary filter (see the **MultiSites** API for an
|
||||
* example).
|
||||
*
|
||||
* The evolution metric is calculated as:
|
||||
*
|
||||
* ((currentValue - pastValue) / pastValue) * 100
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage
|
||||
{
|
||||
/**
|
||||
* The the DataTable that contains past data.
|
||||
*
|
||||
* @var DataTable
|
||||
*/
|
||||
protected $pastDataTable;
|
||||
|
||||
/**
|
||||
* Tells if column being added is the revenue evolution column.
|
||||
*/
|
||||
protected $isRevenueEvolution = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable being filtered.
|
||||
* @param DataTable $pastDataTable The DataTable containing data for the period in the past.
|
||||
* @param string $columnToAdd The column to add evolution data to, eg, `'visits_evolution'`.
|
||||
* @param string $columnToRead The column to use to calculate evolution data, eg, `'nb_visits'`.
|
||||
* @param int $quotientPrecision The precision to use when rounding the evolution value.
|
||||
*/
|
||||
public function __construct($table, $pastDataTable, $columnToAdd, $columnToRead, $quotientPrecision = 0)
|
||||
{
|
||||
parent::__construct(
|
||||
$table, $columnToAdd, $columnToRead, $columnToRead, $quotientPrecision, $shouldSkipRows = true);
|
||||
|
||||
$this->pastDataTable = $pastDataTable;
|
||||
|
||||
$this->isRevenueEvolution = $columnToAdd == 'revenue_evolution';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference between the column in the specific row and its
|
||||
* sister column in the past DataTable.
|
||||
*
|
||||
* @param Row $row
|
||||
* @return int|float
|
||||
*/
|
||||
protected function getDividend($row)
|
||||
{
|
||||
$currentValue = $row->getColumn($this->columnValueToRead);
|
||||
|
||||
// if the site this is for doesn't support ecommerce & this is for the revenue_evolution column,
|
||||
// we don't add the new column
|
||||
if ($currentValue === false
|
||||
&& $this->isRevenueEvolution
|
||||
&& !Site::isEcommerceEnabledFor($row->getColumn('label'))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pastRow = $this->getPastRowFromCurrent($row);
|
||||
if ($pastRow) {
|
||||
$pastValue = $pastRow->getColumn($this->columnValueToRead);
|
||||
} else {
|
||||
$pastValue = 0;
|
||||
}
|
||||
|
||||
return $currentValue - $pastValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the column in $row's sister row in the past
|
||||
* DataTable.
|
||||
*
|
||||
* @param Row $row
|
||||
* @return int|float
|
||||
*/
|
||||
protected function getDivisor($row)
|
||||
{
|
||||
$pastRow = $this->getPastRowFromCurrent($row);
|
||||
if (!$pastRow) return 0;
|
||||
|
||||
return $pastRow->getColumn($this->columnNameUsedAsDivisor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and formats a quotient based on a divisor and dividend.
|
||||
*
|
||||
* Unlike ColumnCallbackAddColumnPercentage's,
|
||||
* version of this method, this method will return 100% if the past
|
||||
* value of a metric is 0, and the current value is not 0. For a
|
||||
* value representative of an evolution, this makes sense.
|
||||
*
|
||||
* @param int|float $value The dividend.
|
||||
* @param int|float $divisor
|
||||
* @return string
|
||||
*/
|
||||
protected function formatValue($value, $divisor)
|
||||
{
|
||||
$value = self::getPercentageValue($value, $divisor, $this->quotientPrecision);
|
||||
$value = self::appendPercentSign($value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function. Returns the current row in the past DataTable.
|
||||
*
|
||||
* @param Row $row The row in the 'current' DataTable.
|
||||
* @return bool|Row
|
||||
*/
|
||||
protected function getPastRowFromCurrent($row)
|
||||
{
|
||||
return $this->pastDataTable->getRowFromLabel($row->getColumn('label'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the evolution percentage for two arbitrary values.
|
||||
*
|
||||
* @param float|int $currentValue The current metric value.
|
||||
* @param float|int $pastValue The value of the metric in the past. We measure the % change
|
||||
* from this value to $currentValue.
|
||||
* @param float|int $quotientPrecision The quotient precision to round to.
|
||||
* @param bool $appendPercentSign Whether to append a '%' sign to the end of the number or not.
|
||||
*
|
||||
* @return string The evolution percent, eg `'15%'`.
|
||||
*/
|
||||
public static function calculate($currentValue, $pastValue, $quotientPrecision = 0, $appendPercentSign = true)
|
||||
{
|
||||
$number = self::getPercentageValue($currentValue - $pastValue, $pastValue, $quotientPrecision);
|
||||
if ($appendPercentSign) {
|
||||
$number = self::appendPercentSign($number);
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
|
||||
public static function appendPercentSign($number)
|
||||
{
|
||||
return $number . '%';
|
||||
}
|
||||
|
||||
public static function prependPlusSignToNumber($number)
|
||||
{
|
||||
if ($number > 0) {
|
||||
$number = '+' . $number;
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an evolution percent based on a value & divisor.
|
||||
*/
|
||||
private static function getPercentageValue($value, $divisor, $quotientPrecision)
|
||||
{
|
||||
if ($value == 0) {
|
||||
$evolution = 0;
|
||||
} elseif ($divisor == 0) {
|
||||
$evolution = 100;
|
||||
} else {
|
||||
$evolution = ($value / $divisor) * 100;
|
||||
}
|
||||
|
||||
$evolution = round($evolution, $quotientPrecision);
|
||||
return $evolution;
|
||||
}
|
||||
}
|
||||
96
www/analytics/core/DataTable/Filter/ColumnCallbackAddColumn.php
Executable file
96
www/analytics/core/DataTable/Filter/ColumnCallbackAddColumn.php
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Adds a new column to every row of a {@link DataTable} based on the result of callback.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $callback = function ($visits, $timeSpent) {
|
||||
* return round($timeSpent / $visits, 2);
|
||||
* };
|
||||
*
|
||||
* $dataTable->filter('ColumnCallbackAddColumn', array(array('nb_visits', 'sum_time_spent'), 'avg_time_on_site', $callback));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackAddColumn extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* The names of the columns to pass to the callback.
|
||||
*/
|
||||
private $columns;
|
||||
|
||||
/**
|
||||
* The name of the column to add.
|
||||
*/
|
||||
private $columnToAdd;
|
||||
|
||||
/**
|
||||
* The callback to apply to each row of the DataTable. The result is added as
|
||||
* the value of a new column.
|
||||
*/
|
||||
private $functionToApply;
|
||||
|
||||
/**
|
||||
* Extra parameters to pass to the callback.
|
||||
*/
|
||||
private $functionParameters;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will be filtered.
|
||||
* @param array|string $columns The names of the columns to pass to the callback.
|
||||
* @param string $columnToAdd The name of the column to add.
|
||||
* @param callable $functionToApply The callback to apply to each row of a DataTable. The columns
|
||||
* specified in `$columns` are passed to this callback.
|
||||
* @param array $functionParameters deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php)
|
||||
* instead.
|
||||
*/
|
||||
public function __construct($table, $columns, $columnToAdd, $functionToApply, $functionParameters = array())
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
if (!is_array($columns)) {
|
||||
$columns = array($columns);
|
||||
}
|
||||
|
||||
$this->columns = $columns;
|
||||
$this->columnToAdd = $columnToAdd;
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->functionParameters = $functionParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ColumnCallbackAddColumn}.
|
||||
*
|
||||
* @param DataTable $table The table to filter.
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $row) {
|
||||
$columnValues = array();
|
||||
foreach ($this->columns as $column) {
|
||||
$columnValues[] = $row->getColumn($column);
|
||||
}
|
||||
|
||||
$parameters = array_merge($columnValues, $this->functionParameters);
|
||||
$value = call_user_func_array($this->functionToApply, $parameters);
|
||||
|
||||
$row->setColumn($this->columnToAdd, $value);
|
||||
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\Piwik;
|
||||
|
||||
/**
|
||||
* Calculates a percentage value for each row of a {@link DataTable} and adds the result
|
||||
* to each row.
|
||||
*
|
||||
* See {@link ColumnCallbackAddColumnQuotient} for more information.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $nbVisits = // ... get the visits for a period ...
|
||||
* $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('nb_visits', 'nb_visits_percentage', $nbVisits, 1));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackAddColumnPercentage extends ColumnCallbackAddColumnQuotient
|
||||
{
|
||||
/**
|
||||
* Formats the given value as a percentage.
|
||||
*
|
||||
* @param number $value
|
||||
* @param number $divisor
|
||||
* @return string
|
||||
*/
|
||||
protected function formatValue($value, $divisor)
|
||||
{
|
||||
return Piwik::getPercentageSafe($value, $divisor, $this->quotientPrecision) . '%';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* Calculates the quotient of two columns and adds the result as a new column
|
||||
* for each row of a DataTable.
|
||||
*
|
||||
* This filter is used to calculate rate values (eg, `'bounce_rate'`), averages
|
||||
* (eg, `'avg_time_on_page'`) and other types of values.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('bounce_rate', 'bounce_count', 'nb_visits', $precision = 2));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackAddColumnQuotient extends BaseFilter
|
||||
{
|
||||
protected $table;
|
||||
protected $columnValueToRead;
|
||||
protected $columnNameToAdd;
|
||||
protected $columnNameUsedAsDivisor;
|
||||
protected $totalValueUsedAsDivisor;
|
||||
protected $quotientPrecision;
|
||||
protected $shouldSkipRows;
|
||||
protected $getDivisorFromSummaryRow;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will eventually be filtered.
|
||||
* @param string $columnNameToAdd The name of the column to add the quotient value to.
|
||||
* @param string $columnValueToRead The name of the column that holds the dividend.
|
||||
* @param number|string $divisorValueOrDivisorColumnName
|
||||
* Either numeric value to use as the divisor for every row,
|
||||
* or the name of the column whose value should be used as the
|
||||
* divisor.
|
||||
* @param int $quotientPrecision The precision to use when rounding the quotient.
|
||||
* @param bool|number $shouldSkipRows Whether rows w/o the column to read should be skipped or not.
|
||||
* @param bool $getDivisorFromSummaryRow Whether to get the divisor from the summary row or the current
|
||||
* row iteration.
|
||||
*/
|
||||
public function __construct($table, $columnNameToAdd, $columnValueToRead, $divisorValueOrDivisorColumnName,
|
||||
$quotientPrecision = 0, $shouldSkipRows = false, $getDivisorFromSummaryRow = false)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->table = $table;
|
||||
$this->columnValueToRead = $columnValueToRead;
|
||||
$this->columnNameToAdd = $columnNameToAdd;
|
||||
if (is_numeric($divisorValueOrDivisorColumnName)) {
|
||||
$this->totalValueUsedAsDivisor = $divisorValueOrDivisorColumnName;
|
||||
} else {
|
||||
$this->columnNameUsedAsDivisor = $divisorValueOrDivisorColumnName;
|
||||
}
|
||||
$this->quotientPrecision = $quotientPrecision;
|
||||
$this->shouldSkipRows = $shouldSkipRows;
|
||||
$this->getDivisorFromSummaryRow = $getDivisorFromSummaryRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ColumnCallbackAddColumnQuotient}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$value = $this->getDividend($row);
|
||||
if ($value === false && $this->shouldSkipRows) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete existing column if it exists
|
||||
$existingValue = $row->getColumn($this->columnNameToAdd);
|
||||
if ($existingValue !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$divisor = $this->getDivisor($row);
|
||||
|
||||
$formattedValue = $this->formatValue($value, $divisor);
|
||||
$row->addColumn($this->columnNameToAdd, $formattedValue);
|
||||
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given value
|
||||
*
|
||||
* @param number $value
|
||||
* @param number $divisor
|
||||
* @return float|int
|
||||
*/
|
||||
protected function formatValue($value, $divisor)
|
||||
{
|
||||
$quotient = 0;
|
||||
if ($divisor > 0 && $value > 0) {
|
||||
$quotient = round($value / $divisor, $this->quotientPrecision);
|
||||
}
|
||||
return $quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dividend to use when calculating the new column value. Can
|
||||
* be overridden by descendent classes to customize behavior.
|
||||
*
|
||||
* @param Row $row The row being modified.
|
||||
* @return int|float
|
||||
*/
|
||||
protected function getDividend($row)
|
||||
{
|
||||
return $row->getColumn($this->columnValueToRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the divisor to use when calculating the new column value. Can
|
||||
* be overridden by descendent classes to customize behavior.
|
||||
*
|
||||
* @param Row $row The row being modified.
|
||||
* @return int|float
|
||||
*/
|
||||
protected function getDivisor($row)
|
||||
{
|
||||
if (!is_null($this->totalValueUsedAsDivisor)) {
|
||||
return $this->totalValueUsedAsDivisor;
|
||||
} else if ($this->getDivisorFromSummaryRow) {
|
||||
$summaryRow = $this->table->getRowFromId(DataTable::ID_SUMMARY_ROW);
|
||||
return $summaryRow->getColumn($this->columnNameUsedAsDivisor);
|
||||
} else {
|
||||
return $row->getColumn($this->columnNameUsedAsDivisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Executes a callback for each row of a {@link DataTable} and adds the result as a new
|
||||
* row metadata value.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik\Plugins\MyPlugin\getLogoFromLabel'));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackAddMetadata extends BaseFilter
|
||||
{
|
||||
private $columnsToRead;
|
||||
private $functionToApply;
|
||||
private $functionParameters;
|
||||
private $metadataToAdd;
|
||||
private $applyToSummaryRow;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable instance that will be filtered.
|
||||
* @param string|array $columnsToRead The columns to read from each row and pass on to the callback.
|
||||
* @param string $metadataToAdd The name of the metadata field that will be added to each row.
|
||||
* @param callable $functionToApply The callback to apply for each row.
|
||||
* @param array $functionParameters deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php)
|
||||
* instead.
|
||||
* @param bool $applyToSummaryRow Whether the callback should be applied to the summary row or not.
|
||||
*/
|
||||
public function __construct($table, $columnsToRead, $metadataToAdd, $functionToApply = null,
|
||||
$functionParameters = null, $applyToSummaryRow = true)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
if (!is_array($columnsToRead)) {
|
||||
$columnsToRead = array($columnsToRead);
|
||||
}
|
||||
$this->columnsToRead = $columnsToRead;
|
||||
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->functionParameters = $functionParameters;
|
||||
$this->metadataToAdd = $metadataToAdd;
|
||||
$this->applyToSummaryRow = $applyToSummaryRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ColumnCallbackAddMetadata}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
if (!$this->applyToSummaryRow && $key == DataTable::ID_SUMMARY_ROW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters = array();
|
||||
foreach ($this->columnsToRead as $columnsToRead) {
|
||||
$parameters[] = $row->getColumn($columnsToRead);
|
||||
}
|
||||
|
||||
if (!is_null($this->functionParameters)) {
|
||||
$parameters = array_merge($parameters, $this->functionParameters);
|
||||
}
|
||||
if (!is_null($this->functionToApply)) {
|
||||
$newValue = call_user_func_array($this->functionToApply, $parameters);
|
||||
} else {
|
||||
$newValue = $parameters[0];
|
||||
}
|
||||
if ($newValue !== false) {
|
||||
$row->addMetadata($this->metadataToAdd, $newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Deletes all rows for which a callback returns true.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $labelsToRemove = array('label1', 'label2', 'label2');
|
||||
* $dataTable->filter('ColumnCallbackDeleteRow', array('label', function ($label) use ($labelsToRemove) {
|
||||
* return in_array($label, $labelsToRemove);
|
||||
* }));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackDeleteRow extends BaseFilter
|
||||
{
|
||||
private $columnToFilter;
|
||||
private $function;
|
||||
private $functionParams;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will be filtered eventually.
|
||||
* @param array|string $columnsToFilter The column or array of columns that should be
|
||||
* passed to the callback.
|
||||
* @param callback $function The callback that determines whether a row should be deleted
|
||||
* or not. Should return `true` if the row should be deleted.
|
||||
* @param array $functionParams deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php)
|
||||
* instead.
|
||||
*/
|
||||
public function __construct($table, $columnsToFilter, $function, $functionParams = array())
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
if (!is_array($functionParams)) {
|
||||
$functionParams = array($functionParams);
|
||||
}
|
||||
|
||||
if (!is_array($columnsToFilter)) {
|
||||
$columnsToFilter = array($columnsToFilter);
|
||||
}
|
||||
|
||||
$this->function = $function;
|
||||
$this->columnsToFilter = $columnsToFilter;
|
||||
$this->functionParams = $functionParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given data table
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$params = array();
|
||||
foreach ($this->columnsToFilter as $column) {
|
||||
$params[] = $row->getColumn($column);
|
||||
}
|
||||
|
||||
$params = array_merge($params, $this->functionParams);
|
||||
if (call_user_func_array($this->function, $params) === true) {
|
||||
$table->deleteRow($key);
|
||||
}
|
||||
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
www/analytics/core/DataTable/Filter/ColumnCallbackReplace.php
Normal file
122
www/analytics/core/DataTable/Filter/ColumnCallbackReplace.php
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* Replaces one or more column values in each row of a DataTable with the results
|
||||
* of a callback.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $truncateString = function ($value, $truncateLength) {
|
||||
* if (strlen($value) > $truncateLength) {
|
||||
* return substr(0, $truncateLength);
|
||||
* } else {
|
||||
* return $value;
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // label, url and truncate_length are columns in $dataTable
|
||||
* $dataTable->filter('ColumnCallbackReplace', array('label', 'url'), $truncateString, null, array('truncate_length'));
|
||||
*
|
||||
*/
|
||||
class ColumnCallbackReplace extends BaseFilter
|
||||
{
|
||||
private $columnsToFilter;
|
||||
private $functionToApply;
|
||||
private $functionParameters;
|
||||
private $extraColumnParameters;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable to filter.
|
||||
* @param array|string $columnsToFilter The columns whose values should be passed to the callback
|
||||
* and then replaced with the callback's result.
|
||||
* @param callable $functionToApply The function to execute. Must take the column value as a parameter
|
||||
* and return a value that will be used to replace the original.
|
||||
* @param array|null $functionParameters deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php)
|
||||
* instead.
|
||||
* @param array $extraColumnParameters Extra column values that should be passed to the callback, but
|
||||
* shouldn't be replaced.
|
||||
*/
|
||||
public function __construct($table, $columnsToFilter, $functionToApply, $functionParameters = null,
|
||||
$extraColumnParameters = array())
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->functionParameters = $functionParameters;
|
||||
|
||||
if (!is_array($columnsToFilter)) {
|
||||
$columnsToFilter = array($columnsToFilter);
|
||||
}
|
||||
|
||||
$this->columnsToFilter = $columnsToFilter;
|
||||
$this->extraColumnParameters = $extraColumnParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ColumnCallbackReplace}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$extraColumnParameters = array();
|
||||
foreach ($this->extraColumnParameters as $columnName) {
|
||||
$extraColumnParameters[] = $row->getColumn($columnName);
|
||||
}
|
||||
|
||||
foreach ($this->columnsToFilter as $column) {
|
||||
// when a value is not defined, we set it to zero by default (rather than displaying '-')
|
||||
$value = $this->getElementToReplace($row, $column);
|
||||
if ($value === false) {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
$parameters = array_merge(array($value), $extraColumnParameters);
|
||||
if (!is_null($this->functionParameters)) {
|
||||
$parameters = array_merge($parameters, $this->functionParameters);
|
||||
}
|
||||
$newValue = call_user_func_array($this->functionToApply, $parameters);
|
||||
$this->setElementToReplace($row, $column, $newValue);
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the given column within given row with the given value
|
||||
*
|
||||
* @param Row $row
|
||||
* @param string $columnToFilter
|
||||
* @param mixed $newValue
|
||||
*/
|
||||
protected function setElementToReplace($row, $columnToFilter, $newValue)
|
||||
{
|
||||
$row->setColumn($columnToFilter, $newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element that should be replaced
|
||||
*
|
||||
* @param Row $row
|
||||
* @param string $columnToFilter
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getElementToReplace($row, $columnToFilter)
|
||||
{
|
||||
return $row->getColumn($columnToFilter);
|
||||
}
|
||||
}
|
||||
150
www/analytics/core/DataTable/Filter/ColumnDelete.php
Normal file
150
www/analytics/core/DataTable/Filter/ColumnDelete.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Filter that will remove columns from a {@link DataTable} using either a blacklist,
|
||||
* whitelist or both.
|
||||
*
|
||||
* This filter is used to handle the **hideColumn** and **showColumn** query parameters.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $columnsToRemove = array('nb_hits', 'nb_pageviews');
|
||||
* $dataTable->filter('ColumnDelete', array($columnsToRemove));
|
||||
*
|
||||
* $columnsToKeep = array('nb_visits');
|
||||
* $dataTable->filter('ColumnDelete', array(array(), $columnsToKeep));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnDelete extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* The columns that should be removed from DataTable rows.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $columnsToRemove;
|
||||
|
||||
/**
|
||||
* The columns that should be kept in DataTable rows. All other columns will be
|
||||
* removed. If a column is in $columnsToRemove and this variable, it will NOT be kept.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $columnsToKeep;
|
||||
|
||||
/**
|
||||
* Hack: when specifying "showColumns", sometimes we'd like to also keep columns that "look" like a given column,
|
||||
* without manually specifying all these columns (which may not be possible if column names are generated dynamically)
|
||||
*
|
||||
* Column will be kept, if they match any name in the $columnsToKeep, or if they look like anyColumnToKeep__anythingHere
|
||||
*/
|
||||
const APPEND_TO_COLUMN_NAME_TO_KEEP = '__';
|
||||
|
||||
/**
|
||||
* Delete the column, only if the value was zero
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $deleteIfZeroOnly;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable instance that will eventually be filtered.
|
||||
* @param array|string $columnsToRemove An array of column names or a comma-separated list of
|
||||
* column names. These columns will be removed.
|
||||
* @param array|string $columnsToKeep An array of column names that should be kept or a
|
||||
* comma-separated list of column names. Columns not in
|
||||
* this list will be removed.
|
||||
* @param bool $deleteIfZeroOnly If true, columns will be removed only if their value is 0.
|
||||
*/
|
||||
public function __construct($table, $columnsToRemove, $columnsToKeep = array(), $deleteIfZeroOnly = false)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
if (is_string($columnsToRemove)) {
|
||||
$columnsToRemove = $columnsToRemove == '' ? array() : explode(',', $columnsToRemove);
|
||||
}
|
||||
|
||||
if (is_string($columnsToKeep)) {
|
||||
$columnsToKeep = $columnsToKeep == '' ? array() : explode(',', $columnsToKeep);
|
||||
}
|
||||
|
||||
$this->columnsToRemove = $columnsToRemove;
|
||||
$this->columnsToKeep = array_flip($columnsToKeep); // flip so we can use isset instead of in_array
|
||||
$this->deleteIfZeroOnly = $deleteIfZeroOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ColumnDelete}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
// always do recursive filter
|
||||
$this->enableRecursive(true);
|
||||
$recurse = false; // only recurse if there are columns to remove/keep
|
||||
|
||||
// remove columns specified in $this->columnsToRemove
|
||||
if (!empty($this->columnsToRemove)) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
foreach ($this->columnsToRemove as $column) {
|
||||
if ($this->deleteIfZeroOnly) {
|
||||
$value = $row->getColumn($column);
|
||||
if ($value === false || !empty($value)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$row->deleteColumn($column);
|
||||
}
|
||||
}
|
||||
|
||||
$recurse = true;
|
||||
}
|
||||
|
||||
// remove columns not specified in $columnsToKeep
|
||||
if (!empty($this->columnsToKeep)) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
foreach ($row->getColumns() as $name => $value) {
|
||||
|
||||
$keep = false;
|
||||
// @see self::APPEND_TO_COLUMN_NAME_TO_KEEP
|
||||
foreach ($this->columnsToKeep as $nameKeep => $true) {
|
||||
if (strpos($name, $nameKeep . self::APPEND_TO_COLUMN_NAME_TO_KEEP) === 0) {
|
||||
$keep = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$keep
|
||||
&& $name != 'label' // label cannot be removed via whitelisting
|
||||
&& !isset($this->columnsToKeep[$name])
|
||||
) {
|
||||
$row->deleteColumn($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$recurse = true;
|
||||
}
|
||||
|
||||
// recurse
|
||||
if ($recurse) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
www/analytics/core/DataTable/Filter/ExcludeLowPopulation.php
Normal file
90
www/analytics/core/DataTable/Filter/ExcludeLowPopulation.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Deletes all rows for which a specific column has a value that is lower than
|
||||
* specified minimum threshold value.
|
||||
*
|
||||
* **Basic usage examples**
|
||||
*
|
||||
* // remove all countries from UserCountry.getCountry that have less than 3 visits
|
||||
* $dataTable = // ... get a DataTable whose queued filters have been run ...
|
||||
* $dataTable->filter('ExcludeLowPopulation', array('nb_visits', 3));
|
||||
*
|
||||
* // remove all countries from UserCountry.getCountry whose percent of total visits is less than 5%
|
||||
* $dataTable = // ... get a DataTable whose queued filters have been run ...
|
||||
* $dataTable->filter('ExcludeLowPopulation', array('nb_visits', false, 0.05));
|
||||
*
|
||||
* // remove all countries from UserCountry.getCountry whose bounce rate is less than 10%
|
||||
* $dataTable = // ... get a DataTable that has a numerical bounce_rate column ...
|
||||
* $dataTable->filter('ExcludeLowPopulation', array('bounce_rate', 0.10));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ExcludeLowPopulation extends BaseFilter
|
||||
{
|
||||
const MINIMUM_SIGNIFICANT_PERCENTAGE_THRESHOLD = 0.02;
|
||||
|
||||
/**
|
||||
* The minimum value to enforce in a datatable for a specified column. Rows found with
|
||||
* a value less than this are removed.
|
||||
*
|
||||
* @var number
|
||||
*/
|
||||
private $minimumValue;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will be filtered eventually.
|
||||
* @param string $columnToFilter The name of the column whose value will determine whether
|
||||
* a row is deleted or not.
|
||||
* @param number|false $minimumValue The minimum column value. Rows with column values <
|
||||
* this number will be deleted. If false,
|
||||
* `$minimumPercentageThreshold` is used.
|
||||
* @param bool|float $minimumPercentageThreshold If supplied, column values must be a greater
|
||||
* percentage of the sum of all column values than
|
||||
* this precentage.
|
||||
*/
|
||||
public function __construct($table, $columnToFilter, $minimumValue, $minimumPercentageThreshold = false)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->columnToFilter = $columnToFilter;
|
||||
|
||||
if ($minimumValue == 0) {
|
||||
if ($minimumPercentageThreshold === false) {
|
||||
$minimumPercentageThreshold = self::MINIMUM_SIGNIFICANT_PERCENTAGE_THRESHOLD;
|
||||
}
|
||||
$allValues = $table->getColumn($this->columnToFilter);
|
||||
$sumValues = array_sum($allValues);
|
||||
$minimumValue = $sumValues * $minimumPercentageThreshold;
|
||||
}
|
||||
|
||||
$this->minimumValue = $minimumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ExcludeLowPopulation}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$minimumValue = $this->minimumValue;
|
||||
$isValueLowPopulation = function ($value) use ($minimumValue) {
|
||||
return $value < $minimumValue;
|
||||
};
|
||||
|
||||
$table->filter('ColumnCallbackDeleteRow', array($this->columnToFilter, $isValueLowPopulation));
|
||||
}
|
||||
}
|
||||
104
www/analytics/core/DataTable/Filter/GroupBy.php
Executable file
104
www/analytics/core/DataTable/Filter/GroupBy.php
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* DataTable filter that will group {@link DataTable} rows together based on the results
|
||||
* of a reduce function. Rows with the same reduce result will be summed and merged.
|
||||
*
|
||||
* _NOTE: This filter should never be queued, it must be applied directly on a {@link DataTable}._
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* // group URLs by host
|
||||
* $dataTable->filter('GroupBy', array('label', function ($labelUrl) {
|
||||
* return parse_url($labelUrl, PHP_URL_HOST);
|
||||
* }));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class GroupBy extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* The name of the columns to reduce.
|
||||
* @var string
|
||||
*/
|
||||
private $groupByColumn;
|
||||
|
||||
/**
|
||||
* A callback that modifies the $groupByColumn of each row in some way. Rows with
|
||||
* the same reduction result will be added together.
|
||||
*/
|
||||
private $reduceFunction;
|
||||
|
||||
/**
|
||||
* Extra parameters to pass to the reduce function.
|
||||
*/
|
||||
private $parameters;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable to filter.
|
||||
* @param string $groupByColumn The column name to reduce.
|
||||
* @param callable $reduceFunction The reduce function. This must alter the `$groupByColumn`
|
||||
* columng in some way.
|
||||
* @param array $parameters deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php)
|
||||
* instead.
|
||||
*/
|
||||
public function __construct($table, $groupByColumn, $reduceFunction, $parameters = array())
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
$this->groupByColumn = $groupByColumn;
|
||||
$this->reduceFunction = $reduceFunction;
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link GroupBy}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$groupByRows = array();
|
||||
$nonGroupByRowIds = array();
|
||||
|
||||
foreach ($table->getRows() as $rowId => $row) {
|
||||
// skip the summary row
|
||||
if ($rowId == DataTable::ID_SUMMARY_ROW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// reduce the group by column of this row
|
||||
$groupByColumnValue = $row->getColumn($this->groupByColumn);
|
||||
$parameters = array_merge(array($groupByColumnValue), $this->parameters);
|
||||
$groupByValue = call_user_func_array($this->reduceFunction, $parameters);
|
||||
|
||||
if (!isset($groupByRows[$groupByValue])) {
|
||||
// if we haven't encountered this group by value before, we mark this row as a
|
||||
// row to keep, and change the group by column to the reduced value.
|
||||
$groupByRows[$groupByValue] = $row;
|
||||
$row->setColumn($this->groupByColumn, $groupByValue);
|
||||
} else {
|
||||
// if we have already encountered this group by value, we add this row to the
|
||||
// row that will be kept, and mark this one for deletion
|
||||
$groupByRows[$groupByValue]->sumRow($row, $copyMeta = true, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME));
|
||||
$nonGroupByRowIds[] = $rowId;
|
||||
}
|
||||
}
|
||||
|
||||
// delete the unneeded rows.
|
||||
$table->deleteRows($nonGroupByRowIds);
|
||||
}
|
||||
}
|
||||
69
www/analytics/core/DataTable/Filter/Limit.php
Normal file
69
www/analytics/core/DataTable/Filter/Limit.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Delete all rows from the table that are not in the given [offset, offset+limit) range.
|
||||
*
|
||||
* **Basic example usage**
|
||||
*
|
||||
* // delete all rows from 5 -> 15
|
||||
* $dataTable->filter('Limit', array(5, 10));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Limit extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will be filtered eventually.
|
||||
* @param int $offset The starting row index to keep.
|
||||
* @param int $limit Number of rows to keep (specify -1 to keep all rows).
|
||||
* @param bool $keepSummaryRow Whether to keep the summary row or not.
|
||||
*/
|
||||
public function __construct($table, $offset, $limit = -1, $keepSummaryRow = false)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->offset = $offset;
|
||||
|
||||
$this->limit = $limit;
|
||||
$this->keepSummaryRow = $keepSummaryRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Limit}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$table->setMetadata(DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME, $table->getRowsCount());
|
||||
|
||||
if ($this->keepSummaryRow) {
|
||||
$summaryRow = $table->getRowFromId(DataTable::ID_SUMMARY_ROW);
|
||||
}
|
||||
|
||||
// we delete from 0 to offset
|
||||
if ($this->offset > 0) {
|
||||
$table->deleteRowsOffset(0, $this->offset);
|
||||
}
|
||||
// at this point the array has offset less elements. We delete from limit to the end
|
||||
if ($this->limit >= 0) {
|
||||
$table->deleteRowsOffset($this->limit);
|
||||
}
|
||||
|
||||
if ($this->keepSummaryRow && !empty($summaryRow)) {
|
||||
$table->addSummaryRow($summaryRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?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\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Executes a callback for each row of a {@link DataTable} and adds the result to the
|
||||
* row as a metadata value. Only metadata values are passed to the callback.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* // add a logo metadata based on the url metadata
|
||||
* $dataTable->filter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik\Plugins\MyPlugin\getLogoFromUrl'));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class MetadataCallbackAddMetadata extends BaseFilter
|
||||
{
|
||||
private $metadataToRead;
|
||||
private $functionToApply;
|
||||
private $metadataToAdd;
|
||||
private $applyToSummaryRow;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable that will eventually be filtered.
|
||||
* @param string|array $metadataToRead The metadata to read from each row and pass to the callback.
|
||||
* @param string $metadataToAdd The name of the metadata to add.
|
||||
* @param callable $functionToApply The callback to execute for each row. The result will be
|
||||
* added as metadata with the name `$metadataToAdd`.
|
||||
* @param bool $applyToSummaryRow True if the callback should be applied to the summary row, false
|
||||
* if otherwise.
|
||||
*/
|
||||
public function __construct($table, $metadataToRead, $metadataToAdd, $functionToApply,
|
||||
$applyToSummaryRow = true)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->functionToApply = $functionToApply;
|
||||
|
||||
if (!is_array($metadataToRead)) {
|
||||
$metadataToRead = array($metadataToRead);
|
||||
}
|
||||
|
||||
$this->metadataToRead = $metadataToRead;
|
||||
$this->metadataToAdd = $metadataToAdd;
|
||||
$this->applyToSummaryRow = $applyToSummaryRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link MetadataCallbackAddMetadata}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
if (!$this->applyToSummaryRow && $key == DataTable::ID_SUMMARY_ROW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
foreach ($this->metadataToRead as $name) {
|
||||
$params[] = $row->getMetadata($name);
|
||||
}
|
||||
|
||||
$newValue = call_user_func_array($this->functionToApply, $params);
|
||||
if ($newValue !== false) {
|
||||
$row->addMetadata($this->metadataToAdd, $newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue