add piwik installation

This commit is contained in:
coderkun 2014-04-25 03:56:02 +02:00
commit 8c5d4f0c31
44 changed files with 563902 additions and 0 deletions

View 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);
}
}
}

View 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
{
}
}

View 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();
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}
}

View 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);
}
}

View 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);
}
}

View 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('+'));
}
}
}

View 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;
}
}

View 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);
}
}
}

View file

@ -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) . '%';
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View 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\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);
}
}
}

View 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);
}
}

View 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);
}
}
}
}

View 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));
}
}

View 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);
}
}

View 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);
}
}
}

View 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\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