update Piwik to version 2.16 (fixes #91)
This commit is contained in:
parent
296343bf3b
commit
d885a4baa9
5833 changed files with 418860 additions and 226988 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,12 +9,11 @@
|
|||
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
|
||||
*
|
||||
* - add/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
|
||||
|
|
@ -22,10 +21,10 @@ use Piwik\DataTable\Row;
|
|||
*
|
||||
* 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
|
||||
|
|
@ -37,7 +36,7 @@ abstract class BaseFilter
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function __construct(DataTable $table)
|
||||
|
|
@ -73,8 +72,8 @@ abstract class BaseFilter
|
|||
if (!$this->enableRecursive) {
|
||||
return;
|
||||
}
|
||||
if ($row->isSubtableLoaded()) {
|
||||
$subTable = Manager::getInstance()->getTable($row->getIdSubDataTable());
|
||||
$subTable = $row->getSubtable();
|
||||
if ($subTable) {
|
||||
$this->filter($subTable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
*/
|
||||
namespace {
|
||||
|
||||
use Piwik\DataTable\Row\DataTableSummaryRow;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable\Row\DataTableSummaryRow;
|
||||
|
||||
class Piwik_DataTable_Row_DataTableSummary extends DataTableSummaryRow
|
||||
{
|
||||
|
|
@ -24,5 +24,11 @@ namespace {
|
|||
{
|
||||
}
|
||||
|
||||
}
|
||||
// only used for BC to unserialize old archived Row instances. We cannot unserialize Row directly as it implements
|
||||
// the Serializable interface and it would fail on PHP5.6+ when userializing the Row instance directly.
|
||||
class Piwik_DataTable_SerializedRow
|
||||
{
|
||||
public $c;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,30 +9,33 @@
|
|||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Plugin\Metric;
|
||||
use Piwik\Plugins\CoreHome\Columns\Metrics\ActionsPerVisit;
|
||||
use Piwik\Plugins\CoreHome\Columns\Metrics\AverageTimeOnSite;
|
||||
use Piwik\Plugins\CoreHome\Columns\Metrics\BounceRate;
|
||||
use Piwik\Plugins\CoreHome\Columns\Metrics\ConversionRate;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -43,7 +46,7 @@ class AddColumnsProcessedMetrics extends BaseFilter
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table to eventually filter.
|
||||
* @param bool $deleteRowsWithNoVisit Whether to delete rows with no visits or not.
|
||||
*/
|
||||
|
|
@ -61,89 +64,32 @@ class AddColumnsProcessedMetrics extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$rowsIdToDelete = array();
|
||||
if ($this->deleteRowsWithNoVisit) {
|
||||
$this->deleteRowsWithNoVisit($table);
|
||||
}
|
||||
|
||||
$extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME);
|
||||
|
||||
$extraProcessedMetrics[] = new ConversionRate();
|
||||
$extraProcessedMetrics[] = new ActionsPerVisit();
|
||||
$extraProcessedMetrics[] = new AverageTimeOnSite();
|
||||
$extraProcessedMetrics[] = new BounceRate();
|
||||
|
||||
$table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics);
|
||||
}
|
||||
|
||||
private function deleteRowsWithNoVisit(DataTable $table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$nbVisits = $this->getColumn($row, Metrics::INDEX_NB_VISITS);
|
||||
$nbActions = $this->getColumn($row, Metrics::INDEX_NB_ACTIONS);
|
||||
$nbVisits = Metric::getMetric($row, 'nb_visits');
|
||||
$nbActions = Metric::getMetric($row, '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;
|
||||
// case of keyword/website/campaign with a conversion for this day, but no visit, we don't show it
|
||||
$table->deleteRow($key);
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,16 +8,23 @@
|
|||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Archive\DataTableFactory;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
use Piwik\Plugin\Metric;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecific\AverageOrderRevenue;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecific\ConversionRate;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecific\Conversions;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecific\ItemsCount;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecific\Revenue;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecific\RevenuePerVisit as GoalSpecificRevenuePerVisit;
|
||||
use Piwik\Plugins\Goals\Columns\Metrics\RevenuePerVisit;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -36,17 +43,17 @@ use Piwik\Tracker\GoalManager;
|
|||
* 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
|
||||
|
|
@ -68,7 +75,7 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics
|
|||
|
||||
/**
|
||||
* 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).
|
||||
|
|
@ -76,13 +83,14 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics
|
|||
* 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)
|
||||
public function __construct($table, $enable = true, $processOnlyIdGoal, $goalsToProcess = null)
|
||||
{
|
||||
$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;
|
||||
$this->goalsToProcess = $goalsToProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -95,132 +103,63 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics
|
|||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
$goals = $this->getGoalsInTable($table);
|
||||
if (!empty($this->goalsToProcess)) {
|
||||
$goals = array_unique(array_merge($goals, $this->goalsToProcess));
|
||||
sort($goals);
|
||||
}
|
||||
|
||||
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);
|
||||
$idSite = DataTableFactory::getSiteIdFromMetadata($table);
|
||||
|
||||
$extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME);
|
||||
|
||||
$extraProcessedMetrics[] = new RevenuePerVisit();
|
||||
if ($this->processOnlyIdGoal != self::GOALS_MINIMAL_REPORT) {
|
||||
foreach ($goals as $idGoal) {
|
||||
if (($this->processOnlyIdGoal > self::GOALS_FULL_TABLE
|
||||
|| $this->isEcommerce)
|
||||
&& $this->processOnlyIdGoal != $idGoal
|
||||
) {
|
||||
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;
|
||||
$extraProcessedMetrics[] = new ConversionRate($idSite, $idGoal); // PerGoal\ConversionRate
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// conversion_rate can be defined upstream apparently? FIXME
|
||||
try {
|
||||
$row->addColumns($newColumns);
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
$expectedColumns['revenue_per_visit'] = true;
|
||||
$extraProcessedMetrics[] = new Conversions($idSite, $idGoal); // PerGoal\Conversions or GoalSpecific\
|
||||
$extraProcessedMetrics[] = new GoalSpecificRevenuePerVisit($idSite, $idGoal); // PerGoal\Revenue
|
||||
$extraProcessedMetrics[] = new Revenue($idSite, $idGoal); // PerGoal\Revenue
|
||||
|
||||
// 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);
|
||||
if ($this->isEcommerce) {
|
||||
$extraProcessedMetrics[] = new AverageOrderRevenue($idSite, $idGoal);
|
||||
$extraProcessedMetrics[] = new ItemsCount($idSite, $idGoal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics);
|
||||
}
|
||||
|
||||
private function getGoalsInTable(DataTable $table)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($table->getRows() as $row) {
|
||||
$goals = Metric::getMetric($row, 'goals');
|
||||
if (!$goals) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($goals as $goalId => $goalMetrics) {
|
||||
$goalId = str_replace("idgoal=", "", $goalId);
|
||||
$result[] = $goalId;
|
||||
}
|
||||
}
|
||||
return array_unique($result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
99
www/analytics/core/DataTable/Filter/AddSegmentByLabel.php
Normal file
99
www/analytics/core/DataTable/Filter/AddSegmentByLabel.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\Development;
|
||||
|
||||
/**
|
||||
* Executes a filter for each row of a {@link DataTable} and generates a segment filter for each row.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddSegmentByLabel', array('segmentName'));
|
||||
* $dataTable->filter('AddSegmentByLabel', array(array('segmentName1', 'segment2'), ';');
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddSegmentByLabel extends BaseFilter
|
||||
{
|
||||
private $segments;
|
||||
private $delimiter;
|
||||
|
||||
/**
|
||||
* Generates a segment filter based on the label column and the given segment names
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @param string|array $segmentOrSegments Either one segment or an array of segments.
|
||||
* If more than one segment is given a delimter has to be defined.
|
||||
* @param string $delimiter The delimiter by which the label should be splitted.
|
||||
*/
|
||||
public function __construct($table, $segmentOrSegments, $delimiter = '')
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
if (!is_array($segmentOrSegments)) {
|
||||
$segmentOrSegments = array($segmentOrSegments);
|
||||
}
|
||||
|
||||
$this->segments = $segmentOrSegments;
|
||||
$this->delimiter = $delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AddSegmentByLabel}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
if (empty($this->segments)) {
|
||||
$msg = 'AddSegmentByLabel is called without having any segments defined';
|
||||
Development::error($msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($this->segments) === 1) {
|
||||
$segment = reset($this->segments);
|
||||
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $key => $row) {
|
||||
$label = $row->getColumn('label');
|
||||
|
||||
if (!empty($label)) {
|
||||
$row->setMetadata('segment', $segment . '==' . urlencode($label));
|
||||
}
|
||||
}
|
||||
} elseif (!empty($this->delimiter)) {
|
||||
$numSegments = count($this->segments);
|
||||
$conditionAnd = ';';
|
||||
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $key => $row) {
|
||||
$label = $row->getColumn('label');
|
||||
if (!empty($label)) {
|
||||
$parts = explode($this->delimiter, $label);
|
||||
|
||||
if (count($parts) === $numSegments) {
|
||||
$filter = array();
|
||||
foreach ($this->segments as $index => $segment) {
|
||||
if (!empty($segment)) {
|
||||
$filter[] = $segment . '==' . urlencode($parts[$index]);
|
||||
}
|
||||
}
|
||||
$row->setMetadata('segment', implode($conditionAnd, $filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$names = implode(', ', $this->segments);
|
||||
$msg = 'Multiple segments are given but no delimiter defined. Segments: ' . $names;
|
||||
Development::error($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Executes a filter for each row of a {@link DataTable} and generates a segment filter for each row.
|
||||
* It will map the label column to a segmentValue by searching for the label in the index of the given
|
||||
* mapping array.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddSegmentByLabelMapping', array('segmentName', array('1' => 'smartphone, '2' => 'desktop')));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddSegmentByLabelMapping extends BaseFilter
|
||||
{
|
||||
private $segment;
|
||||
private $mapping;
|
||||
|
||||
/**
|
||||
* @param DataTable $table
|
||||
* @param string $segment
|
||||
* @param array $mapping
|
||||
*/
|
||||
public function __construct($table, $segment, $mapping)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
$this->segment = $segment;
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AddSegmentByLabelMapping}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
if (empty($this->segment) || empty($this->mapping)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($table->getRows() as $row) {
|
||||
$label = $row->getColumn('label');
|
||||
|
||||
if (!empty($this->mapping[$label])) {
|
||||
$label = $this->mapping[$label];
|
||||
$row->setMetadata('segment', $this->segment . '==' . urlencode($label));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable;
|
||||
|
||||
/**
|
||||
* Converts for each row of a {@link DataTable} a segmentValue to a segment (expression). The name of the segment
|
||||
* is automatically detected based on the given report.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddSegmentBySegmentValue', array($reportInstance));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddSegmentBySegmentValue extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* @var \Piwik\Plugin\Report
|
||||
*/
|
||||
private $report;
|
||||
|
||||
/**
|
||||
* @param DataTable $table
|
||||
* @param $report
|
||||
*/
|
||||
public function __construct($table, $report)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->report = $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AddSegmentBySegmentValue}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @return int The number of deleted rows.
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
if (empty($this->report) || !$table->getRowsCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dimension = $this->report->getDimension();
|
||||
|
||||
if (empty($dimension)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$segments = $dimension->getSegments();
|
||||
|
||||
if (empty($segments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Piwik\Plugin\Segment $segment */
|
||||
$segment = reset($segments);
|
||||
$segmentName = $segment->getSegment();
|
||||
|
||||
foreach ($table->getRows() as $row) {
|
||||
$value = $row->getMetadata('segmentValue');
|
||||
$filter = $row->getMetadata('segment');
|
||||
|
||||
if ($value !== false && $filter === false) {
|
||||
$row->setMetadata('segment', sprintf('%s==%s', $segmentName, urlencode($value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
www/analytics/core/DataTable/Filter/AddSegmentValue.php
Normal file
32
www/analytics/core/DataTable/Filter/AddSegmentValue.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
|
||||
/**
|
||||
* Executes a filter for each row of a {@link DataTable} and generates a segment filter for each row.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('AddSegmentValue', array());
|
||||
* $dataTable->filter('AddSegmentValue', array(function ($label) {
|
||||
* $transformedValue = urldecode($transformedValue);
|
||||
* return $transformedValue;
|
||||
* });
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class AddSegmentValue extends ColumnCallbackAddMetadata
|
||||
{
|
||||
public function __construct($table, $callback = null)
|
||||
{
|
||||
parent::__construct($table, 'label', 'segmentValue', $callback, null, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -16,12 +16,12 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -28,11 +28,11 @@ use Piwik\Piwik;
|
|||
* 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
|
||||
|
|
@ -65,7 +65,7 @@ class BeautifyRangeLabels extends ColumnCallbackReplace
|
|||
parent::__construct($table, 'label', array($this, 'beautify'), array());
|
||||
|
||||
$this->labelSingular = $labelSingular;
|
||||
$this->labelPlural = $labelPlural;
|
||||
$this->labelPlural = $labelPlural;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -17,11 +17,11 @@ use Piwik\DataTable;
|
|||
* 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
|
||||
|
|
@ -70,7 +70,7 @@ class BeautifyTimeRangeLabels extends BeautifyRangeLabels
|
|||
{
|
||||
if ($lowerBound < 60) {
|
||||
return sprintf($this->labelSecondsPlural, $lowerBound, $lowerBound);
|
||||
} else if ($lowerBound == 60) {
|
||||
} elseif ($lowerBound == 60) {
|
||||
return $this->labelSingular;
|
||||
} else {
|
||||
return sprintf($this->labelPlural, ceil($lowerBound / 60));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,8 +8,10 @@
|
|||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\NumberFormatter;
|
||||
use Piwik\Site;
|
||||
|
||||
/**
|
||||
|
|
@ -17,15 +19,16 @@ use Piwik\Site;
|
|||
* 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,
|
||||
* 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
|
||||
* @deprecated since v2.10.0
|
||||
*/
|
||||
class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage
|
||||
{
|
||||
|
|
@ -100,7 +103,9 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage
|
|||
protected function getDivisor($row)
|
||||
{
|
||||
$pastRow = $this->getPastRowFromCurrent($row);
|
||||
if (!$pastRow) return 0;
|
||||
if (!$pastRow) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $pastRow->getColumn($this->columnNameUsedAsDivisor);
|
||||
}
|
||||
|
|
@ -121,6 +126,9 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage
|
|||
{
|
||||
$value = self::getPercentageValue($value, $divisor, $this->quotientPrecision);
|
||||
$value = self::appendPercentSign($value);
|
||||
|
||||
$value = Common::forceDotAsSeparatorForDecimalPoint($value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
|
@ -150,9 +158,10 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage
|
|||
{
|
||||
$number = self::getPercentageValue($currentValue - $pastValue, $pastValue, $quotientPrecision);
|
||||
if ($appendPercentSign) {
|
||||
$number = self::appendPercentSign($number);
|
||||
return NumberFormatter::getInstance()->formatPercent($number, $quotientPrecision);
|
||||
}
|
||||
return $number;
|
||||
|
||||
return NumberFormatter::getInstance()->format($number, $quotientPrecision);
|
||||
}
|
||||
|
||||
public static function appendPercentSign($number)
|
||||
|
|
@ -165,6 +174,7 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage
|
|||
if ($number > 0) {
|
||||
$number = '+' . $number;
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,16 +10,17 @@ namespace Piwik\DataTable\Filter;
|
|||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\Plugins\CoreHome\Columns\Metrics\CallableProcessedMetric;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -79,17 +80,33 @@ class ColumnCallbackAddColumn extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $row) {
|
||||
$columns = $this->columns;
|
||||
$functionParams = $this->functionParameters;
|
||||
$functionToApply = $this->functionToApply;
|
||||
|
||||
$extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME);
|
||||
|
||||
if (empty($extraProcessedMetrics)) {
|
||||
$extraProcessedMetrics = array();
|
||||
}
|
||||
|
||||
$metric = new CallableProcessedMetric($this->columnToAdd, function (DataTable\Row $row) use ($columns, $functionParams, $functionToApply) {
|
||||
|
||||
$columnValues = array();
|
||||
foreach ($this->columns as $column) {
|
||||
foreach ($columns as $column) {
|
||||
$columnValues[] = $row->getColumn($column);
|
||||
}
|
||||
|
||||
$parameters = array_merge($columnValues, $this->functionParameters);
|
||||
$value = call_user_func_array($this->functionToApply, $parameters);
|
||||
$parameters = array_merge($columnValues, $functionParams);
|
||||
|
||||
$row->setColumn($this->columnToAdd, $value);
|
||||
return call_user_func_array($functionToApply, $parameters);
|
||||
}, $columns);
|
||||
$extraProcessedMetrics[] = $metric;
|
||||
|
||||
$table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics);
|
||||
|
||||
foreach ($table->getRows() as $row) {
|
||||
$row->setColumn($this->columnToAdd, $metric->compute($row));
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,11 +13,11 @@ 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));
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,14 +15,14 @@ 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
|
||||
|
|
@ -38,7 +38,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -75,7 +75,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
$value = $this->getDividend($row);
|
||||
if ($value === false && $this->shouldSkipRows) {
|
||||
continue;
|
||||
|
|
@ -109,6 +109,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter
|
|||
if ($divisor > 0 && $value > 0) {
|
||||
$quotient = round($value / $divisor, $this->quotientPrecision);
|
||||
}
|
||||
|
||||
return $quotient;
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +136,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter
|
|||
{
|
||||
if (!is_null($this->totalValueUsedAsDivisor)) {
|
||||
return $this->totalValueUsedAsDivisor;
|
||||
} else if ($this->getDivisorFromSummaryRow) {
|
||||
} elseif ($this->getDivisorFromSummaryRow) {
|
||||
$summaryRow = $this->table->getRowFromId(DataTable::ID_SUMMARY_ROW);
|
||||
return $summaryRow->getColumn($this->columnNameUsedAsDivisor);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -14,11 +14,11 @@ 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
|
||||
|
|
@ -31,7 +31,7 @@ class ColumnCallbackAddMetadata extends BaseFilter
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -48,12 +48,12 @@ class ColumnCallbackAddMetadata extends BaseFilter
|
|||
if (!is_array($columnsToRead)) {
|
||||
$columnsToRead = array($columnsToRead);
|
||||
}
|
||||
$this->columnsToRead = $columnsToRead;
|
||||
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->columnsToRead = $columnsToRead;
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->functionParameters = $functionParameters;
|
||||
$this->metadataToAdd = $metadataToAdd;
|
||||
$this->applyToSummaryRow = $applyToSummaryRow;
|
||||
$this->metadataToAdd = $metadataToAdd;
|
||||
$this->applyToSummaryRow = $applyToSummaryRow;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,11 +63,13 @@ class ColumnCallbackAddMetadata extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
if (!$this->applyToSummaryRow && $key == DataTable::ID_SUMMARY_ROW) {
|
||||
continue;
|
||||
}
|
||||
if ($this->applyToSummaryRow) {
|
||||
$rows = $table->getRows();
|
||||
} else {
|
||||
$rows = $table->getRowsWithoutSummaryRow();
|
||||
}
|
||||
|
||||
foreach ($rows as $key => $row) {
|
||||
$parameters = array();
|
||||
foreach ($this->columnsToRead as $columnsToRead) {
|
||||
$parameters[] = $row->getColumn($columnsToRead);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Executes a callback for each row of a {@link DataTable} and removes the defined metadata column from each row.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('ColumnCallbackDeleteMetadata', array('segmentValue'));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackDeleteMetadata extends BaseFilter
|
||||
{
|
||||
private $metadataToRemove;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The DataTable instance that will be filtered.
|
||||
* @param string $metadataToRemove The name of the metadata field that will be removed from each row.
|
||||
*/
|
||||
public function __construct($table, $metadataToRemove)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
$this->metadataToRemove = $metadataToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link ColumnCallbackDeleteMetadata}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$this->enableRecursive(true);
|
||||
|
||||
foreach ($table->getRows() as $row) {
|
||||
$row->deleteMetadata($this->metadataToRemove);
|
||||
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,23 +15,22 @@ 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.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,9 +15,9 @@ 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);
|
||||
|
|
@ -25,10 +25,11 @@ use Piwik\DataTable\Row;
|
|||
* return $value;
|
||||
* }
|
||||
* };
|
||||
*
|
||||
*
|
||||
* // label, url and truncate_length are columns in $dataTable
|
||||
* $dataTable->filter('ColumnCallbackReplace', array('label', 'url'), $truncateString, null, array('truncate_length'));
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ColumnCallbackReplace extends BaseFilter
|
||||
{
|
||||
|
|
@ -39,7 +40,7 @@ class ColumnCallbackReplace extends BaseFilter
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -54,14 +55,14 @@ class ColumnCallbackReplace extends BaseFilter
|
|||
$extraColumnParameters = array())
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->functionToApply = $functionToApply;
|
||||
$this->functionParameters = $functionParameters;
|
||||
|
||||
if (!is_array($columnsToFilter)) {
|
||||
$columnsToFilter = array($columnsToFilter);
|
||||
}
|
||||
|
||||
$this->columnsToFilter = $columnsToFilter;
|
||||
$this->columnsToFilter = $columnsToFilter;
|
||||
$this->extraColumnParameters = $extraColumnParameters;
|
||||
}
|
||||
|
||||
|
|
@ -72,13 +73,14 @@ class ColumnCallbackReplace extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
foreach ($table->getRows() as $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) {
|
||||
|
|
@ -86,14 +88,21 @@ class ColumnCallbackReplace extends BaseFilter
|
|||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array('label', $this->columnsToFilter)) {
|
||||
// we need to force rebuilding the index
|
||||
$table->setLabelsHaveChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -16,15 +16,15 @@ use Piwik\DataTable\BaseFilter;
|
|||
* 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
|
||||
|
|
@ -91,6 +91,7 @@ class ColumnDelete extends BaseFilter
|
|||
* See {@link ColumnDelete}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @return DataTable
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
|
|
@ -100,15 +101,16 @@ class ColumnDelete extends BaseFilter
|
|||
|
||||
// remove columns specified in $this->columnsToRemove
|
||||
if (!empty($this->columnsToRemove)) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
foreach ($table as $index => $row) {
|
||||
foreach ($this->columnsToRemove as $column) {
|
||||
if ($this->deleteIfZeroOnly) {
|
||||
$value = $row->getColumn($column);
|
||||
$value = $row[$column];
|
||||
if ($value === false || !empty($value)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$row->deleteColumn($column);
|
||||
|
||||
unset($table[$index][$column]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,9 +119,8 @@ class ColumnDelete extends BaseFilter
|
|||
|
||||
// remove columns not specified in $columnsToKeep
|
||||
if (!empty($this->columnsToKeep)) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
foreach ($row->getColumns() as $name => $value) {
|
||||
|
||||
foreach ($table as $index => $row) {
|
||||
foreach ($row as $name => $value) {
|
||||
$keep = false;
|
||||
// @see self::APPEND_TO_COLUMN_NAME_TO_KEEP
|
||||
foreach ($this->columnsToKeep as $nameKeep => $true) {
|
||||
|
|
@ -132,7 +133,7 @@ class ColumnDelete extends BaseFilter
|
|||
&& $name != 'label' // label cannot be removed via whitelisting
|
||||
&& !isset($this->columnsToKeep[$name])
|
||||
) {
|
||||
$row->deleteColumn($name);
|
||||
unset($table[$index][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -141,10 +142,12 @@ class ColumnDelete extends BaseFilter
|
|||
}
|
||||
|
||||
// recurse
|
||||
if ($recurse) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
if ($recurse && !is_array($table)) {
|
||||
foreach ($table as $row) {
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,21 +10,22 @@ namespace Piwik\DataTable\Filter;
|
|||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\Metrics;
|
||||
|
||||
/**
|
||||
* 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));
|
||||
|
|
@ -50,7 +51,7 @@ class ExcludeLowPopulation extends BaseFilter
|
|||
* @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,
|
||||
* 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
|
||||
|
|
@ -59,7 +60,13 @@ class ExcludeLowPopulation extends BaseFilter
|
|||
public function __construct($table, $columnToFilter, $minimumValue, $minimumPercentageThreshold = false)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->columnToFilter = $columnToFilter;
|
||||
|
||||
$row = $table->getFirstRow();
|
||||
if ($row === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->columnToFilter = $this->selectColumnToExclude($columnToFilter, $row);
|
||||
|
||||
if ($minimumValue == 0) {
|
||||
if ($minimumPercentageThreshold === false) {
|
||||
|
|
@ -80,6 +87,9 @@ class ExcludeLowPopulation extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
if(empty($this->columnToFilter)) {
|
||||
return;
|
||||
}
|
||||
$minimumValue = $this->minimumValue;
|
||||
$isValueLowPopulation = function ($value) use ($minimumValue) {
|
||||
return $value < $minimumValue;
|
||||
|
|
@ -87,4 +97,29 @@ class ExcludeLowPopulation extends BaseFilter
|
|||
|
||||
$table->filter('ColumnCallbackDeleteRow', array($this->columnToFilter, $isValueLowPopulation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column to be used for Excluding low population
|
||||
*
|
||||
* @param DataTable\Row $row
|
||||
* @return int
|
||||
*/
|
||||
private function selectColumnToExclude($columnToFilter, $row)
|
||||
{
|
||||
if ($row->hasColumn($columnToFilter)) {
|
||||
return $columnToFilter;
|
||||
}
|
||||
|
||||
// filter_excludelowpop=nb_visits but the column name is still Metrics::INDEX_NB_VISITS in the table
|
||||
$columnIdToName = Metrics::getMappingFromNameToId();
|
||||
if (isset($columnIdToName[$columnToFilter])) {
|
||||
$column = $columnIdToName[$columnToFilter];
|
||||
|
||||
if ($row->hasColumn($column)) {
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
|
||||
return $columnToFilter;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,6 +10,7 @@ namespace Piwik\DataTable\Filter;
|
|||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* DataTable filter that will group {@link DataTable} rows together based on the results
|
||||
|
|
@ -18,12 +19,12 @@ use Piwik\DataTable\BaseFilter;
|
|||
* _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
|
||||
|
|
@ -51,17 +52,17 @@ class GroupBy extends BaseFilter
|
|||
* @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.
|
||||
* columng in some way. If not set then the filter will group by the raw column value.
|
||||
* @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())
|
||||
public function __construct($table, $groupByColumn, $reduceFunction = null, $parameters = array())
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
$this->groupByColumn = $groupByColumn;
|
||||
$this->groupByColumn = $groupByColumn;
|
||||
$this->reduceFunction = $reduceFunction;
|
||||
$this->parameters = $parameters;
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,19 +72,19 @@ class GroupBy extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
/** @var Row[] $groupByRows */
|
||||
$groupByRows = array();
|
||||
$nonGroupByRowIds = array();
|
||||
|
||||
foreach ($table->getRows() as $rowId => $row) {
|
||||
// skip the summary row
|
||||
if ($rowId == DataTable::ID_SUMMARY_ROW) {
|
||||
continue;
|
||||
}
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $rowId => $row) {
|
||||
$groupByColumnValue = $row->getColumn($this->groupByColumn);
|
||||
$groupByValue = $groupByColumnValue;
|
||||
|
||||
// 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 ($this->reduceFunction) {
|
||||
$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
|
||||
|
|
@ -98,6 +99,10 @@ class GroupBy extends BaseFilter
|
|||
}
|
||||
}
|
||||
|
||||
if ($this->groupByColumn === 'label') {
|
||||
$table->setLabelsHaveChanged();
|
||||
}
|
||||
|
||||
// delete the unneeded rows.
|
||||
$table->deleteRows($nonGroupByRowIds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,9 +13,9 @@ 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));
|
||||
*
|
||||
|
|
@ -34,9 +34,9 @@ class Limit extends BaseFilter
|
|||
public function __construct($table, $offset, $limit = -1, $keepSummaryRow = false)
|
||||
{
|
||||
parent::__construct($table);
|
||||
$this->offset = $offset;
|
||||
|
||||
$this->limit = $limit;
|
||||
$this->offset = $offset;
|
||||
$this->limit = $limit;
|
||||
$this->keepSummaryRow = $keepSummaryRow;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -16,7 +16,7 @@ use Piwik\DataTable\BaseFilter;
|
|||
* 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'));
|
||||
*
|
||||
|
|
@ -31,7 +31,7 @@ class MetadataCallbackAddMetadata extends BaseFilter
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -57,16 +57,18 @@ class MetadataCallbackAddMetadata extends BaseFilter
|
|||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
if ($this->applyToSummaryRow) {
|
||||
$rows = $table->getRows();
|
||||
} else {
|
||||
$rows = $table->getRowsWithoutSummaryRow();
|
||||
}
|
||||
|
||||
foreach ($rows as $key => $row) {
|
||||
$params = array();
|
||||
foreach ($this->metadataToRead as $name) {
|
||||
$params[] = $row->getMetadata($name);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -14,9 +14,9 @@ use Piwik\DataTable\Row;
|
|||
/**
|
||||
* Execute a callback for each row of a {@link DataTable} passing certain column values and metadata
|
||||
* as metadata, and replaces row metadata with the callback result.
|
||||
*
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
*
|
||||
* $dataTable->filter('MetadataCallbackReplace', array('url', function ($url) {
|
||||
* return $url . '#index';
|
||||
* }));
|
||||
|
|
@ -27,7 +27,7 @@ class MetadataCallbackReplace extends ColumnCallbackReplace
|
|||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The DataTable that will eventually be filtered.
|
||||
* @param array|string $metadataToFilter The metadata whose values should be passed to the callback
|
||||
* and then replaced with the callback's result.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,9 +13,9 @@ use Piwik\DataTable\BaseFilter;
|
|||
|
||||
/**
|
||||
* Deletes every row for which a specific column does not match a supplied regex pattern.
|
||||
*
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
*
|
||||
* // filter out all rows whose labels doesn't start with piwik
|
||||
* $dataTable->filter('Pattern', array('label', '^piwik'));
|
||||
*
|
||||
|
|
@ -23,6 +23,9 @@ use Piwik\DataTable\BaseFilter;
|
|||
*/
|
||||
class Pattern extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* @var string|array
|
||||
*/
|
||||
private $columnToFilter;
|
||||
private $patternToSearch;
|
||||
private $patternToSearchQuoted;
|
||||
|
|
@ -30,7 +33,7 @@ class Pattern extends BaseFilter
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table to eventually filter.
|
||||
* @param string $columnToFilter The column to match with the `$patternToSearch` pattern.
|
||||
* @param string $patternToSearch The regex pattern to use.
|
||||
|
|
@ -53,7 +56,7 @@ class Pattern extends BaseFilter
|
|||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
static public function getPatternQuoted($pattern)
|
||||
public static function getPatternQuoted($pattern)
|
||||
{
|
||||
return '/' . str_replace('/', '\/', $pattern) . '/';
|
||||
}
|
||||
|
|
@ -67,14 +70,14 @@ class Pattern extends BaseFilter
|
|||
* @return int
|
||||
* @ignore
|
||||
*/
|
||||
static public function match($patternQuoted, $string, $invertedMatch = false)
|
||||
public static function match($patternQuoted, $string, $invertedMatch = false)
|
||||
{
|
||||
return preg_match($patternQuoted . "i", $string) == 1 ^ $invertedMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Pattern}.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
|
|
@ -93,4 +96,30 @@ class Pattern extends BaseFilter
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Pattern}.
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
public function filterArray($array)
|
||||
{
|
||||
$newArray = array();
|
||||
|
||||
foreach ($array as $key => $row) {
|
||||
foreach ($this->columnToFilter as $column) {
|
||||
if (!array_key_exists($column, $row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::match($this->patternToSearchQuoted, $row[$column], $this->invertedMatch)) {
|
||||
$newArray[$key] = $row;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $newArray;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,17 +8,15 @@
|
|||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Exception;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Manager;
|
||||
|
||||
/**
|
||||
* Deletes rows that do not contain a column that matches a regex pattern and do not contain a
|
||||
* subtable that contains a column that matches a regex pattern.
|
||||
*
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
*
|
||||
* // only display index pageviews in Actions.getPageUrls
|
||||
* $dataTable->filter('PatternRecursive', array('label', 'index'));
|
||||
*
|
||||
|
|
@ -32,7 +30,7 @@ class PatternRecursive extends BaseFilter
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table to eventually filter.
|
||||
* @param string $columnToFilter The column to match with the `$patternToSearch` pattern.
|
||||
* @param string $patternToSearch The regex pattern to use.
|
||||
|
|
@ -48,7 +46,7 @@ class PatternRecursive extends BaseFilter
|
|||
|
||||
/**
|
||||
* See {@link PatternRecursive}.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @return int The number of deleted rows.
|
||||
*/
|
||||
|
|
@ -62,18 +60,15 @@ class PatternRecursive extends BaseFilter
|
|||
// AND 2 - the label is not found in the children
|
||||
$patternNotFoundInChildren = false;
|
||||
|
||||
try {
|
||||
$idSubTable = $row->getIdSubDataTable();
|
||||
$subTable = Manager::getInstance()->getTable($idSubTable);
|
||||
|
||||
$subTable = $row->getSubtable();
|
||||
if (!$subTable) {
|
||||
$patternNotFoundInChildren = true;
|
||||
} else {
|
||||
// we delete the row if we couldn't find the pattern in any row in the
|
||||
// children hierarchy
|
||||
if ($this->filter($subTable) == 0) {
|
||||
$patternNotFoundInChildren = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// there is no subtable loaded for example
|
||||
$patternNotFoundInChildren = true;
|
||||
}
|
||||
|
||||
if ($patternNotFoundInChildren
|
||||
|
|
|
|||
550
www/analytics/core/DataTable/Filter/PivotByDimension.php
Normal file
550
www/analytics/core/DataTable/Filter/PivotByDimension.php
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Columns\Dimension;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\Log;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Report;
|
||||
use Piwik\Plugin\Segment;
|
||||
use Piwik\Site;
|
||||
|
||||
/**
|
||||
* DataTable filter that creates a pivot table from a report.
|
||||
*
|
||||
* A pivot table is a table that displays one metric value for two dimensions. The rows of
|
||||
* the table represent one dimension and the columns another.
|
||||
*
|
||||
* This filter can pivot any report by any dimension as long as either:
|
||||
*
|
||||
* - the pivot-by dimension is the dimension of the report's subtable
|
||||
* - or, the pivot-by dimension has an associated report, and the report to pivot has a dimension with
|
||||
* a segment
|
||||
*
|
||||
* Reports are pivoted by iterating over the rows of the report, fetching the pivot-by report
|
||||
* for the current row, and setting the columns of row to the rows of the pivot-by report. For example:
|
||||
*
|
||||
* to pivot Referrers.getKeywords by UserCountry.City, we first loop through the Referrers.getKeywords
|
||||
* report's rows. For each row, we take the label (which is the referrer keyword), and get the
|
||||
* UserCountry.getCity report using the referrerKeyword=... segment. If the row's label were 'abcdefg',
|
||||
* we would use the 'referrerKeyword==abcdefg' segment.
|
||||
*
|
||||
* The UserCountry.getCity report we find is the report on visits by country, but only for the visits
|
||||
* for the specific row. We take this report's row labels and add them as columns for the Referrers.getKeywords
|
||||
* table.
|
||||
*
|
||||
* Implementation details:
|
||||
*
|
||||
* Fetching intersected table can be done by segment or subtable. If the requested pivot by
|
||||
* dimension is the report's subtable dimension, then the subtable is used regardless, since it
|
||||
* is much faster than fetching by segment.
|
||||
*
|
||||
* Also, by default, fetching by segment is disabled in the config (see the
|
||||
* '[General] pivot_by_filter_enable_fetch_by_segment' option).
|
||||
*/
|
||||
class PivotByDimension extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* The pivot-by Dimension. The metadata in this class is used to determine if we can
|
||||
* pivot the report and used to fetch intersected tables.
|
||||
*
|
||||
* @var Dimension
|
||||
*/
|
||||
private $pivotByDimension;
|
||||
|
||||
/**
|
||||
* The report that reports on visits by the pivot dimension. The metadata in this class
|
||||
* is used to determine if we can pivot the report and used to fetch intersected tables
|
||||
* by segment.
|
||||
*
|
||||
* @var Report
|
||||
*/
|
||||
private $pivotDimensionReport;
|
||||
|
||||
/**
|
||||
* The column that should be displayed in the pivot table. This should be a metric, eg,
|
||||
* `'nb_visits'`, `'nb_actions'`, etc.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $pivotColumn;
|
||||
|
||||
/**
|
||||
* The number of columns to limit the pivot table to. Applying a pivot can result in
|
||||
* tables with many, many columns. This can cause problems when displayed in web page.
|
||||
*
|
||||
* A default limit of 7 is imposed if no column limit is specified in construction.
|
||||
* If a negative value is supplied, no limiting is performed.
|
||||
*
|
||||
* Columns are summed and sorted before being limited so the columns w/ the most
|
||||
* visits will be displayed and the columns w/ the least will be cut off.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $pivotByColumnLimit;
|
||||
|
||||
/**
|
||||
* Metadata for the report being pivoted. The metadata in this class is used to
|
||||
* determine if we can pivot the report and used to fetch intersected tables.
|
||||
*
|
||||
* @var Report
|
||||
*/
|
||||
private $thisReport;
|
||||
|
||||
/**
|
||||
* Metadata for the segment of the dimension of the report being pivoted. When
|
||||
* fetching intersected tables by segment, this is the segment used.
|
||||
*
|
||||
* @var Segment
|
||||
*/
|
||||
private $thisReportDimensionSegment;
|
||||
|
||||
/**
|
||||
* Whether fetching by segment is enabled or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isFetchingBySegmentEnabled;
|
||||
|
||||
/**
|
||||
* The subtable dimension of the report being pivoted. Used to determine if and
|
||||
* how intersected tables are fetched.
|
||||
*
|
||||
* @var Dimension|null
|
||||
*/
|
||||
private $subtableDimension;
|
||||
|
||||
/**
|
||||
* The index value (if any) for the metric that should be displayed in the pivot
|
||||
* table.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $metricIndexValue;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DataTable $table The table to pivot.
|
||||
* @param string $report The ID of the report being pivoted, eg, `'Referrers.getKeywords'`.
|
||||
* @param string $pivotByDimension The ID of the dimension to pivot by, eg, `'Referrers.Keyword'`.
|
||||
* @param string|false $pivotColumn The metric that should be displayed in the pivot table, eg, `'nb_visits'`.
|
||||
* If `false`, the first non-label column is used.
|
||||
* @param false|int $pivotByColumnLimit The number of columns to limit the pivot table to.
|
||||
* @param bool $isFetchingBySegmentEnabled Whether to allow fetching by segment.
|
||||
* @throws Exception if pivoting the report by a dimension is unsupported.
|
||||
*/
|
||||
public function __construct($table, $report, $pivotByDimension, $pivotColumn, $pivotByColumnLimit = false,
|
||||
$isFetchingBySegmentEnabled = true)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
Log::debug("PivotByDimension::%s: creating with [report = %s, pivotByDimension = %s, pivotColumn = %s, "
|
||||
. "pivotByColumnLimit = %s, isFetchingBySegmentEnabled = %s]", __FUNCTION__, $report, $pivotByDimension,
|
||||
$pivotColumn, $pivotByColumnLimit, $isFetchingBySegmentEnabled);
|
||||
|
||||
$this->pivotColumn = $pivotColumn;
|
||||
$this->pivotByColumnLimit = $pivotByColumnLimit ?: self::getDefaultColumnLimit();
|
||||
$this->isFetchingBySegmentEnabled = $isFetchingBySegmentEnabled;
|
||||
|
||||
$namesToId = Metrics::getMappingFromNameToId();
|
||||
$this->metricIndexValue = isset($namesToId[$this->pivotColumn]) ? $namesToId[$this->pivotColumn] : null;
|
||||
|
||||
$this->setPivotByDimension($pivotByDimension);
|
||||
$this->setThisReportMetadata($report);
|
||||
|
||||
$this->checkSupportedPivot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pivots to table.
|
||||
*
|
||||
* @param DataTable $table The table to manipulate.
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
// set of all column names in the pivoted table mapped with the sum of all column
|
||||
// values. used later in truncating and ordering the pivoted table's columns.
|
||||
$columnSet = array();
|
||||
|
||||
// if no pivot column was set, use the first one found in the row
|
||||
if (empty($this->pivotColumn)) {
|
||||
$this->pivotColumn = $this->getNameOfFirstNonLabelColumnInTable($table);
|
||||
}
|
||||
|
||||
Log::debug("PivotByDimension::%s: pivoting table with pivot column = %s", __FUNCTION__, $this->pivotColumn);
|
||||
|
||||
foreach ($table->getRows() as $row) {
|
||||
$row->setColumns(array('label' => $row->getColumn('label')));
|
||||
|
||||
$associatedTable = $this->getIntersectedTable($table, $row);
|
||||
if (!empty($associatedTable)) {
|
||||
foreach ($associatedTable->getRows() as $columnRow) {
|
||||
$pivotTableColumn = $columnRow->getColumn('label');
|
||||
|
||||
$columnValue = $this->getColumnValue($columnRow, $this->pivotColumn);
|
||||
|
||||
if (isset($columnSet[$pivotTableColumn])) {
|
||||
$columnSet[$pivotTableColumn] += $columnValue;
|
||||
} else {
|
||||
$columnSet[$pivotTableColumn] = $columnValue;
|
||||
}
|
||||
|
||||
$row->setColumn($pivotTableColumn, $columnValue);
|
||||
}
|
||||
|
||||
Common::destroy($associatedTable);
|
||||
unset($associatedTable);
|
||||
}
|
||||
}
|
||||
|
||||
Log::debug("PivotByDimension::%s: pivoted columns set: %s", __FUNCTION__, $columnSet);
|
||||
|
||||
$others = Piwik::translate('General_Others');
|
||||
$defaultRow = $this->getPivotTableDefaultRowFromColumnSummary($columnSet, $others);
|
||||
|
||||
Log::debug("PivotByDimension::%s: un-prepended default row: %s", __FUNCTION__, $defaultRow);
|
||||
|
||||
// post process pivoted datatable
|
||||
foreach ($table->getRows() as $row) {
|
||||
// remove subtables from rows
|
||||
$row->removeSubtable();
|
||||
$row->deleteMetadata('idsubdatatable_in_db');
|
||||
|
||||
// use default row to ensure column ordering and add missing columns/aggregate cut-off columns
|
||||
$orderedColumns = $defaultRow;
|
||||
foreach ($row->getColumns() as $name => $value) {
|
||||
if (isset($orderedColumns[$name])) {
|
||||
$orderedColumns[$name] = $value;
|
||||
} else {
|
||||
$orderedColumns[$others] += $value;
|
||||
}
|
||||
}
|
||||
$row->setColumns($orderedColumns);
|
||||
}
|
||||
|
||||
$table->clearQueuedFilters(); // TODO: shouldn't clear queued filters, but we can't wait for them to be run
|
||||
// since generic filters are run before them. remove after refactoring
|
||||
// processed metrics.
|
||||
|
||||
// prepend numerals to columns in a queued filter (this way, disable_queued_filters can be used
|
||||
// to get machine readable data from the API if needed)
|
||||
$prependedColumnNames = $this->getOrderedColumnsWithPrependedNumerals($defaultRow, $others);
|
||||
|
||||
Log::debug("PivotByDimension::%s: prepended column name mapping: %s", __FUNCTION__, $prependedColumnNames);
|
||||
|
||||
$table->queueFilter(function (DataTable $table) use ($prependedColumnNames) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
$row->setColumns(array_combine($prependedColumnNames, $row->getColumns()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An intersected table is a table that describes visits by a certain dimension for the visits
|
||||
* represented by a row in another table. This method fetches intersected tables either via
|
||||
* subtable or by using a segment. Read the class docs for more info.
|
||||
*/
|
||||
private function getIntersectedTable(DataTable $table, Row $row)
|
||||
{
|
||||
if ($this->isPivotDimensionSubtable()) {
|
||||
return $this->loadSubtable($table, $row);
|
||||
}
|
||||
|
||||
if ($this->isFetchingBySegmentEnabled) {
|
||||
$segmentValue = $row->getColumn('label');
|
||||
return $this->fetchIntersectedWithThisBySegment($table, $segmentValue);
|
||||
}
|
||||
|
||||
// should never occur, unless checkSupportedPivot() fails to catch an unsupported pivot
|
||||
throw new Exception("Unexpected error, cannot fetch intersected table.");
|
||||
}
|
||||
|
||||
private function isPivotDimensionSubtable()
|
||||
{
|
||||
return self::areDimensionsEqualAndNotNull($this->subtableDimension, $this->pivotByDimension);
|
||||
}
|
||||
|
||||
private function loadSubtable(DataTable $table, Row $row)
|
||||
{
|
||||
$idSubtable = $row->getIdSubDataTable();
|
||||
if ($idSubtable === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$subtable = $row->getSubtable();
|
||||
if (!$subtable) {
|
||||
$subtable = $this->thisReport->fetchSubtable($idSubtable, $this->getRequestParamOverride($table));
|
||||
}
|
||||
|
||||
if (!$subtable) { // sanity check
|
||||
throw new Exception("Unexpected error: could not load subtable '$idSubtable'.");
|
||||
}
|
||||
|
||||
return $subtable;
|
||||
}
|
||||
|
||||
private function fetchIntersectedWithThisBySegment(DataTable $table, $segmentValue)
|
||||
{
|
||||
$segmentStr = $this->thisReportDimensionSegment->getSegment() . "==" . urlencode($segmentValue);
|
||||
|
||||
// TODO: segment + report API method query params should be stored in DataTable metadata so we don't have to access it here
|
||||
$originalSegment = Common::getRequestVar('segment', false);
|
||||
if (!empty($originalSegment)) {
|
||||
$segmentStr = $originalSegment . ';' . $segmentStr;
|
||||
}
|
||||
|
||||
Log::debug("PivotByDimension: Fetching intersected with segment '%s'", $segmentStr);
|
||||
|
||||
$params = array('segment' => $segmentStr) + $this->getRequestParamOverride($table);
|
||||
return $this->pivotDimensionReport->fetch($params);
|
||||
}
|
||||
|
||||
private function setPivotByDimension($pivotByDimension)
|
||||
{
|
||||
$this->pivotByDimension = Dimension::factory($pivotByDimension);
|
||||
if (empty($this->pivotByDimension)) {
|
||||
throw new Exception("Invalid dimension '$pivotByDimension'.");
|
||||
}
|
||||
|
||||
$this->pivotDimensionReport = Report::getForDimension($this->pivotByDimension);
|
||||
}
|
||||
|
||||
private function setThisReportMetadata($report)
|
||||
{
|
||||
list($module, $method) = explode('.', $report);
|
||||
|
||||
$this->thisReport = Report::factory($module, $method);
|
||||
if (empty($this->thisReport)) {
|
||||
throw new Exception("Unable to find report '$report'.");
|
||||
}
|
||||
|
||||
$this->subtableDimension = $this->thisReport->getSubtableDimension();
|
||||
|
||||
$thisReportDimension = $this->thisReport->getDimension();
|
||||
if ($thisReportDimension !== null) {
|
||||
$segments = $thisReportDimension->getSegments();
|
||||
$this->thisReportDimensionSegment = reset($segments);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkSupportedPivot()
|
||||
{
|
||||
$reportId = $this->thisReport->getModule() . '.' . $this->thisReport->getName();
|
||||
|
||||
if (!$this->isFetchingBySegmentEnabled) {
|
||||
// if fetching by segment is disabled, then there must be a subtable for the current report and
|
||||
// subtable's dimension must be the pivot dimension
|
||||
|
||||
if (empty($this->subtableDimension)) {
|
||||
throw new Exception("Unsupported pivot: report '$reportId' has no subtable dimension.");
|
||||
}
|
||||
|
||||
if (!$this->isPivotDimensionSubtable()) {
|
||||
throw new Exception("Unsupported pivot: the subtable dimension for '$reportId' does not match the "
|
||||
. "requested pivotBy dimension. [subtable dimension = {$this->subtableDimension->getId()}, "
|
||||
. "pivot by dimension = {$this->pivotByDimension->getId()}]");
|
||||
}
|
||||
} else {
|
||||
$canFetchBySubtable = !empty($this->subtableDimension)
|
||||
&& $this->subtableDimension->getId() === $this->pivotByDimension->getId();
|
||||
if ($canFetchBySubtable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if fetching by segment is enabled, and we cannot fetch by subtable, then there has to be a report
|
||||
// for the pivot dimension (so we can fetch the report), and there has to be a segment for this report's
|
||||
// dimension (so we can use it when fetching)
|
||||
|
||||
if (empty($this->pivotDimensionReport)) {
|
||||
throw new Exception("Unsupported pivot: No report for pivot dimension '{$this->pivotByDimension->getId()}'"
|
||||
. " (report required for fetching intersected tables by segment).");
|
||||
}
|
||||
|
||||
if (empty($this->thisReportDimensionSegment)) {
|
||||
throw new Exception("Unsupported pivot: No segment for dimension of report '$reportId'."
|
||||
. " (segment required for fetching intersected tables by segment).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $columnRow
|
||||
* @param $pivotColumn
|
||||
* @return false|mixed
|
||||
*/
|
||||
private function getColumnValue(Row $columnRow, $pivotColumn)
|
||||
{
|
||||
$value = $columnRow->getColumn($pivotColumn);
|
||||
if (empty($value)
|
||||
&& !empty($this->metricIndexValue)
|
||||
) {
|
||||
$value = $columnRow->getColumn($this->metricIndexValue);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function getNameOfFirstNonLabelColumnInTable(DataTable $table)
|
||||
{
|
||||
foreach ($table->getRows() as $row) {
|
||||
foreach ($row->getColumns() as $columnName => $ignore) {
|
||||
if ($columnName != 'label') {
|
||||
return $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getRequestParamOverride(DataTable $table)
|
||||
{
|
||||
$params = array(
|
||||
'pivotBy' => '',
|
||||
'column' => '',
|
||||
'flat' => 0,
|
||||
'totals' => 0,
|
||||
'disable_queued_filters' => 1,
|
||||
'disable_generic_filters' => 1,
|
||||
'showColumns' => '',
|
||||
'hideColumns' => ''
|
||||
);
|
||||
|
||||
/** @var Site $site */
|
||||
$site = $table->getMetadata('site');
|
||||
if (!empty($site)) {
|
||||
$params['idSite'] = $site->getId();
|
||||
}
|
||||
|
||||
/** @var Period $period */
|
||||
$period = $table->getMetadata('period');
|
||||
if (!empty($period)) {
|
||||
$params['period'] = $period->getLabel();
|
||||
|
||||
if ($params['period'] == 'range') {
|
||||
$params['date'] = $period->getRangeString();
|
||||
} else {
|
||||
$params['date'] = $period->getDateStart()->toString();
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function getPivotTableDefaultRowFromColumnSummary($columnSet, $othersRowLabel)
|
||||
{
|
||||
// sort columns by sum (to ensure deterministic ordering)
|
||||
arsort($columnSet);
|
||||
|
||||
// limit columns if necessary (adding aggregate Others column at end)
|
||||
if ($this->pivotByColumnLimit > 0
|
||||
&& count($columnSet) > $this->pivotByColumnLimit
|
||||
) {
|
||||
$columnSet = array_slice($columnSet, 0, $this->pivotByColumnLimit - 1, $preserveKeys = true);
|
||||
$columnSet[$othersRowLabel] = 0;
|
||||
}
|
||||
|
||||
// remove column sums from array so it can be used as a default row
|
||||
$columnSet = array_map(function () { return false; }, $columnSet);
|
||||
|
||||
// make sure label column is first
|
||||
$columnSet = array('label' => false) + $columnSet;
|
||||
|
||||
return $columnSet;
|
||||
}
|
||||
|
||||
private function getOrderedColumnsWithPrependedNumerals($defaultRow, $othersRowLabel)
|
||||
{
|
||||
$flags = ENT_COMPAT;
|
||||
if (defined('ENT_HTML401')) {
|
||||
$flags |= ENT_HTML401; // part of default flags for 5.4, but not 5.3
|
||||
}
|
||||
|
||||
// must use decoded character otherwise sort later will fail
|
||||
// (sort column will be set to decoded but columns will have )
|
||||
$nbsp = html_entity_decode(' ', $flags, 'utf-8');
|
||||
|
||||
$result = array();
|
||||
|
||||
$currentIndex = 1;
|
||||
foreach ($defaultRow as $columnName => $ignore) {
|
||||
if ($columnName === $othersRowLabel
|
||||
|| $columnName === 'label'
|
||||
) {
|
||||
$result[] = $columnName;
|
||||
} else {
|
||||
$modifiedColumnName = $currentIndex . '.' . $nbsp . $columnName;
|
||||
$result[] = $modifiedColumnName;
|
||||
|
||||
++$currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if pivoting by subtable is supported for a report. Will return true if the report
|
||||
* has a subtable dimension and if the subtable dimension is different than the report's dimension.
|
||||
*
|
||||
* @param Report $report
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPivotingReportBySubtableSupported(Report $report)
|
||||
{
|
||||
return self::areDimensionsNotEqualAndNotNull($report->getSubtableDimension(), $report->getDimension());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if fetching intersected tables by segment is enabled in the INI config, false if otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSegmentFetchingEnabledInConfig()
|
||||
{
|
||||
return Config::getInstance()->General['pivot_by_filter_enable_fetch_by_segment'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default maximum number of columns to allow in a pivot table from the INI config.
|
||||
* Uses the **pivot_by_filter_default_column_limit** INI config option.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getDefaultColumnLimit()
|
||||
{
|
||||
return Config::getInstance()->General['pivot_by_filter_default_column_limit'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dimension|null $lhs
|
||||
* @param Dimension|null $rhs
|
||||
* @return bool
|
||||
*/
|
||||
private static function areDimensionsEqualAndNotNull($lhs, $rhs)
|
||||
{
|
||||
return !empty($lhs) && !empty($rhs) && $lhs->getId() == $rhs->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dimension|null $lhs
|
||||
* @param Dimension|null $rhs
|
||||
* @return bool
|
||||
*/
|
||||
private static function areDimensionsNotEqualAndNotNull($lhs, $rhs)
|
||||
{
|
||||
return !empty($lhs) && !empty($rhs) && $lhs->getId() != $rhs->getId();
|
||||
}
|
||||
}
|
||||
34
www/analytics/core/DataTable/Filter/PrependSegment.php
Normal file
34
www/analytics/core/DataTable/Filter/PrependSegment.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
|
||||
/**
|
||||
* Executes a callback for each row of a {@link DataTable} and prepends each existing segment with the
|
||||
* given segment.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('PrependSegment', array('segmentName==segmentValue;'));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class PrependSegment extends PrependValueToMetadata
|
||||
{
|
||||
/**
|
||||
* @param DataTable $table
|
||||
* @param string $prependSegment The segment to prepend if a segment is already defined. Make sure to include
|
||||
* A condition, eg the segment should end with ';' or ','
|
||||
*/
|
||||
public function __construct($table, $prependSegment = '')
|
||||
{
|
||||
parent::__construct($table, 'segment', $prependSegment);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\DataTable\Filter;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
|
||||
/**
|
||||
* Executes a callback for each row of a {@link DataTable} and prepends the given value to each metadata entry
|
||||
* but only if the given metadata entry exists.
|
||||
*
|
||||
* **Basic usage example**
|
||||
*
|
||||
* $dataTable->filter('PrependValueToMetadata', array('segment', 'segmentName==segmentValue'));
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class PrependValueToMetadata extends BaseFilter
|
||||
{
|
||||
private $metadataColumn;
|
||||
private $valueToPrepend;
|
||||
|
||||
/**
|
||||
* @param DataTable $table
|
||||
* @param string $metadataName The name of the metadata that should be prepended
|
||||
* @param string $valueToPrepend The value to prepend if the metadata entry exists
|
||||
*/
|
||||
public function __construct($table, $metadataName, $valueToPrepend)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
$this->metadataColumn = $metadataName;
|
||||
$this->valueToPrepend = $valueToPrepend;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link PrependValueToMetadata}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
if (empty($this->metadataColumn) || empty($this->valueToPrepend)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metadataColumn = $this->metadataColumn;
|
||||
$valueToPrepend = $this->valueToPrepend;
|
||||
|
||||
$table->filter(function (DataTable $dataTable) use ($metadataColumn, $valueToPrepend) {
|
||||
foreach ($dataTable->getRows() as $row) {
|
||||
$filter = $row->getMetadata($metadataColumn);
|
||||
if ($filter !== false) {
|
||||
$row->setMetadata($metadataColumn, $valueToPrepend . $filter);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -17,8 +17,8 @@ use Piwik\DataTable\BaseFilter;
|
|||
*/
|
||||
class RangeCheck extends BaseFilter
|
||||
{
|
||||
static public $minimumValue = 0.00;
|
||||
static public $maximumValue = 100.0;
|
||||
public static $minimumValue = 0.00;
|
||||
public static $maximumValue = 100.0;
|
||||
|
||||
/**
|
||||
* @param DataTable $table
|
||||
|
|
@ -32,7 +32,7 @@ class RangeCheck extends BaseFilter
|
|||
|
||||
$this->columnToFilter = $columnToFilter;
|
||||
|
||||
if ($minimumValue < $maximumValue) {
|
||||
if ((float) $minimumValue < (float) $maximumValue) {
|
||||
self::$minimumValue = $minimumValue;
|
||||
self::$maximumValue = $maximumValue;
|
||||
}
|
||||
|
|
@ -47,10 +47,23 @@ class RangeCheck extends BaseFilter
|
|||
{
|
||||
foreach ($table->getRows() as $row) {
|
||||
$value = $row->getColumn($this->columnToFilter);
|
||||
|
||||
if ($value === false) {
|
||||
$value = $row->getMetadata($this->columnToFilter);
|
||||
if ($value !== false) {
|
||||
if ($value < (float) self::$minimumValue) {
|
||||
$row->setMetadata($this->columnToFilter, self::$minimumValue);
|
||||
} elseif ($value > (float) self::$maximumValue) {
|
||||
$row->setMetadata($this->columnToFilter, self::$maximumValue);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value !== false) {
|
||||
if ($value < self::$minimumValue) {
|
||||
if ($value < (float) self::$minimumValue) {
|
||||
$row->setColumn($this->columnToFilter, self::$minimumValue);
|
||||
} elseif ($value > self::$maximumValue) {
|
||||
} elseif ($value > (float) self::$maximumValue) {
|
||||
$row->setColumn($this->columnToFilter, self::$maximumValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -18,15 +18,15 @@ use Piwik\Tracker\GoalManager;
|
|||
/**
|
||||
* Replaces column names in each row of a table using an array that maps old column
|
||||
* names new ones.
|
||||
*
|
||||
*
|
||||
* If no mapping is provided, this column will use one that maps index metric names
|
||||
* (which are integers) with their string column names. In the database, reports are
|
||||
* stored with integer metric names because it results in blobs that take up less space.
|
||||
* When loading the reports, the column names must be replaced, which is handled by this
|
||||
* class. (See {@link Piwik\Metrics} for more information about integer metric names.)
|
||||
*
|
||||
*
|
||||
* **Basic example**
|
||||
*
|
||||
*
|
||||
* // filter use in a plugin's API method
|
||||
* public function getMyReport($idSite, $period, $date, $segment = false, $expanded = false)
|
||||
* {
|
||||
|
|
@ -34,7 +34,7 @@ use Piwik\Tracker\GoalManager;
|
|||
* $dataTable->queueFilter('ReplaceColumnNames');
|
||||
* return $dataTable;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ReplaceColumnNames extends BaseFilter
|
||||
|
|
@ -43,14 +43,14 @@ class ReplaceColumnNames extends BaseFilter
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table that will be eventually filtered.
|
||||
* @param array|null $mappingToApply The name mapping to apply. Must map old column names
|
||||
* with new ones, eg,
|
||||
*
|
||||
*
|
||||
* array('OLD_COLUMN_NAME' => 'NEW_COLUMN NAME',
|
||||
* 'OLD_COLUMN_NAME2' => 'NEW_COLUMN NAME2')
|
||||
*
|
||||
*
|
||||
* If null, {@link Piwik\Metrics::$mappingFromIdToName} is used.
|
||||
*/
|
||||
public function __construct($table, $mappingToApply = null)
|
||||
|
|
@ -81,9 +81,8 @@ class ReplaceColumnNames extends BaseFilter
|
|||
*/
|
||||
protected function filterTable($table)
|
||||
{
|
||||
foreach ($table->getRows() as $key => $row) {
|
||||
$oldColumns = $row->getColumns();
|
||||
$newColumns = $this->getRenamedColumns($oldColumns);
|
||||
foreach ($table->getRows() as $row) {
|
||||
$newColumns = $this->getRenamedColumns($row->getColumns());
|
||||
$row->setColumns($newColumns);
|
||||
$this->filterSubTable($row);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,29 +10,28 @@ namespace Piwik\DataTable\Filter;
|
|||
|
||||
use Piwik\DataTable\BaseFilter;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Manager;
|
||||
use Piwik\Piwik;
|
||||
|
||||
/**
|
||||
* Replaces the label of the summary row with a supplied label.
|
||||
*
|
||||
*
|
||||
* This filter is only used to prettify the summary row label and so it should
|
||||
* always be queued on a {@link DataTable}.
|
||||
*
|
||||
*
|
||||
* This filter always recurses. In other words, this filter will always apply itself to
|
||||
* all subtables in the given {@link DataTable}'s table hierarchy.
|
||||
*
|
||||
*
|
||||
* **Basic example**
|
||||
*
|
||||
*
|
||||
* $dataTable->queueFilter('ReplaceSummaryRowLabel', array(Piwik::translate('General_Others')));
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ReplaceSummaryRowLabel extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table that will eventually be filtered.
|
||||
* @param string|null $newLabel The new label for summary row. If null, defaults to
|
||||
* `Piwik::translate('General_Others')`.
|
||||
|
|
@ -53,20 +52,20 @@ class ReplaceSummaryRowLabel extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
$rows = $table->getRows();
|
||||
foreach ($rows as $id => $row) {
|
||||
if ($row->getColumn('label') == DataTable::LABEL_SUMMARY_ROW
|
||||
|| $id == DataTable::ID_SUMMARY_ROW
|
||||
) {
|
||||
$row = $table->getRowFromId(DataTable::ID_SUMMARY_ROW);
|
||||
if ($row) {
|
||||
$row->setColumn('label', $this->newLabel);
|
||||
} else {
|
||||
$row = $table->getRowFromLabel(DataTable::LABEL_SUMMARY_ROW);
|
||||
if ($row) {
|
||||
$row->setColumn('label', $this->newLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// recurse
|
||||
foreach ($rows as $row) {
|
||||
if ($row->isSubtableLoaded()) {
|
||||
$subTable = Manager::getInstance()->getTable($row->getIdSubDataTable());
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $row) {
|
||||
$subTable = $row->getSubtable();
|
||||
if ($subTable) {
|
||||
$this->filter($subTable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -13,7 +13,7 @@ use Piwik\DataTable\BaseFilter;
|
|||
|
||||
/**
|
||||
* Sanitizes DataTable labels as an extra precaution. Called internally by Piwik.
|
||||
*
|
||||
*
|
||||
*/
|
||||
class SafeDecodeLabel extends BaseFilter
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -16,7 +16,7 @@ use Piwik\Metrics;
|
|||
|
||||
/**
|
||||
* Sorts a {@link DataTable} based on the value of a specific column.
|
||||
*
|
||||
*
|
||||
* It is possible to specify a natural sorting (see [php.net/natsort](http://php.net/natsort) for details).
|
||||
*
|
||||
* @api
|
||||
|
|
@ -25,24 +25,30 @@ class Sort extends BaseFilter
|
|||
{
|
||||
protected $columnToSort;
|
||||
protected $order;
|
||||
protected $sign;
|
||||
|
||||
const ORDER_DESC = 'desc';
|
||||
const ORDER_ASC = 'asc';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table to eventually filter.
|
||||
* @param string $columnToSort The name of the column to sort by.
|
||||
* @param string $order order `'asc'` or `'desc'`.
|
||||
* @param bool $naturalSort Whether to use a natural sort or not (see {@link http://php.net/natsort}).
|
||||
* @param bool $recursiveSort Whether to sort all subtables or not.
|
||||
*/
|
||||
public function __construct($table, $columnToSort, $order = 'desc', $naturalSort = true, $recursiveSort = false)
|
||||
public function __construct($table, $columnToSort, $order = 'desc', $naturalSort = true, $recursiveSort = true)
|
||||
{
|
||||
parent::__construct($table);
|
||||
|
||||
if ($recursiveSort) {
|
||||
$table->enableRecursiveSort();
|
||||
}
|
||||
|
||||
$this->columnToSort = $columnToSort;
|
||||
$this->naturalSort = $naturalSort;
|
||||
$this->naturalSort = $naturalSort;
|
||||
$this->setOrder($order);
|
||||
}
|
||||
|
||||
|
|
@ -55,67 +61,56 @@ class Sort extends BaseFilter
|
|||
{
|
||||
if ($order == 'asc') {
|
||||
$this->order = 'asc';
|
||||
$this->sign = 1;
|
||||
$this->sign = 1;
|
||||
} else {
|
||||
$this->order = 'desc';
|
||||
$this->sign = -1;
|
||||
$this->sign = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting method used for sorting numbers
|
||||
*
|
||||
* @param number $a
|
||||
* @param number $b
|
||||
* @param array $rowA array[0 => value of column to sort, 1 => label]
|
||||
* @param array $rowB array[0 => value of column to sort, 1 => label]
|
||||
* @return int
|
||||
*/
|
||||
public function numberSort($a, $b)
|
||||
public function numberSort($rowA, $rowB)
|
||||
{
|
||||
return !isset($a->c[Row::COLUMNS][$this->columnToSort])
|
||||
&& !isset($b->c[Row::COLUMNS][$this->columnToSort])
|
||||
if (isset($rowA[0]) && isset($rowB[0])) {
|
||||
if ($rowA[0] != $rowB[0] || !isset($rowA[1])) {
|
||||
return $this->sign * ($rowA[0] < $rowB[0] ? -1 : 1);
|
||||
} else {
|
||||
return -1 * $this->sign * strnatcasecmp($rowA[1], $rowB[1]);
|
||||
}
|
||||
} elseif (!isset($rowB[0]) && !isset($rowA[0])) {
|
||||
return -1 * $this->sign * strnatcasecmp($rowA[1], $rowB[1]);
|
||||
} elseif (!isset($rowA[0])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
? 0
|
||||
: (
|
||||
!isset($a->c[Row::COLUMNS][$this->columnToSort])
|
||||
? 1
|
||||
: (
|
||||
!isset($b->c[Row::COLUMNS][$this->columnToSort])
|
||||
? -1
|
||||
: (($a->c[Row::COLUMNS][$this->columnToSort] != $b->c[Row::COLUMNS][$this->columnToSort]
|
||||
|| !isset($a->c[Row::COLUMNS]['label']))
|
||||
? ($this->sign * (
|
||||
$a->c[Row::COLUMNS][$this->columnToSort]
|
||||
< $b->c[Row::COLUMNS][$this->columnToSort]
|
||||
? -1
|
||||
: 1)
|
||||
)
|
||||
: -1 * $this->sign * strnatcasecmp(
|
||||
$a->c[Row::COLUMNS]['label'],
|
||||
$b->c[Row::COLUMNS]['label'])
|
||||
)
|
||||
)
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting method used for sorting values natural
|
||||
*
|
||||
* @param mixed $a
|
||||
* @param mixed $b
|
||||
* @param mixed $valA
|
||||
* @param mixed $valB
|
||||
* @return int
|
||||
*/
|
||||
function naturalSort($a, $b)
|
||||
public function naturalSort($valA, $valB)
|
||||
{
|
||||
return !isset($a->c[Row::COLUMNS][$this->columnToSort])
|
||||
&& !isset($b->c[Row::COLUMNS][$this->columnToSort])
|
||||
return !isset($valA)
|
||||
&& !isset($valB)
|
||||
? 0
|
||||
: (!isset($a->c[Row::COLUMNS][$this->columnToSort])
|
||||
: (!isset($valA)
|
||||
? 1
|
||||
: (!isset($b->c[Row::COLUMNS][$this->columnToSort])
|
||||
: (!isset($valB)
|
||||
? -1
|
||||
: $this->sign * strnatcasecmp(
|
||||
$a->c[Row::COLUMNS][$this->columnToSort],
|
||||
$b->c[Row::COLUMNS][$this->columnToSort]
|
||||
$valA,
|
||||
$valB
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
@ -124,27 +119,38 @@ class Sort extends BaseFilter
|
|||
/**
|
||||
* Sorting method used for sorting values
|
||||
*
|
||||
* @param mixed $a
|
||||
* @param mixed $b
|
||||
* @param mixed $valA
|
||||
* @param mixed $valB
|
||||
* @return int
|
||||
*/
|
||||
function sortString($a, $b)
|
||||
public function sortString($valA, $valB)
|
||||
{
|
||||
return !isset($a->c[Row::COLUMNS][$this->columnToSort])
|
||||
&& !isset($b->c[Row::COLUMNS][$this->columnToSort])
|
||||
return !isset($valA)
|
||||
&& !isset($valB)
|
||||
? 0
|
||||
: (!isset($a->c[Row::COLUMNS][$this->columnToSort])
|
||||
: (!isset($valA)
|
||||
? 1
|
||||
: (!isset($b->c[Row::COLUMNS][$this->columnToSort])
|
||||
: (!isset($valB)
|
||||
? -1
|
||||
: $this->sign *
|
||||
strcasecmp($a->c[Row::COLUMNS][$this->columnToSort],
|
||||
$b->c[Row::COLUMNS][$this->columnToSort]
|
||||
strcasecmp($valA,
|
||||
$valB
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function getColumnValue(Row $row)
|
||||
{
|
||||
$value = $row->getColumn($this->columnToSort);
|
||||
|
||||
if ($value === false || is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column to be used for sorting
|
||||
*
|
||||
|
|
@ -153,18 +159,18 @@ class Sort extends BaseFilter
|
|||
*/
|
||||
protected function selectColumnToSort($row)
|
||||
{
|
||||
$value = $row->getColumn($this->columnToSort);
|
||||
if ($value !== false) {
|
||||
$value = $row->hasColumn($this->columnToSort);
|
||||
if ($value) {
|
||||
return $this->columnToSort;
|
||||
}
|
||||
|
||||
$columnIdToName = Metrics::getMappingFromIdToName();
|
||||
$columnIdToName = Metrics::getMappingFromNameToId();
|
||||
// sorting by "nb_visits" but the index is Metrics::INDEX_NB_VISITS in the table
|
||||
if (isset($columnIdToName[$this->columnToSort])) {
|
||||
$column = $columnIdToName[$this->columnToSort];
|
||||
$value = $row->getColumn($column);
|
||||
$value = $row->hasColumn($column);
|
||||
|
||||
if ($value !== false) {
|
||||
if ($value) {
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
|
|
@ -172,8 +178,8 @@ class Sort extends BaseFilter
|
|||
// eg. was previously sorted by revenue_per_visit, but this table
|
||||
// doesn't have this column; defaults with nb_visits
|
||||
$column = Metrics::INDEX_NB_VISITS;
|
||||
$value = $row->getColumn($column);
|
||||
if ($value !== false) {
|
||||
$value = $row->hasColumn($column);
|
||||
if ($value) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
|
|
@ -193,21 +199,25 @@ class Sort extends BaseFilter
|
|||
if ($table instanceof Simple) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($this->columnToSort)) {
|
||||
return;
|
||||
}
|
||||
$rows = $table->getRows();
|
||||
if (count($rows) == 0) {
|
||||
|
||||
if (!$table->getRowsCount()) {
|
||||
return;
|
||||
}
|
||||
$row = current($rows);
|
||||
|
||||
$row = $table->getFirstRow();
|
||||
if ($row === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->columnToSort = $this->selectColumnToSort($row);
|
||||
|
||||
$value = $row->getColumn($this->columnToSort);
|
||||
if (is_numeric($value)) {
|
||||
$value = $this->getFirstValueFromDataTable($table);
|
||||
|
||||
if (is_numeric($value) && $this->columnToSort !== 'label') {
|
||||
$methodToUse = "numberSort";
|
||||
} else {
|
||||
if ($this->naturalSort) {
|
||||
|
|
@ -216,6 +226,65 @@ class Sort extends BaseFilter
|
|||
$methodToUse = "sortString";
|
||||
}
|
||||
}
|
||||
$table->sort(array($this, $methodToUse), $this->columnToSort);
|
||||
|
||||
$this->sort($table, $methodToUse);
|
||||
}
|
||||
|
||||
private function getFirstValueFromDataTable($table)
|
||||
{
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $row) {
|
||||
$value = $this->getColumnValue($row);
|
||||
if (!is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the DataTable rows using the supplied callback function.
|
||||
*
|
||||
* @param string $functionCallback A comparison callback compatible with {@link usort}.
|
||||
* @param string $columnSortedBy The column name `$functionCallback` sorts by. This is stored
|
||||
* so we can determine how the DataTable was sorted in the future.
|
||||
*/
|
||||
private function sort(DataTable $table, $functionCallback)
|
||||
{
|
||||
$table->setTableSortedBy($this->columnToSort);
|
||||
|
||||
$rows = $table->getRowsWithoutSummaryRow();
|
||||
|
||||
// get column value and label only once for performance tweak
|
||||
$values = array();
|
||||
if ($functionCallback === 'numberSort') {
|
||||
foreach ($rows as $key => $row) {
|
||||
$values[$key] = array($this->getColumnValue($row), $row->getColumn('label'));
|
||||
}
|
||||
} else {
|
||||
foreach ($rows as $key => $row) {
|
||||
$values[$key] = $this->getColumnValue($row);
|
||||
}
|
||||
}
|
||||
|
||||
uasort($values, array($this, $functionCallback));
|
||||
|
||||
$sortedRows = array();
|
||||
foreach ($values as $key => $value) {
|
||||
$sortedRows[] = $rows[$key];
|
||||
}
|
||||
|
||||
$table->setRows($sortedRows);
|
||||
|
||||
unset($rows);
|
||||
unset($sortedRows);
|
||||
|
||||
if ($table->isSortRecursiveEnabled()) {
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $row) {
|
||||
$subTable = $row->getSubtable();
|
||||
if ($subTable) {
|
||||
$subTable->enableRecursiveSort();
|
||||
$this->sort($subTable, $functionCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -16,26 +16,26 @@ use Piwik\Piwik;
|
|||
/**
|
||||
* Truncates a {@link DataTable} by merging all rows after a certain index into a new summary
|
||||
* row. If the count of rows is less than the index, nothing happens.
|
||||
*
|
||||
*
|
||||
* The {@link ReplaceSummaryRowLabel} filter will be queued after the table is truncated.
|
||||
*
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
*
|
||||
* **Basic usage**
|
||||
*
|
||||
*
|
||||
* $dataTable->filter('Truncate', array($truncateAfter = 500));
|
||||
*
|
||||
*
|
||||
* **Using a custom summary row label**
|
||||
*
|
||||
*
|
||||
* $dataTable->filter('Truncate', array($truncateAfter = 500, $summaryRowLabel = Piwik::translate('General_Total')));
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Truncate extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable $table The table that will be filtered eventually.
|
||||
* @param int $truncateAfter The row index to truncate at. All rows passed this index will
|
||||
* be removed.
|
||||
|
|
@ -69,11 +69,15 @@ class Truncate extends BaseFilter
|
|||
*/
|
||||
public function filter($table)
|
||||
{
|
||||
if ($this->truncateAfter < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addSummaryRow($table);
|
||||
$table->queueFilter('ReplaceSummaryRowLabel', array($this->labelSummaryRow));
|
||||
|
||||
if ($this->filterRecursive) {
|
||||
foreach ($table->getRows() as $row) {
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $row) {
|
||||
if ($row->isSubtableLoaded()) {
|
||||
$this->filter($row->getSubtable());
|
||||
}
|
||||
|
|
@ -81,17 +85,23 @@ class Truncate extends BaseFilter
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable $table
|
||||
*/
|
||||
private function addSummaryRow($table)
|
||||
{
|
||||
$table->filter('Sort', array($this->columnToSortByBeforeTruncating, 'desc'));
|
||||
|
||||
if ($table->getRowsCount() <= $this->truncateAfter + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = $table->getRows();
|
||||
$count = $table->getRowsCount();
|
||||
$table->filter('Sort', array($this->columnToSortByBeforeTruncating, 'desc', $naturalSort = true, $recursiveSort = false));
|
||||
|
||||
$rows = array_values($table->getRows());
|
||||
$count = $table->getRowsCount();
|
||||
$newRow = new Row(array(Row::COLUMNS => array('label' => DataTable::LABEL_SUMMARY_ROW)));
|
||||
|
||||
$aggregationOps = $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME);
|
||||
|
||||
for ($i = $this->truncateAfter; $i < $count; $i++) {
|
||||
if (!isset($rows[$i])) {
|
||||
// case when the last row is a summary row, it is not indexed by $cout but by DataTable::ID_SUMMARY_ROW
|
||||
|
|
@ -99,10 +109,10 @@ class Truncate extends BaseFilter
|
|||
|
||||
//FIXME: I'm not sure why it could return false, but it was reported in: http://forum.piwik.org/read.php?2,89324,page=1#msg-89442
|
||||
if ($summaryRow) {
|
||||
$newRow->sumRow($summaryRow, $enableCopyMetadata = false, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME));
|
||||
$newRow->sumRow($summaryRow, $enableCopyMetadata = false, $aggregationOps);
|
||||
}
|
||||
} else {
|
||||
$newRow->sumRow($rows[$i], $enableCopyMetadata = false, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME));
|
||||
$newRow->sumRow($rows[$i], $enableCopyMetadata = false, $aggregationOps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -12,29 +12,30 @@ namespace Piwik\DataTable;
|
|||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Singleton;
|
||||
|
||||
/**
|
||||
* The DataTable_Manager registers all the instanciated DataTable and provides an
|
||||
* easy way to access them. This is used to store all the DataTable during the archiving process.
|
||||
* At the end of archiving, the ArchiveProcessor will read the stored datatable and record them in the DB.
|
||||
*
|
||||
* @method static \Piwik\DataTable\Manager getInstance()
|
||||
*/
|
||||
class Manager extends Singleton
|
||||
class Manager extends \ArrayObject
|
||||
{
|
||||
/**
|
||||
* Array used to store the DataTable
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tables = array();
|
||||
|
||||
/**
|
||||
* Id of the next inserted table id in the Manager
|
||||
* @var int
|
||||
*/
|
||||
protected $nextTableId = 1;
|
||||
protected $nextTableId = 0;
|
||||
|
||||
private static $instance;
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new Manager();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a DataTable to the registry
|
||||
|
|
@ -44,9 +45,9 @@ class Manager extends Singleton
|
|||
*/
|
||||
public function addTable($table)
|
||||
{
|
||||
$this->tables[$this->nextTableId] = $table;
|
||||
$this->nextTableId++;
|
||||
return $this->nextTableId - 1;
|
||||
$this[$this->nextTableId] = $table;
|
||||
return $this->nextTableId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,10 +61,11 @@ class Manager extends Singleton
|
|||
*/
|
||||
public function getTable($idTable)
|
||||
{
|
||||
if (!isset($this->tables[$idTable])) {
|
||||
throw new TableNotFoundException(sprintf("This report has been reprocessed since your last click. To see this error less often, please increase the timeout value in seconds in Settings > General Settings. (error: id %s not found).", $idTable));
|
||||
if (!isset($this[$idTable])) {
|
||||
throw new TableNotFoundException(sprintf("Error: table id %s not found in memory. (If this error is causing you problems in production, please report it in Piwik issue tracker.)", $idTable));
|
||||
}
|
||||
return $this->tables[$idTable];
|
||||
|
||||
return $this[$idTable];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,7 +75,7 @@ class Manager extends Singleton
|
|||
*/
|
||||
public function getMostRecentTableId()
|
||||
{
|
||||
return $this->nextTableId - 1;
|
||||
return $this->nextTableId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -81,14 +83,15 @@ class Manager extends Singleton
|
|||
*/
|
||||
public function deleteAll($deleteWhenIdTableGreaterThan = 0)
|
||||
{
|
||||
foreach ($this->tables as $id => $table) {
|
||||
foreach ($this as $id => $table) {
|
||||
if ($id > $deleteWhenIdTableGreaterThan) {
|
||||
$this->deleteTable($id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleteWhenIdTableGreaterThan == 0) {
|
||||
$this->tables = array();
|
||||
$this->nextTableId = 1;
|
||||
$this->exchangeArray(array());
|
||||
$this->nextTableId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,8 +103,8 @@ class Manager extends Singleton
|
|||
*/
|
||||
public function deleteTable($id)
|
||||
{
|
||||
if (isset($this->tables[$id])) {
|
||||
Common::destroy($this->tables[$id]);
|
||||
if (isset($this[$id])) {
|
||||
Common::destroy($this[$id]);
|
||||
$this->setTableDeleted($id);
|
||||
}
|
||||
}
|
||||
|
|
@ -131,7 +134,7 @@ class Manager extends Singleton
|
|||
*/
|
||||
public function setTableDeleted($id)
|
||||
{
|
||||
$this->tables[$id] = null;
|
||||
$this[$id] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -140,7 +143,7 @@ class Manager extends Singleton
|
|||
public function dumpAllTables()
|
||||
{
|
||||
echo "<hr />Manager->dumpAllTables()<br />";
|
||||
foreach ($this->tables as $id => $table) {
|
||||
foreach ($this as $id => $table) {
|
||||
if (!($table instanceof DataTable)) {
|
||||
echo "Error table $id is not instance of datatable<br />";
|
||||
var_export($table);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
namespace Piwik\DataTable;
|
||||
|
||||
use Closure;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Renderer\Console;
|
||||
|
|
@ -15,10 +16,10 @@ use Piwik\DataTable\Renderer\Console;
|
|||
/**
|
||||
* Stores an array of {@link DataTable}s indexed by one type of {@link DataTable} metadata (such as site ID
|
||||
* or period).
|
||||
*
|
||||
*
|
||||
* DataTable Maps are returned on all queries that involve multiple sites and/or multiple
|
||||
* periods. The Maps will contain a {@link DataTable} for each site and period combination.
|
||||
*
|
||||
*
|
||||
* The Map implements some {@link DataTable} such as {@link queueFilter()} and {@link getRowsCount}.
|
||||
*
|
||||
*
|
||||
|
|
@ -73,7 +74,7 @@ class Map implements DataTableInterface
|
|||
|
||||
/**
|
||||
* Queue a filter to {@link DataTable} child of contained by this instance.
|
||||
*
|
||||
*
|
||||
* See {@link Piwik\DataTable::queueFilter()} for more information..
|
||||
*
|
||||
* @param string|Closure $className Filter name, eg. `'Limit'` or a Closure.
|
||||
|
|
@ -104,11 +105,37 @@ class Map implements DataTableInterface
|
|||
*/
|
||||
public function filter($className, $parameters = array())
|
||||
{
|
||||
foreach ($this->getDataTables() as $id => $table) {
|
||||
foreach ($this->getDataTables() as $table) {
|
||||
$table->filter($className, $parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a filter to all subtables contained by this instance.
|
||||
*
|
||||
* @param string|Closure $className Name of filter class or a Closure.
|
||||
* @param array $parameters Parameters to pass to the filter.
|
||||
*/
|
||||
public function filterSubtables($className, $parameters = array())
|
||||
{
|
||||
foreach ($this->getDataTables() as $table) {
|
||||
$table->filterSubtables($className, $parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a queued filter to all subtables contained by this instance.
|
||||
*
|
||||
* @param string|Closure $className Name of filter class or a Closure.
|
||||
* @param array $parameters Parameters to pass to the filter.
|
||||
*/
|
||||
public function queueFilterSubtables($className, $parameters = array())
|
||||
{
|
||||
foreach ($this->getDataTables() as $table) {
|
||||
$table->queueFilterSubtables($className, $parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of DataTables contained by this class.
|
||||
*
|
||||
|
|
@ -142,7 +169,7 @@ class Map implements DataTableInterface
|
|||
|
||||
/**
|
||||
* Returns the last element in the Map's array.
|
||||
*
|
||||
*
|
||||
* @return DataTable|Map|false
|
||||
*/
|
||||
public function getLastRow()
|
||||
|
|
@ -161,6 +188,22 @@ class Map implements DataTableInterface
|
|||
$this->array[$label] = $table;
|
||||
}
|
||||
|
||||
public function getRowFromIdSubDataTable($idSubtable)
|
||||
{
|
||||
$dataTables = $this->getDataTables();
|
||||
|
||||
// find first datatable containing data
|
||||
foreach ($dataTables as $subTable) {
|
||||
$subTableRow = $subTable->getRowFromIdSubDataTable($idSubtable);
|
||||
|
||||
if (!empty($subTableRow)) {
|
||||
return $subTableRow;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string output of this DataTable\Map (applying the default renderer to every {@link DataTable}
|
||||
* of this DataTable\Map).
|
||||
|
|
@ -184,11 +227,31 @@ class Map implements DataTableInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public function disableRecursiveFilters()
|
||||
{
|
||||
foreach ($this->getDataTables() as $table) {
|
||||
$table->disableRecursiveFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public function enableRecursiveFilters()
|
||||
{
|
||||
foreach ($this->getDataTables() as $table) {
|
||||
$table->enableRecursiveFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the given column in each contained {@link DataTable}.
|
||||
*
|
||||
* See {@link DataTable::renameColumn()}.
|
||||
*
|
||||
*
|
||||
* @param string $oldName
|
||||
* @param string $newName
|
||||
*/
|
||||
|
|
@ -203,7 +266,7 @@ class Map implements DataTableInterface
|
|||
* Deletes the specified columns in each contained {@link DataTable}.
|
||||
*
|
||||
* See {@link DataTable::deleteColumns()}.
|
||||
*
|
||||
*
|
||||
* @param array $columns The columns to delete.
|
||||
* @param bool $deleteRecursiveInSubtables This param is currently not used.
|
||||
*/
|
||||
|
|
@ -216,7 +279,7 @@ class Map implements DataTableInterface
|
|||
|
||||
/**
|
||||
* Deletes a table from the array of DataTables.
|
||||
*
|
||||
*
|
||||
* @param string $id The label associated with {@link DataTable}.
|
||||
*/
|
||||
public function deleteRow($id)
|
||||
|
|
@ -246,12 +309,14 @@ class Map implements DataTableInterface
|
|||
public function getColumn($name)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($this->getDataTables() as $table) {
|
||||
$moreValues = $table->getColumn($name);
|
||||
foreach ($moreValues as &$value) {
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
|
@ -263,19 +328,19 @@ class Map implements DataTableInterface
|
|||
* The result of this function is determined by the type of DataTable
|
||||
* this instance holds. If this DataTable\Map instance holds an array
|
||||
* of DataTables, this function will transform it from:
|
||||
*
|
||||
*
|
||||
* Label 0:
|
||||
* DataTable(row1)
|
||||
* Label 1:
|
||||
* DataTable(row2)
|
||||
*
|
||||
*
|
||||
* to:
|
||||
*
|
||||
*
|
||||
* DataTable(row1[label = 'Label 0'], row2[label = 'Label 1'])
|
||||
*
|
||||
* If this instance holds an array of DataTable\Maps, this function will
|
||||
* transform it from:
|
||||
*
|
||||
*
|
||||
* Outer Label 0: // the outer DataTable\Map
|
||||
* Inner Label 0: // one of the inner DataTable\Maps
|
||||
* DataTable(row1)
|
||||
|
|
@ -286,9 +351,9 @@ class Map implements DataTableInterface
|
|||
* DataTable(row3)
|
||||
* Inner Label 1:
|
||||
* DataTable(row4)
|
||||
*
|
||||
*
|
||||
* to:
|
||||
*
|
||||
*
|
||||
* Inner Label 0:
|
||||
* DataTable(row1[label = 'Outer Label 0'], row3[label = 'Outer Label 1'])
|
||||
* Inner Label 1:
|
||||
|
|
@ -366,11 +431,11 @@ class Map implements DataTableInterface
|
|||
|
||||
/**
|
||||
* Sums a DataTable to all the tables in this array.
|
||||
*
|
||||
*
|
||||
* _Note: Will only add `$tableToSum` if the childTable has some rows._
|
||||
*
|
||||
* See {@link Piwik\DataTable::addDataTable()}.
|
||||
*
|
||||
*
|
||||
* @param DataTable $tableToSum
|
||||
*/
|
||||
public function addDataTable(DataTable $tableToSum)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,10 +9,11 @@
|
|||
namespace Piwik\DataTable;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Loader;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\BaseFactory;
|
||||
|
||||
/**
|
||||
* A DataTable Renderer can produce an output given a DataTable object.
|
||||
|
|
@ -22,7 +23,7 @@ use Piwik\Piwik;
|
|||
* $render->setTable($dataTable);
|
||||
* echo $render;
|
||||
*/
|
||||
abstract class Renderer
|
||||
abstract class Renderer extends BaseFactory
|
||||
{
|
||||
protected $table;
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ abstract class Renderer
|
|||
*/
|
||||
protected function renderHeader()
|
||||
{
|
||||
@header('Content-Type: text/plain; charset=utf-8');
|
||||
Common::sendHeader('Content-Type: text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -110,22 +111,6 @@ abstract class Renderer
|
|||
*/
|
||||
abstract public function render();
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function renderException();
|
||||
|
||||
protected function getExceptionMessage()
|
||||
{
|
||||
$message = $this->exception->getMessage();
|
||||
if (\Piwik_ShouldPrintBackTraceWithMessage()) {
|
||||
$message .= "\n" . $this->exception->getTraceAsString();
|
||||
}
|
||||
return self::renderHtmlEntities($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see render()
|
||||
* @return string
|
||||
|
|
@ -144,32 +129,17 @@ abstract class Renderer
|
|||
public function setTable($table)
|
||||
{
|
||||
if (!is_array($table)
|
||||
&& !($table instanceof DataTable)
|
||||
&& !($table instanceof DataTable\Map)
|
||||
&& !($table instanceof DataTableInterface)
|
||||
) {
|
||||
throw new Exception("DataTable renderers renderer accepts only DataTable and Map instances, and arrays.");
|
||||
throw new Exception("DataTable renderers renderer accepts only DataTable, Simple and Map instances, and arrays.");
|
||||
}
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Exception to be rendered
|
||||
*
|
||||
* @param Exception $exception to be rendered
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setException($exception)
|
||||
{
|
||||
if (!($exception instanceof Exception)) {
|
||||
throw new Exception("The exception renderer accepts only an Exception object.");
|
||||
}
|
||||
$this->exception = $exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
static protected $availableRenderers = array('xml',
|
||||
protected static $availableRenderers = array('xml',
|
||||
'json',
|
||||
'csv',
|
||||
'tsv',
|
||||
|
|
@ -182,41 +152,25 @@ abstract class Renderer
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function getRenderers()
|
||||
public static function getRenderers()
|
||||
{
|
||||
return self::$availableRenderers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DataTable associated to the output format $name
|
||||
*
|
||||
* @param string $name
|
||||
* @throws Exception If the renderer is unknown
|
||||
* @return \Piwik\DataTable\Renderer
|
||||
*/
|
||||
static public function factory($name)
|
||||
protected static function getClassNameFromClassId($id)
|
||||
{
|
||||
$className = ucfirst(strtolower($name));
|
||||
$className = ucfirst(strtolower($id));
|
||||
$className = 'Piwik\DataTable\Renderer\\' . $className;
|
||||
try {
|
||||
Loader::loadClass($className);
|
||||
return new $className;
|
||||
} catch (Exception $e) {
|
||||
$availableRenderers = implode(', ', self::getRenderers());
|
||||
@header('Content-Type: text/plain; charset=utf-8');
|
||||
throw new Exception(Piwik::translate('General_ExceptionInvalidRendererFormat', array($className, $availableRenderers)));
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns $rawData after all applicable characters have been converted to HTML entities.
|
||||
*
|
||||
* @param String $rawData data to be converted
|
||||
* @return String
|
||||
*/
|
||||
static protected function renderHtmlEntities($rawData)
|
||||
protected static function getInvalidClassIdExceptionMessage($id)
|
||||
{
|
||||
return self::formatValueXml($rawData);
|
||||
$availableRenderers = implode(', ', self::getRenderers());
|
||||
$klassName = self::getClassNameFromClassId($id);
|
||||
|
||||
return Piwik::translate('General_ExceptionInvalidRendererFormat', array($klassName, $availableRenderers));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -236,12 +190,14 @@ abstract class Renderer
|
|||
$value = @mb_convert_encoding($value, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
|
||||
|
||||
$htmlentities = array(" ", "¡", "¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€");
|
||||
$xmlentities = array("¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€");
|
||||
$value = str_replace($htmlentities, $xmlentities, $value);
|
||||
$xmlentities = array("¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€");
|
||||
$value = str_replace($htmlentities, $xmlentities, $value);
|
||||
} elseif ($value === false) {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,7 +8,6 @@
|
|||
*/
|
||||
namespace Piwik\DataTable\Renderer;
|
||||
|
||||
use Piwik\DataTable\Manager;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Renderer;
|
||||
|
||||
|
|
@ -31,22 +30,9 @@ class Console extends Renderer
|
|||
*/
|
||||
public function render()
|
||||
{
|
||||
$this->renderHeader();
|
||||
return $this->renderTable($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderException()
|
||||
{
|
||||
$this->renderHeader();
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
return 'Error: ' . $exceptionMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to be used
|
||||
*
|
||||
|
|
@ -85,8 +71,9 @@ class Console extends Renderer
|
|||
*/
|
||||
protected function renderTable($table, $prefix = "")
|
||||
{
|
||||
if (is_array($table)) // convert array to DataTable
|
||||
{
|
||||
if (is_array($table)) {
|
||||
// convert array to DataTable
|
||||
|
||||
$table = DataTable::makeFromSimpleArray($table);
|
||||
}
|
||||
|
||||
|
|
@ -110,8 +97,11 @@ class Console extends Renderer
|
|||
$dataTableMapBreak = true;
|
||||
break;
|
||||
}
|
||||
if (is_string($value)) $value = "'$value'";
|
||||
elseif (is_array($value)) $value = var_export($value, true);
|
||||
if (is_string($value)) {
|
||||
$value = "'$value'";
|
||||
} elseif (is_array($value)) {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
$columns[] = "'$column' => $value";
|
||||
}
|
||||
|
|
@ -122,8 +112,11 @@ class Console extends Renderer
|
|||
|
||||
$metadata = array();
|
||||
foreach ($row->getMetadata() as $name => $value) {
|
||||
if (is_string($value)) $value = "'$value'";
|
||||
elseif (is_array($value)) $value = var_export($value, true);
|
||||
if (is_string($value)) {
|
||||
$value = "'$value'";
|
||||
} elseif (is_array($value)) {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
$metadata[] = "'$name' => $value";
|
||||
}
|
||||
$metadata = implode(", ", $metadata);
|
||||
|
|
@ -133,14 +126,10 @@ class Console extends Renderer
|
|||
. $row->getIdSubDataTable() . "]<br />\n";
|
||||
|
||||
if (!is_null($row->getIdSubDataTable())) {
|
||||
if ($row->isSubtableLoaded()) {
|
||||
$subTable = $row->getSubtable();
|
||||
if ($subTable) {
|
||||
$depth++;
|
||||
$output .= $this->renderTable(
|
||||
Manager::getInstance()->getTable(
|
||||
$row->getIdSubDataTable()
|
||||
),
|
||||
$prefix . ' '
|
||||
);
|
||||
$output .= $this->renderTable($subTable, $prefix . ' ');
|
||||
$depth--;
|
||||
} else {
|
||||
$output .= "-- Sub DataTable not loaded<br />\n";
|
||||
|
|
@ -155,7 +144,7 @@ class Console extends Renderer
|
|||
foreach ($metadata as $id => $metadataIn) {
|
||||
$output .= "<br />";
|
||||
$output .= $prefix . " <b>$id</b><br />";
|
||||
if(is_array($metadataIn)) {
|
||||
if (is_array($metadataIn)) {
|
||||
foreach ($metadataIn as $name => $value) {
|
||||
$output .= $prefix . $prefix . "$name => $value";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -70,6 +70,8 @@ class Csv extends Renderer
|
|||
*/
|
||||
const NO_DATA_AVAILABLE = 'No data available';
|
||||
|
||||
private $unsupportedColumns = array();
|
||||
|
||||
/**
|
||||
* Computes the dataTable output and returns the string/binary
|
||||
*
|
||||
|
|
@ -84,26 +86,10 @@ class Csv extends Renderer
|
|||
|
||||
$this->renderHeader();
|
||||
|
||||
if ($this->convertToUnicode
|
||||
&& function_exists('mb_convert_encoding')
|
||||
) {
|
||||
$str = chr(255) . chr(254) . mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
|
||||
}
|
||||
$str = $this->convertToUnicode($str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function renderException()
|
||||
{
|
||||
@header('Content-Type: text/html; charset=utf-8');
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
return 'Error: ' . $exceptionMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables / Disables unicode converting
|
||||
*
|
||||
|
|
@ -133,8 +119,9 @@ class Csv extends Renderer
|
|||
*/
|
||||
protected function renderTable($table, &$allColumns = array())
|
||||
{
|
||||
if (is_array($table)) // convert array to DataTable
|
||||
{
|
||||
if (is_array($table)) {
|
||||
// convert array to DataTable
|
||||
|
||||
$table = DataTable::makeFromSimpleArray($table);
|
||||
}
|
||||
|
||||
|
|
@ -205,42 +192,7 @@ class Csv extends Renderer
|
|||
}
|
||||
}
|
||||
|
||||
$csv = array();
|
||||
foreach ($table->getRows() as $row) {
|
||||
$csvRow = $this->flattenColumnArray($row->getColumns());
|
||||
|
||||
if ($this->exportMetadata) {
|
||||
$metadata = $row->getMetadata();
|
||||
foreach ($metadata as $name => $value) {
|
||||
if ($name == 'idsubdatatable_in_db') {
|
||||
continue;
|
||||
}
|
||||
//if a metadata and a column have the same name make sure they dont overwrite
|
||||
if ($this->translateColumnNames) {
|
||||
$name = Piwik::translate('General_Metadata') . ': ' . $name;
|
||||
} else {
|
||||
$name = 'metadata_' . $name;
|
||||
}
|
||||
|
||||
$csvRow[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($csvRow as $name => $value) {
|
||||
$allColumns[$name] = true;
|
||||
}
|
||||
|
||||
if ($this->exportIdSubtable) {
|
||||
$idsubdatatable = $row->getIdSubDataTable();
|
||||
if ($idsubdatatable !== false
|
||||
&& $this->hideIdSubDatatable === false
|
||||
) {
|
||||
$csvRow['idsubdatatable'] = $idsubdatatable;
|
||||
}
|
||||
}
|
||||
|
||||
$csv[] = $csvRow;
|
||||
}
|
||||
$csv = $this->makeArrayFromDataTable($table, $allColumns);
|
||||
|
||||
// now we make sure that all the rows in the CSV array have all the columns
|
||||
foreach ($csv as &$row) {
|
||||
|
|
@ -251,31 +203,7 @@ class Csv extends Renderer
|
|||
}
|
||||
}
|
||||
|
||||
$str = '';
|
||||
|
||||
// specific case, we have only one column and this column wasn't named properly (indexed by a number)
|
||||
// we don't print anything in the CSV file => an empty line
|
||||
if (sizeof($allColumns) == 1
|
||||
&& reset($allColumns)
|
||||
&& !is_string(key($allColumns))
|
||||
) {
|
||||
$str .= '';
|
||||
} else {
|
||||
// render row names
|
||||
$str .= $this->getHeaderLine(array_keys($allColumns)) . $this->lineEnd;
|
||||
}
|
||||
|
||||
// we render the CSV
|
||||
foreach ($csv as $theRow) {
|
||||
$rowStr = '';
|
||||
foreach ($allColumns as $columnName => $true) {
|
||||
$rowStr .= $this->formatValue($theRow[$columnName]) . $this->separator;
|
||||
}
|
||||
// remove the last separator
|
||||
$rowStr = substr_replace($rowStr, "", -strlen($this->separator));
|
||||
$str .= $rowStr . $this->lineEnd;
|
||||
}
|
||||
$str = substr($str, 0, -strlen($this->lineEnd));
|
||||
$str = $this->buildCsvString($allColumns, $csv);
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
|
@ -287,9 +215,20 @@ class Csv extends Renderer
|
|||
*/
|
||||
private function getHeaderLine($columnMetrics)
|
||||
{
|
||||
foreach ($columnMetrics as $index => $value) {
|
||||
if (in_array($value, $this->unsupportedColumns)) {
|
||||
unset($columnMetrics[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->translateColumnNames) {
|
||||
$columnMetrics = $this->translateColumnNames($columnMetrics);
|
||||
}
|
||||
|
||||
foreach ($columnMetrics as &$value) {
|
||||
$value = $this->formatValue($value);
|
||||
}
|
||||
|
||||
return implode($this->separator, $columnMetrics);
|
||||
}
|
||||
|
||||
|
|
@ -334,14 +273,15 @@ class Csv extends Renderer
|
|||
|
||||
$period = Common::getRequestVar('period', false);
|
||||
$date = Common::getRequestVar('date', false);
|
||||
if ($period || $date) // in test cases, there are no request params set
|
||||
{
|
||||
if ($period || $date) {
|
||||
// in test cases, there are no request params set
|
||||
|
||||
if ($period == 'range') {
|
||||
$period = new Range($period, $date);
|
||||
} else if (strpos($date, ',') !== false) {
|
||||
} elseif (strpos($date, ',') !== false) {
|
||||
$period = new Range('range', $date);
|
||||
} else {
|
||||
$period = Period::factory($period, Date::factory($date));
|
||||
$period = Period\Factory::build($period, Date::factory($date));
|
||||
}
|
||||
|
||||
$prettyDate = $period->getLocalizedLongString();
|
||||
|
|
@ -353,8 +293,7 @@ class Csv extends Renderer
|
|||
}
|
||||
|
||||
// silent fail otherwise unit tests fail
|
||||
@header('Content-Type: application/vnd.ms-excel');
|
||||
@header('Content-Disposition: attachment; filename="' . $fileName . '"');
|
||||
Common::sendHeader('Content-Disposition: attachment; filename="' . $fileName . '"', true);
|
||||
ProxyHttp::overrideCacheControlHeaders();
|
||||
}
|
||||
|
||||
|
|
@ -400,4 +339,119 @@ class Csv extends Renderer
|
|||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $allColumns
|
||||
* @param $csv
|
||||
* @return array
|
||||
*/
|
||||
private function buildCsvString($allColumns, $csv)
|
||||
{
|
||||
$str = '';
|
||||
|
||||
// specific case, we have only one column and this column wasn't named properly (indexed by a number)
|
||||
// we don't print anything in the CSV file => an empty line
|
||||
if (sizeof($allColumns) == 1
|
||||
&& reset($allColumns)
|
||||
&& !is_string(key($allColumns))
|
||||
) {
|
||||
$str .= '';
|
||||
} else {
|
||||
// render row names
|
||||
$str .= $this->getHeaderLine(array_keys($allColumns)) . $this->lineEnd;
|
||||
}
|
||||
|
||||
// we render the CSV
|
||||
foreach ($csv as $theRow) {
|
||||
$rowStr = '';
|
||||
foreach ($allColumns as $columnName => $true) {
|
||||
$rowStr .= $this->formatValue($theRow[$columnName]) . $this->separator;
|
||||
}
|
||||
// remove the last separator
|
||||
$rowStr = substr_replace($rowStr, "", -strlen($this->separator));
|
||||
$str .= $rowStr . $this->lineEnd;
|
||||
}
|
||||
$str = substr($str, 0, -strlen($this->lineEnd));
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param $allColumns
|
||||
* @return array of csv data
|
||||
*/
|
||||
private function makeArrayFromDataTable($table, &$allColumns)
|
||||
{
|
||||
$csv = array();
|
||||
foreach ($table->getRows() as $row) {
|
||||
$csvRow = $this->flattenColumnArray($row->getColumns());
|
||||
|
||||
if ($this->exportMetadata) {
|
||||
$metadata = $row->getMetadata();
|
||||
foreach ($metadata as $name => $value) {
|
||||
if ($name == 'idsubdatatable_in_db') {
|
||||
continue;
|
||||
}
|
||||
//if a metadata and a column have the same name make sure they dont overwrite
|
||||
if ($this->translateColumnNames) {
|
||||
$name = Piwik::translate('General_Metadata') . ': ' . $name;
|
||||
} else {
|
||||
$name = 'metadata_' . $name;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
if (!in_array($name, $this->unsupportedColumns)) {
|
||||
$this->unsupportedColumns[] = $name;
|
||||
}
|
||||
} else {
|
||||
$csvRow[$name] = $value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($csvRow as $name => $value) {
|
||||
if (in_array($name, $this->unsupportedColumns)) {
|
||||
unset($allColumns[$name]);
|
||||
} else {
|
||||
$allColumns[$name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->exportIdSubtable) {
|
||||
$idsubdatatable = $row->getIdSubDataTable();
|
||||
if ($idsubdatatable !== false
|
||||
&& $this->hideIdSubDatatable === false
|
||||
) {
|
||||
$csvRow['idsubdatatable'] = $idsubdatatable;
|
||||
}
|
||||
}
|
||||
|
||||
$csv[] = $csvRow;
|
||||
}
|
||||
|
||||
if (!empty($this->unsupportedColumns)) {
|
||||
foreach ($this->unsupportedColumns as $unsupportedColumn) {
|
||||
foreach ($csv as $index => $row) {
|
||||
unset($row[$index][$unsupportedColumn]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
private function convertToUnicode($str)
|
||||
{
|
||||
if ($this->convertToUnicode
|
||||
&& function_exists('mb_convert_encoding')
|
||||
) {
|
||||
$str = chr(255) . chr(254) . mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -29,27 +29,18 @@ class Html extends Renderer
|
|||
*
|
||||
* @param string $id
|
||||
*/
|
||||
function setTableId($id)
|
||||
public function setTableId($id)
|
||||
{
|
||||
$this->tableId = str_replace('.', '_', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output HTTP Content-Type header
|
||||
*/
|
||||
protected function renderHeader()
|
||||
{
|
||||
@header('Content-Type: text/html; charset=utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the dataTable output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function render()
|
||||
public function render()
|
||||
{
|
||||
$this->renderHeader();
|
||||
$this->tableStructure = array();
|
||||
$this->allColumns = array();
|
||||
$this->i = 0;
|
||||
|
|
@ -57,18 +48,6 @@ class Html extends Renderer
|
|||
return $this->renderTable($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function renderException()
|
||||
{
|
||||
$this->renderHeader();
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
return nl2br($exceptionMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the output for the given data table
|
||||
*
|
||||
|
|
@ -77,8 +56,9 @@ class Html extends Renderer
|
|||
*/
|
||||
protected function renderTable($table)
|
||||
{
|
||||
if (is_array($table)) // convert array to DataTable
|
||||
{
|
||||
if (is_array($table)) {
|
||||
// convert array to DataTable
|
||||
|
||||
$table = DataTable::makeFromSimpleArray($table);
|
||||
}
|
||||
|
||||
|
|
@ -88,8 +68,9 @@ class Html extends Renderer
|
|||
$this->buildTableStructure($subtable, '_' . $table->getKeyName(), $date);
|
||||
}
|
||||
}
|
||||
} else // Simple
|
||||
{
|
||||
} else {
|
||||
// Simple
|
||||
|
||||
if ($table->getRowsCount()) {
|
||||
$this->buildTableStructure($table);
|
||||
}
|
||||
|
|
@ -134,7 +115,9 @@ class Html extends Renderer
|
|||
|
||||
$metadata = array();
|
||||
foreach ($row->getMetadata() as $name => $value) {
|
||||
if (is_string($value)) $value = "'$value'";
|
||||
if (is_string($value)) {
|
||||
$value = "'$value'";
|
||||
}
|
||||
$metadata[] = "'$name' => $value";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -11,7 +11,6 @@ namespace Piwik\DataTable\Renderer;
|
|||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\ProxyHttp;
|
||||
|
||||
/**
|
||||
* JSON export.
|
||||
|
|
@ -27,27 +26,9 @@ class Json extends Renderer
|
|||
*/
|
||||
public function render()
|
||||
{
|
||||
$this->renderHeader();
|
||||
return $this->renderTable($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function renderException()
|
||||
{
|
||||
$this->renderHeader();
|
||||
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
$exceptionMessage = str_replace(array("\r\n", "\n"), "", $exceptionMessage);
|
||||
|
||||
$result = json_encode(array('result' => 'error', 'message' => $exceptionMessage));
|
||||
|
||||
return $this->jsonpWrap($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the output for the given data table
|
||||
*
|
||||
|
|
@ -73,7 +54,6 @@ class Json extends Renderer
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$array = $this->convertDataTableToArray($table);
|
||||
}
|
||||
|
|
@ -90,40 +70,15 @@ class Json extends Renderer
|
|||
};
|
||||
array_walk_recursive($array, $callback);
|
||||
|
||||
$str = json_encode($array);
|
||||
|
||||
return $this->jsonpWrap($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
protected function jsonpWrap($str)
|
||||
{
|
||||
if (($jsonCallback = Common::getRequestVar('callback', false)) === false)
|
||||
$jsonCallback = Common::getRequestVar('jsoncallback', false);
|
||||
if ($jsonCallback !== false) {
|
||||
if (preg_match('/^[0-9a-zA-Z_.]*$/D', $jsonCallback) > 0) {
|
||||
$str = $jsonCallback . "(" . $str . ")";
|
||||
}
|
||||
}
|
||||
// silence "Warning: json_encode(): Invalid UTF-8 sequence in argument"
|
||||
$str = @json_encode($array);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the http header for json file
|
||||
*/
|
||||
protected function renderHeader()
|
||||
{
|
||||
self::sendHeaderJSON();
|
||||
ProxyHttp::overrideCacheControlHeaders();
|
||||
}
|
||||
|
||||
public static function sendHeaderJSON()
|
||||
{
|
||||
@header('Content-Type: application/json; charset=utf-8');
|
||||
Common::sendHeader('Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
|
||||
private function convertDataTableToArray($table)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,7 +9,6 @@
|
|||
namespace Piwik\DataTable\Renderer;
|
||||
|
||||
use Exception;
|
||||
use Piwik\DataTable\Manager;
|
||||
use Piwik\DataTable\Renderer;
|
||||
use Piwik\DataTable\Simple;
|
||||
use Piwik\DataTable;
|
||||
|
|
@ -71,8 +70,6 @@ class Php extends Renderer
|
|||
*/
|
||||
public function render($dataTable = null)
|
||||
{
|
||||
$this->renderHeader();
|
||||
|
||||
if (is_null($dataTable)) {
|
||||
$dataTable = $this->table;
|
||||
}
|
||||
|
|
@ -87,26 +84,6 @@ class Php extends Renderer
|
|||
return $toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderException()
|
||||
{
|
||||
$this->renderHeader();
|
||||
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
|
||||
$return = array('result' => 'error', 'message' => $exceptionMessage);
|
||||
|
||||
if ($this->serialize) {
|
||||
$return = serialize($return);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a flat php array from the DataTable, putting "columns" and "metadata" on the same level.
|
||||
*
|
||||
|
|
@ -133,7 +110,7 @@ class Php extends Renderer
|
|||
if (self::shouldWrapArrayBeforeRendering($flatArray)) {
|
||||
$flatArray = array($flatArray);
|
||||
}
|
||||
} else if ($dataTable instanceof DataTable\Map) {
|
||||
} elseif ($dataTable instanceof DataTable\Map) {
|
||||
$flatArray = array();
|
||||
foreach ($dataTable->getDataTables() as $keyName => $table) {
|
||||
$serializeSave = $this->serialize;
|
||||
|
|
@ -141,7 +118,7 @@ class Php extends Renderer
|
|||
$flatArray[$keyName] = $this->flatRender($table);
|
||||
$this->serialize = $serializeSave;
|
||||
}
|
||||
} else if ($dataTable instanceof Simple) {
|
||||
} elseif ($dataTable instanceof Simple) {
|
||||
$flatArray = $this->renderSimpleTable($dataTable);
|
||||
|
||||
// if we return only one numeric value then we print out the result in a simple <result> tag
|
||||
|
|
@ -228,10 +205,11 @@ class Php extends Renderer
|
|||
$newRow['issummaryrow'] = true;
|
||||
}
|
||||
|
||||
$subTable = $row->getSubtable();
|
||||
if ($this->isRenderSubtables()
|
||||
&& $row->isSubtableLoaded()
|
||||
&& $subTable
|
||||
) {
|
||||
$subTable = $this->renderTable(Manager::getInstance()->getTable($row->getIdSubDataTable()));
|
||||
$subTable = $this->renderTable($subTable);
|
||||
$newRow['subtable'] = $subTable;
|
||||
if ($this->hideIdSubDatatable === false
|
||||
&& isset($newRow['metadata']['idsubdatatable_in_db'])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,7 +15,6 @@ use Piwik\DataTable\Renderer;
|
|||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\SettingsPiwik;
|
||||
use Piwik\Url;
|
||||
|
||||
/**
|
||||
* RSS Feed.
|
||||
|
|
@ -30,24 +29,11 @@ class Rss extends Renderer
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
function render()
|
||||
public function render()
|
||||
{
|
||||
$this->renderHeader();
|
||||
return $this->renderTable($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function renderException()
|
||||
{
|
||||
header('Content-type: text/plain');
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
return 'Error: ' . $exceptionMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the output for the given data table
|
||||
*
|
||||
|
|
@ -101,14 +87,6 @@ class Rss extends Renderer
|
|||
return $header . $out . $footer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the xml file http header
|
||||
*/
|
||||
protected function renderHeader()
|
||||
{
|
||||
@header('Content-Type: text/xml; charset=utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RSS file footer
|
||||
*
|
||||
|
|
@ -185,7 +163,6 @@ class Rss extends Renderer
|
|||
}
|
||||
}
|
||||
$html .= "\n</tr>";
|
||||
$colspan = count($allColumns);
|
||||
|
||||
foreach ($tableStructure as $row) {
|
||||
$html .= "\n\n<tr>";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,7 +8,6 @@
|
|||
*/
|
||||
namespace Piwik\DataTable\Renderer;
|
||||
|
||||
|
||||
/**
|
||||
* TSV export
|
||||
*
|
||||
|
|
@ -21,7 +20,7 @@ class Tsv extends Csv
|
|||
/**
|
||||
* Constructor
|
||||
*/
|
||||
function __construct()
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setSeparator("\t");
|
||||
|
|
@ -32,7 +31,7 @@ class Tsv extends Csv
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
function render()
|
||||
public function render()
|
||||
{
|
||||
return parent::render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -30,31 +30,11 @@ class Xml extends Renderer
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
function render()
|
||||
public function render()
|
||||
{
|
||||
$this->renderHeader();
|
||||
return '<?xml version="1.0" encoding="utf-8" ?>' . "\n" . $this->renderTable($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the exception output and returns the string/binary
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function renderException()
|
||||
{
|
||||
$this->renderHeader();
|
||||
|
||||
$exceptionMessage = $this->getExceptionMessage();
|
||||
|
||||
$return = '<?xml version="1.0" encoding="utf-8" ?>' . "\n" .
|
||||
"<result>\n" .
|
||||
"\t<error message=\"" . $exceptionMessage . "\" />\n" .
|
||||
"</result>";
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given data table to an array
|
||||
*
|
||||
|
|
@ -174,17 +154,16 @@ class Xml extends Renderer
|
|||
foreach ($array as $key => $value) {
|
||||
// based on the type of array & the key, determine how this node will look
|
||||
if ($isAssociativeArray) {
|
||||
$keyIsInvalidXmlElement = is_numeric($key) || is_numeric($key[0]);
|
||||
if ($keyIsInvalidXmlElement) {
|
||||
$prefix = "<row key=\"$key\">";
|
||||
$suffix = "</row>";
|
||||
$emptyNode = "<row key=\"$key\"/>";
|
||||
} else if (strpos($key, '=') !== false) {
|
||||
if (strpos($key, '=') !== false) {
|
||||
list($keyAttributeName, $key) = explode('=', $key, 2);
|
||||
|
||||
$prefix = "<row $keyAttributeName=\"$key\">";
|
||||
$suffix = "</row>";
|
||||
$emptyNode = "<row $keyAttributeName=\"$key\">";
|
||||
} elseif (!self::isValidXmlTagName($key)) {
|
||||
$prefix = "<row key=\"$key\">";
|
||||
$suffix = "</row>";
|
||||
$emptyNode = "<row key=\"$key\"/>";
|
||||
} else {
|
||||
$prefix = "<$key>";
|
||||
$suffix = "</$key>";
|
||||
|
|
@ -201,7 +180,7 @@ class Xml extends Renderer
|
|||
$result .= $prefixLines . $prefix . "\n";
|
||||
$result .= $this->renderArray($value, $prefixLines . "\t");
|
||||
$result .= $prefixLines . $suffix . "\n";
|
||||
} else if ($value instanceof DataTable
|
||||
} elseif ($value instanceof DataTable
|
||||
|| $value instanceof Map
|
||||
) {
|
||||
if ($value->getRowsCount() == 0) {
|
||||
|
|
@ -210,7 +189,7 @@ class Xml extends Renderer
|
|||
$result .= $prefixLines . $prefix . "\n";
|
||||
if ($value instanceof Map) {
|
||||
$result .= $this->renderDataTableMap($value, $this->getArrayFromDataTable($value), $prefixLines);
|
||||
} else if ($value instanceof Simple) {
|
||||
} elseif ($value instanceof Simple) {
|
||||
$result .= $this->renderDataTableSimple($this->getArrayFromDataTable($value), $prefixLines);
|
||||
} else {
|
||||
$result .= $this->renderDataTable($this->getArrayFromDataTable($value), $prefixLines);
|
||||
|
|
@ -358,6 +337,8 @@ class Xml extends Renderer
|
|||
*/
|
||||
protected function renderDataTable($array, $prefixLine = "")
|
||||
{
|
||||
$columnsHaveInvalidChars = $this->areTableLabelsInvalidXmlTagNames(reset($array));
|
||||
|
||||
$out = '';
|
||||
foreach ($array as $rowId => $row) {
|
||||
if (!is_array($row)) {
|
||||
|
|
@ -370,10 +351,9 @@ class Xml extends Renderer
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Handing case idgoal=7, creating a new array for that one
|
||||
$rowAttribute = '';
|
||||
if (($equalFound = strstr($rowId, '=')) !== false) {
|
||||
if (strstr($rowId, '=') !== false) {
|
||||
$rowAttribute = explode('=', $rowId);
|
||||
$rowAttribute = " " . $rowAttribute[0] . "='" . $rowAttribute[1] . "'";
|
||||
}
|
||||
|
|
@ -394,10 +374,13 @@ class Xml extends Renderer
|
|||
} else {
|
||||
$value = self::formatValueXml($value);
|
||||
}
|
||||
|
||||
list($tagStart, $tagEnd) = $this->getTagStartAndEndFor($name, $columnsHaveInvalidChars);
|
||||
|
||||
if (strlen($value) == 0) {
|
||||
$out .= $prefixLine . "\t\t<$name />\n";
|
||||
$out .= $prefixLine . "\t\t<$tagStart />\n";
|
||||
} else {
|
||||
$out .= $prefixLine . "\t\t<$name>" . $value . "</$name>\n";
|
||||
$out .= $prefixLine . "\t\t<$tagStart>" . $value . "</$tagEnd>\n";
|
||||
}
|
||||
}
|
||||
$out .= "\t";
|
||||
|
|
@ -420,24 +403,62 @@ class Xml extends Renderer
|
|||
$array = array('value' => $array);
|
||||
}
|
||||
|
||||
$columnsHaveInvalidChars = $this->areTableLabelsInvalidXmlTagNames($array);
|
||||
|
||||
$out = '';
|
||||
foreach ($array as $keyName => $value) {
|
||||
$xmlValue = self::formatValueXml($value);
|
||||
list($tagStart, $tagEnd) = $this->getTagStartAndEndFor($keyName, $columnsHaveInvalidChars);
|
||||
if (strlen($xmlValue) == 0) {
|
||||
$out .= $prefixLine . "\t<$keyName />\n";
|
||||
$out .= $prefixLine . "\t<$tagStart />\n";
|
||||
} else {
|
||||
$out .= $prefixLine . "\t<$keyName>" . $xmlValue . "</$keyName>\n";
|
||||
$out .= $prefixLine . "\t<$tagStart>" . $xmlValue . "</$tagEnd>\n";
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the XML headers
|
||||
* Returns true if a string is a valid XML tag name, false if otherwise.
|
||||
*
|
||||
* @param string $str
|
||||
* @return bool
|
||||
*/
|
||||
protected function renderHeader()
|
||||
private static function isValidXmlTagName($str)
|
||||
{
|
||||
// silent fail because otherwise it throws an exception in the unit tests
|
||||
@header('Content-Type: text/xml; charset=utf-8');
|
||||
static $validTagRegex = null;
|
||||
|
||||
if ($validTagRegex === null) {
|
||||
$invalidTagChars = "!\"#$%&'()*+,\\/;<=>?@[\\]\\\\^`{|}~";
|
||||
$invalidTagStartChars = $invalidTagChars . "\\-.0123456789";
|
||||
$validTagRegex = "/^[^" . $invalidTagStartChars . "][^" . $invalidTagChars . "]*$/";
|
||||
}
|
||||
|
||||
$result = preg_match($validTagRegex, $str);
|
||||
return !empty($result);
|
||||
}
|
||||
|
||||
private function areTableLabelsInvalidXmlTagNames($rowArray)
|
||||
{
|
||||
if (!empty($rowArray)) {
|
||||
foreach ($rowArray as $name => $value) {
|
||||
if (!self::isValidXmlTagName($name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getTagStartAndEndFor($keyName, $columnsHaveInvalidChars)
|
||||
{
|
||||
if ($columnsHaveInvalidChars) {
|
||||
$tagStart = "col name=\"" . self::formatValueXml($keyName) . "\"";
|
||||
$tagEnd = "col";
|
||||
} else {
|
||||
$tagStart = $tagEnd = $keyName;
|
||||
}
|
||||
|
||||
return array($tagStart, $tagEnd);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,18 +10,18 @@ namespace Piwik\DataTable;
|
|||
|
||||
use Exception;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Log;
|
||||
use Piwik\Metrics;
|
||||
|
||||
/**
|
||||
* This is what a {@link Piwik\DataTable} is composed of.
|
||||
*
|
||||
*
|
||||
* DataTable rows contain columns, metadata and a subtable ID. Columns and metadata
|
||||
* are stored as an array of name => value mappings.
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Row
|
||||
class Row implements \ArrayAccess, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* List of columns that cannot be summed. An associative array for speed.
|
||||
|
|
@ -30,27 +30,21 @@ class Row
|
|||
*/
|
||||
private static $unsummableColumns = array(
|
||||
'label' => true,
|
||||
'full_url' => true // column used w/ old Piwik versions
|
||||
'full_url' => true // column used w/ old Piwik versions,
|
||||
);
|
||||
|
||||
/**
|
||||
* This array contains the row information:
|
||||
* - array indexed by self::COLUMNS contains the columns, pairs of (column names, value)
|
||||
* - (optional) array indexed by self::METADATA contains the metadata, pairs of (metadata name, value)
|
||||
* - (optional) integer indexed by self::DATATABLE_ASSOCIATED contains the ID of the DataTable associated to this row.
|
||||
* This ID can be used to read the DataTable from the DataTable_Manager.
|
||||
*
|
||||
* @var array
|
||||
* @see constructor for more information
|
||||
* @ignore
|
||||
*/
|
||||
public $c = array();
|
||||
|
||||
private $subtableIdWasNegativeBeforeSerialize = false;
|
||||
|
||||
// @see sumRow - implementation detail
|
||||
public $maxVisitsSummed = 0;
|
||||
|
||||
private $columns = array();
|
||||
private $metadata = array();
|
||||
private $isSubtableLoaded = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public $subtableId = null;
|
||||
|
||||
const COLUMNS = 0;
|
||||
const METADATA = 1;
|
||||
const DATATABLE_ASSOCIATED = 3;
|
||||
|
|
@ -59,7 +53,7 @@ class Row
|
|||
* Constructor.
|
||||
*
|
||||
* @param array $row An array with the following structure:
|
||||
*
|
||||
*
|
||||
* array(
|
||||
* Row::COLUMNS => array('label' => 'Piwik',
|
||||
* 'column1' => 42,
|
||||
|
|
@ -72,51 +66,33 @@ class Row
|
|||
*/
|
||||
public function __construct($row = array())
|
||||
{
|
||||
$this->c[self::COLUMNS] = array();
|
||||
$this->c[self::METADATA] = array();
|
||||
$this->c[self::DATATABLE_ASSOCIATED] = null;
|
||||
|
||||
if (isset($row[self::COLUMNS])) {
|
||||
$this->c[self::COLUMNS] = $row[self::COLUMNS];
|
||||
$this->columns = $row[self::COLUMNS];
|
||||
}
|
||||
if (isset($row[self::METADATA])) {
|
||||
$this->c[self::METADATA] = $row[self::METADATA];
|
||||
$this->metadata = $row[self::METADATA];
|
||||
}
|
||||
if (isset($row[self::DATATABLE_ASSOCIATED])
|
||||
&& $row[self::DATATABLE_ASSOCIATED] instanceof DataTable
|
||||
) {
|
||||
$this->setSubtable($row[self::DATATABLE_ASSOCIATED]);
|
||||
if (isset($row[self::DATATABLE_ASSOCIATED])) {
|
||||
if ($row[self::DATATABLE_ASSOCIATED] instanceof DataTable) {
|
||||
$this->setSubtable($row[self::DATATABLE_ASSOCIATED]);
|
||||
} else {
|
||||
$this->subtableId = $row[self::DATATABLE_ASSOCIATED];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Because $this->c[self::DATATABLE_ASSOCIATED] is negative when the table is in memory,
|
||||
* we must prior to serialize() call, make sure the ID is saved as positive integer
|
||||
*
|
||||
* Only serialize the "c" member
|
||||
* Used when archiving to serialize the Row's properties.
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function __sleep()
|
||||
public function export()
|
||||
{
|
||||
if (!empty($this->c[self::DATATABLE_ASSOCIATED])
|
||||
&& $this->c[self::DATATABLE_ASSOCIATED] < 0
|
||||
) {
|
||||
$this->c[self::DATATABLE_ASSOCIATED] = -1 * $this->c[self::DATATABLE_ASSOCIATED];
|
||||
$this->subtableIdWasNegativeBeforeSerialize = true;
|
||||
}
|
||||
return array('c');
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called after the row was serialized and __sleep was called.
|
||||
* @ignore
|
||||
*/
|
||||
public function cleanPostSerialize()
|
||||
{
|
||||
if ($this->subtableIdWasNegativeBeforeSerialize) {
|
||||
$this->c[self::DATATABLE_ASSOCIATED] = -1 * $this->c[self::DATATABLE_ASSOCIATED];
|
||||
$this->subtableIdWasNegativeBeforeSerialize = false;
|
||||
}
|
||||
return array(
|
||||
self::COLUMNS => $this->columns,
|
||||
self::METADATA => $this->metadata,
|
||||
self::DATATABLE_ASSOCIATED => $this->subtableId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -125,9 +101,10 @@ class Row
|
|||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->isSubtableLoaded()) {
|
||||
Manager::getInstance()->deleteTable($this->getIdSubDataTable());
|
||||
$this->c[self::DATATABLE_ASSOCIATED] = null;
|
||||
if ($this->isSubtableLoaded) {
|
||||
Manager::getInstance()->deleteTable($this->subtableId);
|
||||
$this->subtableId = null;
|
||||
$this->isSubtableLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,15 +118,21 @@ class Row
|
|||
{
|
||||
$columns = array();
|
||||
foreach ($this->getColumns() as $column => $value) {
|
||||
if (is_string($value)) $value = "'$value'";
|
||||
elseif (is_array($value)) $value = var_export($value, true);
|
||||
if (is_string($value)) {
|
||||
$value = "'$value'";
|
||||
} elseif (is_array($value)) {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
$columns[] = "'$column' => $value";
|
||||
}
|
||||
$columns = implode(", ", $columns);
|
||||
$metadata = array();
|
||||
foreach ($this->getMetadata() as $name => $value) {
|
||||
if (is_string($value)) $value = "'$value'";
|
||||
elseif (is_array($value)) $value = var_export($value, true);
|
||||
if (is_string($value)) {
|
||||
$value = "'$value'";
|
||||
} elseif (is_array($value)) {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
$metadata[] = "'$name' => $value";
|
||||
}
|
||||
$metadata = implode(", ", $metadata);
|
||||
|
|
@ -165,10 +148,11 @@ class Row
|
|||
*/
|
||||
public function deleteColumn($name)
|
||||
{
|
||||
if (!array_key_exists($name, $this->c[self::COLUMNS])) {
|
||||
if (!array_key_exists($name, $this->columns)) {
|
||||
return false;
|
||||
}
|
||||
unset($this->c[self::COLUMNS][$name]);
|
||||
|
||||
unset($this->columns[$name]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -180,11 +164,12 @@ class Row
|
|||
*/
|
||||
public function renameColumn($oldName, $newName)
|
||||
{
|
||||
if (isset($this->c[self::COLUMNS][$oldName])) {
|
||||
$this->c[self::COLUMNS][$newName] = $this->c[self::COLUMNS][$oldName];
|
||||
if (isset($this->columns[$oldName])) {
|
||||
$this->columns[$newName] = $this->columns[$oldName];
|
||||
}
|
||||
// outside the if() since we want to delete nulled columns
|
||||
unset($this->c[self::COLUMNS][$oldName]);
|
||||
|
||||
// outside the if () since we want to delete nulled columns
|
||||
unset($this->columns[$oldName]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -195,10 +180,11 @@ class Row
|
|||
*/
|
||||
public function getColumn($name)
|
||||
{
|
||||
if (!isset($this->c[self::COLUMNS][$name])) {
|
||||
if (!isset($this->columns[$name])) {
|
||||
return false;
|
||||
}
|
||||
return $this->c[self::COLUMNS][$name];
|
||||
|
||||
return $this->columns[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -210,19 +196,31 @@ class Row
|
|||
public function getMetadata($name = null)
|
||||
{
|
||||
if (is_null($name)) {
|
||||
return $this->c[self::METADATA];
|
||||
return $this->metadata;
|
||||
}
|
||||
if (!isset($this->c[self::METADATA][$name])) {
|
||||
if (!isset($this->metadata[$name])) {
|
||||
return false;
|
||||
}
|
||||
return $this->c[self::METADATA][$name];
|
||||
return $this->metadata[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a column having the given name is already registered. The value will not be evaluated, it will
|
||||
* just check whether a column exists independent of its value.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumn($name)
|
||||
{
|
||||
return array_key_exists($name, $this->columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array containing all the columns.
|
||||
*
|
||||
* @return array Example:
|
||||
*
|
||||
*
|
||||
* array(
|
||||
* 'column1' => VALUE,
|
||||
* 'label' => 'www.php.net'
|
||||
|
|
@ -231,7 +229,7 @@ class Row
|
|||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->c[self::COLUMNS];
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,10 +240,7 @@ class Row
|
|||
*/
|
||||
public function getIdSubDataTable()
|
||||
{
|
||||
return !is_null($this->c[self::DATATABLE_ASSOCIATED])
|
||||
// abs() is to ensure we return a positive int, @see isSubtableLoaded()
|
||||
? abs($this->c[self::DATATABLE_ASSOCIATED])
|
||||
: null;
|
||||
return $this->subtableId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -255,48 +250,49 @@ class Row
|
|||
*/
|
||||
public function getSubtable()
|
||||
{
|
||||
if ($this->isSubtableLoaded()) {
|
||||
return Manager::getInstance()->getTable($this->getIdSubDataTable());
|
||||
if ($this->isSubtableLoaded) {
|
||||
try {
|
||||
return Manager::getInstance()->getTable($this->subtableId);
|
||||
} catch (TableNotFoundException $e) {
|
||||
// edge case
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $subtableId
|
||||
* @ignore
|
||||
*/
|
||||
public function setNonLoadedSubtableId($subtableId)
|
||||
{
|
||||
$this->subtableId = $subtableId;
|
||||
$this->isSubtableLoaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sums a DataTable to this row's subtable. If this row has no subtable a new
|
||||
* one is created.
|
||||
*
|
||||
*
|
||||
* See {@link Piwik\DataTable::addDataTable()} to learn how DataTables are summed.
|
||||
*
|
||||
*
|
||||
* @param DataTable $subTable Table to sum to this row's subtable.
|
||||
*/
|
||||
public function sumSubtable(DataTable $subTable)
|
||||
{
|
||||
if ($this->isSubtableLoaded()) {
|
||||
if ($this->isSubtableLoaded) {
|
||||
$thisSubTable = $this->getSubtable();
|
||||
} else {
|
||||
$this->warnIfSubtableAlreadyExists();
|
||||
|
||||
$thisSubTable = new DataTable();
|
||||
$this->addSubtable($thisSubTable);
|
||||
$this->setSubtable($thisSubTable);
|
||||
}
|
||||
$columnOps = $subTable->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME);
|
||||
$thisSubTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnOps);
|
||||
$thisSubTable->addDataTable($subTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a subtable to this row.
|
||||
*
|
||||
* @param DataTable $subTable DataTable to associate to this row.
|
||||
* @return DataTable Returns `$subTable`.
|
||||
* @throws Exception if a subtable already exists for this row.
|
||||
*/
|
||||
public function addSubtable(DataTable $subTable)
|
||||
{
|
||||
if (!is_null($this->c[self::DATATABLE_ASSOCIATED])) {
|
||||
throw new Exception("Adding a subtable to the row, but it already has a subtable associated.");
|
||||
}
|
||||
return $this->setSubtable($subTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a subtable to this row, overwriting the existing subtable,
|
||||
* if any.
|
||||
|
|
@ -306,9 +302,9 @@ class Row
|
|||
*/
|
||||
public function setSubtable(DataTable $subTable)
|
||||
{
|
||||
// Hacking -1 to ensure value is negative, so we know the table was loaded
|
||||
// @see isSubtableLoaded()
|
||||
$this->c[self::DATATABLE_ASSOCIATED] = -1 * $subTable->getId();
|
||||
$this->subtableId = $subTable->getId();
|
||||
$this->isSubtableLoaded = true;
|
||||
|
||||
return $subTable;
|
||||
}
|
||||
|
||||
|
|
@ -321,8 +317,7 @@ class Row
|
|||
{
|
||||
// self::DATATABLE_ASSOCIATED are set as negative values,
|
||||
// as a flag to signify that the subtable is loaded in memory
|
||||
return !is_null($this->c[self::DATATABLE_ASSOCIATED])
|
||||
&& $this->c[self::DATATABLE_ASSOCIATED] < 0;
|
||||
return $this->isSubtableLoaded;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -330,17 +325,18 @@ class Row
|
|||
*/
|
||||
public function removeSubtable()
|
||||
{
|
||||
$this->c[self::DATATABLE_ASSOCIATED] = null;
|
||||
$this->subtableId = null;
|
||||
$this->isSubtableLoaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all the columns at once. Overwrites **all** previously set columns.
|
||||
*
|
||||
* @param array eg, `array('label' => 'www.php.net', 'nb_visits' => 15894)`
|
||||
* @param array $columns eg, `array('label' => 'www.php.net', 'nb_visits' => 15894)`
|
||||
*/
|
||||
public function setColumns($columns)
|
||||
{
|
||||
$this->c[self::COLUMNS] = $columns;
|
||||
$this->columns = $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -351,7 +347,7 @@ class Row
|
|||
*/
|
||||
public function setColumn($name, $value)
|
||||
{
|
||||
$this->c[self::COLUMNS][$name] = $value;
|
||||
$this->columns[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -362,7 +358,7 @@ class Row
|
|||
*/
|
||||
public function setMetadata($name, $value)
|
||||
{
|
||||
$this->c[self::METADATA][$name] = $value;
|
||||
$this->metadata[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -374,13 +370,13 @@ class Row
|
|||
public function deleteMetadata($name = false)
|
||||
{
|
||||
if ($name === false) {
|
||||
$this->c[self::METADATA] = array();
|
||||
$this->metadata = array();
|
||||
return true;
|
||||
}
|
||||
if (!isset($this->c[self::METADATA][$name])) {
|
||||
if (!isset($this->metadata[$name])) {
|
||||
return false;
|
||||
}
|
||||
unset($this->c[self::METADATA][$name]);
|
||||
unset($this->metadata[$name]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -388,15 +384,15 @@ class Row
|
|||
* Add a new column to the row. If the column already exists, throws an exception.
|
||||
*
|
||||
* @param string $name name of the column to add.
|
||||
* @param mixed $value value of the column to set.
|
||||
* @param mixed $value value of the column to set or a PHP callable.
|
||||
* @throws Exception if the column already exists.
|
||||
*/
|
||||
public function addColumn($name, $value)
|
||||
{
|
||||
if (isset($this->c[self::COLUMNS][$name])) {
|
||||
if (isset($this->columns[$name])) {
|
||||
throw new Exception("Column $name already in the array!");
|
||||
}
|
||||
$this->c[self::COLUMNS][$name] = $value;
|
||||
$this->setColumn($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -429,51 +425,61 @@ class Row
|
|||
*/
|
||||
public function addMetadata($name, $value)
|
||||
{
|
||||
if (isset($this->c[self::METADATA][$name])) {
|
||||
if (isset($this->metadata[$name])) {
|
||||
throw new Exception("Metadata $name already in the array!");
|
||||
}
|
||||
$this->c[self::METADATA][$name] = $value;
|
||||
$this->setMetadata($name, $value);
|
||||
}
|
||||
|
||||
private function isSummableColumn($columnName)
|
||||
{
|
||||
return empty(self::$unsummableColumns[$columnName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sums the given `$rowToSum` columns values to the existing row column values.
|
||||
* Only the int or float values will be summed. Label columns will be ignored
|
||||
* even if they have a numeric value.
|
||||
*
|
||||
*
|
||||
* Columns in `$rowToSum` that don't exist in `$this` are added to `$this`.
|
||||
*
|
||||
* @param \Piwik\DataTable\Row $rowToSum The row to sum to this row.
|
||||
* @param bool $enableCopyMetadata Whether metadata should be copied or not.
|
||||
* @param array $aggregationOperations for columns that should not be summed, determine which
|
||||
* @param array|bool $aggregationOperations for columns that should not be summed, determine which
|
||||
* aggregation should be used (min, max). format:
|
||||
* `array('column name' => 'function name')`
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sumRow(Row $rowToSum, $enableCopyMetadata = true, $aggregationOperations = false)
|
||||
{
|
||||
foreach ($rowToSum->getColumns() as $columnToSumName => $columnToSumValue) {
|
||||
if (!isset(self::$unsummableColumns[$columnToSumName])) // make sure we can add this column
|
||||
{
|
||||
$thisColumnValue = $this->getColumn($columnToSumName);
|
||||
|
||||
$operation = (is_array($aggregationOperations) && isset($aggregationOperations[$columnToSumName]) ?
|
||||
strtolower($aggregationOperations[$columnToSumName]) : 'sum');
|
||||
|
||||
// max_actions is a core metric that is generated in ArchiveProcess_Day. Therefore, it can be
|
||||
// present in any data table and is not part of the $aggregationOperations mechanism.
|
||||
if ($columnToSumName == Metrics::INDEX_MAX_ACTIONS) {
|
||||
$operation = 'max';
|
||||
}
|
||||
if(empty($operation)) {
|
||||
throw new Exception("Unknown aggregation operation for column $columnToSumName.");
|
||||
}
|
||||
$newValue = $this->getColumnValuesMerged($operation, $thisColumnValue, $columnToSumValue);
|
||||
|
||||
$this->setColumn($columnToSumName, $newValue);
|
||||
if (!$this->isSummableColumn($columnToSumName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$thisColumnValue = $this->getColumn($columnToSumName);
|
||||
|
||||
$operation = 'sum';
|
||||
if (is_array($aggregationOperations) && isset($aggregationOperations[$columnToSumName])) {
|
||||
$operation = strtolower($aggregationOperations[$columnToSumName]);
|
||||
}
|
||||
|
||||
// max_actions is a core metric that is generated in ArchiveProcess_Day. Therefore, it can be
|
||||
// present in any data table and is not part of the $aggregationOperations mechanism.
|
||||
if ($columnToSumName == Metrics::INDEX_MAX_ACTIONS) {
|
||||
$operation = 'max';
|
||||
}
|
||||
if (empty($operation)) {
|
||||
throw new Exception("Unknown aggregation operation for column $columnToSumName.");
|
||||
}
|
||||
|
||||
$newValue = $this->getColumnValuesMerged($operation, $thisColumnValue, $columnToSumValue);
|
||||
|
||||
$this->setColumn($columnToSumName, $newValue);
|
||||
}
|
||||
|
||||
if ($enableCopyMetadata) {
|
||||
$this->sumRowMetadata($rowToSum);
|
||||
$this->sumRowMetadata($rowToSum, $aggregationOperations);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -491,7 +497,7 @@ class Row
|
|||
case 'min':
|
||||
if (!$thisColumnValue) {
|
||||
$newValue = $columnToSumValue;
|
||||
} else if (!$columnToSumValue) {
|
||||
} elseif (!$columnToSumValue) {
|
||||
$newValue = $thisColumnValue;
|
||||
} else {
|
||||
$newValue = min($thisColumnValue, $columnToSumValue);
|
||||
|
|
@ -500,6 +506,19 @@ class Row
|
|||
case 'sum':
|
||||
$newValue = $this->sumRowArray($thisColumnValue, $columnToSumValue);
|
||||
break;
|
||||
case 'uniquearraymerge':
|
||||
if (is_array($thisColumnValue) && is_array($columnToSumValue)) {
|
||||
foreach ($columnToSumValue as $columnSum) {
|
||||
if (!in_array($columnSum, $thisColumnValue)) {
|
||||
$thisColumnValue[] = $columnSum;
|
||||
}
|
||||
}
|
||||
} elseif (!is_array($thisColumnValue) && is_array($columnToSumValue)) {
|
||||
$thisColumnValue = $columnToSumValue;
|
||||
}
|
||||
|
||||
$newValue = $thisColumnValue;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown operation '$operation'.");
|
||||
}
|
||||
|
|
@ -508,23 +527,45 @@ class Row
|
|||
|
||||
/**
|
||||
* Sums the metadata in `$rowToSum` with the metadata in `$this` row.
|
||||
*
|
||||
*
|
||||
* @param Row $rowToSum
|
||||
* @param array $aggregationOperations
|
||||
*/
|
||||
public function sumRowMetadata($rowToSum)
|
||||
public function sumRowMetadata($rowToSum, $aggregationOperations = array())
|
||||
{
|
||||
if (!empty($rowToSum->c[self::METADATA])
|
||||
if (!empty($rowToSum->metadata)
|
||||
&& !$this->isSummaryRow()
|
||||
) {
|
||||
$aggregatedMetadata = array();
|
||||
|
||||
if (is_array($aggregationOperations)) {
|
||||
// we need to aggregate value before value is overwritten by maybe another row
|
||||
foreach ($aggregationOperations as $columnn => $operation) {
|
||||
$thisMetadata = $this->getMetadata($columnn);
|
||||
$sumMetadata = $rowToSum->getMetadata($columnn);
|
||||
|
||||
if ($thisMetadata === false && $sumMetadata === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aggregatedMetadata[$columnn] = $this->getColumnValuesMerged($operation, $thisMetadata, $sumMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// We shall update metadata, and keep the metadata with the _most visits or pageviews_, rather than first or last seen
|
||||
$visits = max($rowToSum->getColumn(Metrics::INDEX_PAGE_NB_HITS) || $rowToSum->getColumn(Metrics::INDEX_NB_VISITS),
|
||||
// Old format pre-1.2, @see also method doSumVisitsMetrics()
|
||||
$rowToSum->getColumn('nb_actions') || $rowToSum->getColumn('nb_visits'));
|
||||
if (($visits && $visits > $this->maxVisitsSummed)
|
||||
|| empty($this->c[self::METADATA])
|
||||
|| empty($this->metadata)
|
||||
) {
|
||||
$this->maxVisitsSummed = $visits;
|
||||
$this->c[self::METADATA] = $rowToSum->c[self::METADATA];
|
||||
$this->metadata = $rowToSum->metadata;
|
||||
}
|
||||
|
||||
foreach ($aggregatedMetadata as $column => $value) {
|
||||
// we need to make sure aggregated value is used, and not metadata from $rowToSum
|
||||
$this->setMetadata($column, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -532,7 +573,7 @@ class Row
|
|||
/**
|
||||
* Returns `true` if this row is the summary row, `false` if otherwise. This function
|
||||
* depends on the label of the row, and so, is not 100% accurate.
|
||||
*
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSummaryRow()
|
||||
|
|
@ -558,10 +599,15 @@ class Row
|
|||
return $thisColumnValue + $columnToSumValue;
|
||||
}
|
||||
|
||||
if ($columnToSumValue === false) {
|
||||
return $thisColumnValue;
|
||||
}
|
||||
|
||||
if ($thisColumnValue === false) {
|
||||
return $columnToSumValue;
|
||||
}
|
||||
|
||||
if (is_array($columnToSumValue)) {
|
||||
if ($thisColumnValue == false) {
|
||||
return $columnToSumValue;
|
||||
}
|
||||
$newValue = $thisColumnValue;
|
||||
foreach ($columnToSumValue as $arrayIndex => $arrayValue) {
|
||||
if (!isset($newValue[$arrayIndex])) {
|
||||
|
|
@ -572,16 +618,7 @@ class Row
|
|||
return $newValue;
|
||||
}
|
||||
|
||||
if (is_string($columnToSumValue)) {
|
||||
if ($thisColumnValue === false) {
|
||||
return $columnToSumValue;
|
||||
} else if ($columnToSumValue === false) {
|
||||
return $thisColumnValue;
|
||||
} else {
|
||||
throw new Exception("Trying to add two strings values in DataTable\Row::sumRowArray: "
|
||||
. "'$thisColumnValue' + '$columnToSumValue'");
|
||||
}
|
||||
}
|
||||
$this->warnWhenSummingTwoStrings($thisColumnValue, $columnToSumValue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -594,7 +631,7 @@ class Row
|
|||
* @return bool
|
||||
* @ignore
|
||||
*/
|
||||
static public function compareElements($elem1, $elem2)
|
||||
public static function compareElements($elem1, $elem2)
|
||||
{
|
||||
if (is_array($elem1)) {
|
||||
if (is_array($elem2)) {
|
||||
|
|
@ -602,11 +639,13 @@ class Row
|
|||
}
|
||||
return 1;
|
||||
}
|
||||
if (is_array($elem2))
|
||||
if (is_array($elem2)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((string)$elem1 === (string)$elem2)
|
||||
if ((string)$elem1 === (string)$elem2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((string)$elem1 > (string)$elem2) ? 1 : -1;
|
||||
}
|
||||
|
|
@ -615,17 +654,17 @@ class Row
|
|||
* Helper function that tests if two rows are equal.
|
||||
*
|
||||
* Two rows are equal if:
|
||||
*
|
||||
*
|
||||
* - they have exactly the same columns / metadata
|
||||
* - they have a subDataTable associated, then we check that both of them are the same.
|
||||
*
|
||||
*
|
||||
* Column order is not important.
|
||||
*
|
||||
* @param \Piwik\DataTable\Row $row1 first to compare
|
||||
* @param \Piwik\DataTable\Row $row2 second to compare
|
||||
* @return bool
|
||||
*/
|
||||
static public function isEqual(Row $row1, Row $row2)
|
||||
public static function isEqual(Row $row1, Row $row2)
|
||||
{
|
||||
//same columns
|
||||
$cols1 = $row1->getColumns();
|
||||
|
|
@ -662,4 +701,53 @@ class Row
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return $this->hasColumn($offset);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->getColumn($offset);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->setColumn($offset, $value);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->deleteColumn($offset);
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->columns);
|
||||
}
|
||||
|
||||
private function warnIfSubtableAlreadyExists()
|
||||
{
|
||||
if (!is_null($this->subtableId)) {
|
||||
Log::warning(
|
||||
"Row with label '%s' (columns = %s) has already a subtable id=%s but it was not loaded - overwriting the existing sub-table.",
|
||||
$this->getColumn('label'),
|
||||
implode(", ", $this->getColumns()),
|
||||
$this->getIdSubDataTable()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function warnWhenSummingTwoStrings($thisColumnValue, $columnToSumValue)
|
||||
{
|
||||
if (is_string($columnToSumValue)) {
|
||||
Log::warning(
|
||||
"Trying to add two strings in DataTable\Row::sumRowArray: %s + %s for row %s",
|
||||
$thisColumnValue,
|
||||
$columnToSumValue,
|
||||
$this->__toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,15 +8,14 @@
|
|||
*/
|
||||
namespace Piwik\DataTable\Row;
|
||||
|
||||
use Piwik\DataTable\Manager;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* A special row whose column values are the aggregate of the row's subtable.
|
||||
*
|
||||
*
|
||||
* This class creates sets its own columns to the sum of each row in the row's subtable.
|
||||
*
|
||||
*
|
||||
* Non-numeric columns are bypassed during summation and do not appear in this
|
||||
* rows columns.
|
||||
*
|
||||
|
|
@ -27,7 +26,7 @@ class DataTableSummaryRow extends Row
|
|||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param DataTable|null $subTable The subtable of this row. This parameter is mostly for
|
||||
* convenience. If set, its rows will be summed to this one,
|
||||
* but it will not be set as this row's subtable (so
|
||||
|
|
@ -35,9 +34,7 @@ class DataTableSummaryRow extends Row
|
|||
*/
|
||||
public function __construct($subTable = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if ($subTable !== null) {
|
||||
if (isset($subTable)) {
|
||||
$this->sumTable($subTable);
|
||||
}
|
||||
}
|
||||
|
|
@ -47,9 +44,8 @@ class DataTableSummaryRow extends Row
|
|||
*/
|
||||
public function recalculate()
|
||||
{
|
||||
$id = $this->getIdSubDataTable();
|
||||
if ($id !== null) {
|
||||
$subTable = Manager::getInstance()->getTable($id);
|
||||
$subTable = $this->getSubtable();
|
||||
if ($subTable) {
|
||||
$this->sumTable($subTable);
|
||||
}
|
||||
}
|
||||
|
|
@ -61,8 +57,17 @@ class DataTableSummaryRow extends Row
|
|||
*/
|
||||
private function sumTable($table)
|
||||
{
|
||||
foreach ($table->getRows() as $row) {
|
||||
$this->sumRow($row, $enableCopyMetadata = false, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME));
|
||||
$metadata = $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME);
|
||||
$enableCopyMetadata = false;
|
||||
|
||||
foreach ($table->getRowsWithoutSummaryRow() as $row) {
|
||||
$this->sumRow($row, $enableCopyMetadata, $metadata);
|
||||
}
|
||||
|
||||
$summaryRow = $table->getRowFromId(DataTable::ID_SUMMARY_ROW);
|
||||
|
||||
if ($summaryRow) {
|
||||
$this->sumRow($summaryRow, $enableCopyMetadata, $metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -12,7 +12,7 @@ use Piwik\DataTable;
|
|||
|
||||
/**
|
||||
* A {@link Piwik\DataTable} where every row has two columns: **label** and **value**.
|
||||
*
|
||||
*
|
||||
* Simple DataTables are only used to slightly alter the output of some renderers
|
||||
* (notably the XML renderer).
|
||||
*
|
||||
|
|
@ -25,7 +25,7 @@ class Simple extends DataTable
|
|||
* values.
|
||||
*
|
||||
* @param array $array Array containing the rows, eg,
|
||||
*
|
||||
*
|
||||
* array(
|
||||
* 'Label row 1' => $value1,
|
||||
* 'Label row 2' => $value2,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,8 +8,6 @@
|
|||
*/
|
||||
namespace Piwik\DataTable;
|
||||
|
||||
|
||||
class TableNotFoundException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue