Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/global.ini.php5
-rw-r--r--core/API/DocumentationGenerator.php3
-rw-r--r--core/API/ResponseBuilder.php14
-rw-r--r--core/Columns/Dimension.php21
-rw-r--r--core/DataTable.php8
-rw-r--r--core/DataTable/Filter/PivotByDimension.php453
-rw-r--r--core/DataTable/Renderer/Csv.php15
-rw-r--r--core/DataTable/Renderer/Xml.php65
-rw-r--r--core/EventDispatcher.php6
-rw-r--r--core/Metrics.php1
-rw-r--r--core/Plugin/ComponentFactory.php85
-rw-r--r--core/Plugin/Report.php79
-rw-r--r--core/Plugin/Segment.php10
-rw-r--r--core/ViewDataTable/Config.php34
-rw-r--r--plugins/CoreConsole/Commands/TestsSetupFixture.php3
-rw-r--r--plugins/CoreHome/CoreHome.php2
-rw-r--r--plugins/CoreHome/javascripts/dataTable.js31
-rw-r--r--plugins/CoreHome/lang/en.json4
-rw-r--r--plugins/CoreHome/templates/_dataTableFooter.twig5
-rw-r--r--tests/PHPUnit/Core/Columns/DimensionTest.php2
-rw-r--r--tests/PHPUnit/Core/DataTable/Filter/PivotByDimensionTest.php351
-rw-r--r--tests/PHPUnit/Core/DataTable/Renderer/CSVTest.php27
-rw-r--r--tests/PHPUnit/Core/DataTable/Renderer/XMLTest.php54
-rw-r--r--tests/PHPUnit/Core/Plugin/ComponentFactoryTest.php57
-rw-r--r--tests/PHPUnit/Impl/TestRequestCollection.php1
-rw-r--r--tests/PHPUnit/Integration/Core/ReportTest.php100
-rw-r--r--tests/PHPUnit/Integration/PivotByQueryParamTest.php167
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithDataTableMaps__Referrers.getKeywords_day.xml49
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithOtherQueryParams__Referrers.getKeywords_week.xml33
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithColumnLimiting__Referrers.getKeywords_week.xml33
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithCsvOutput__Referrers.getKeywords_week.csvbin0 -> 440 bytes
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithJsonOutput__Referrers.getKeywords_week.json1
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithReportWhoseSubtableIsSelf__Actions.getPageUrls_week.xml123
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySegmentCreatesCorrectPivotTable__Referrers.getKeywords_week.xml39
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTableWhenEntireHirearchyIsNotLoaded__Referrers.getKeywords_week.xml57
-rw-r--r--tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTable__Referrers.getKeywords_week.xml57
-rwxr-xr-xtests/PHPUnit/IntegrationTestCase.php7
37 files changed, 1969 insertions, 33 deletions
diff --git a/config/global.ini.php b/config/global.ini.php
index 43b0605b39..2ce4085ab3 100644
--- a/config/global.ini.php
+++ b/config/global.ini.php
@@ -472,6 +472,11 @@ enable_auto_update = 1
; If set to 0 it also disables the "sent plugin update emails" feature in general and the related setting in the UI.
enable_update_communication = 1
+; This controls whether the pivotBy query parameter can be used with any dimension or just subtable
+; dimensions. If set to 1, it will fetch a report with a segment for each row of the table being pivoted.
+; At present, this is very inefficient, so it is disabled by default.
+pivot_by_filter_enable_fetch_by_segment = 0
+
[Tracker]
; Piwik uses first party cookies by default. If set to 1,
; the visit ID cookie will be set on the Piwik server domain as well
diff --git a/core/API/DocumentationGenerator.php b/core/API/DocumentationGenerator.php
index 5e8b0d1e41..0662ff153e 100644
--- a/core/API/DocumentationGenerator.php
+++ b/core/API/DocumentationGenerator.php
@@ -190,6 +190,9 @@ class DocumentationGenerator
$aParameters['hideColumns'] = false;
$aParameters['showColumns'] = false;
$aParameters['filter_pattern_recursive'] = false;
+ $aParameters['pivotBy'] = false;
+ $aParameters['pivotByColumn'] = false;
+ $aParameters['pivotByColumnLimit'] = false;
$moduleName = Proxy::getInstance()->getModuleNameFromClassName($class);
$aParameters = array_merge(array('module' => 'API', 'method' => $moduleName . '.' . $methodName), $aParameters);
diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php
index f5af551917..17d5d488f7 100644
--- a/core/API/ResponseBuilder.php
+++ b/core/API/ResponseBuilder.php
@@ -14,6 +14,7 @@ use Piwik\API\DataTableManipulator\LabelFilter;
use Piwik\API\DataTableManipulator\ReportTotalsCalculator;
use Piwik\Common;
use Piwik\DataTable;
+use Piwik\DataTable\Filter\PivotByDimension;
use Piwik\DataTable\Renderer;
use Piwik\DataTable\DataTableInterface;
use Piwik\DataTable\Filter\ColumnDelete;
@@ -157,10 +158,21 @@ class ResponseBuilder
return Renderer::formatValueXml($message);
}
- protected function handleDataTable($datatable)
+ protected function handleDataTable(DataTableInterface $datatable)
{
$label = $this->getLabelFromRequest($this->request);
+ // handle pivot by dimension filter
+ $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $this->request);
+ if (!empty($pivotBy)) {
+ $reportId = $this->apiModule . '.' . $this->apiMethod;
+ $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $this->request);
+ $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $this->request);
+
+ $datatable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit,
+ PivotByDimension::isSegmentFetchingEnabledInConfig()));
+ }
+
// if requested, flatten nested tables
if (Common::getRequestVar('flat', '0', 'string', $this->request) == '1') {
$flattener = new Flattener($this->apiModule, $this->apiMethod, $this->request);
diff --git a/core/Columns/Dimension.php b/core/Columns/Dimension.php
index c2b39093e4..a6f8831a71 100644
--- a/core/Columns/Dimension.php
+++ b/core/Columns/Dimension.php
@@ -195,10 +195,29 @@ abstract class Dimension
* @return Dimension|null The created instance or null if there is no Dimension for
* $dimensionId or if the plugin that contains the Dimension is
* not loaded.
+ * @api
*/
public static function factory($dimensionId)
{
list($module, $dimension) = explode('.', $dimensionId);
return ComponentFactory::factory($module, $dimension, __CLASS__);
}
-}
+
+ /**
+ * Returns the name of the plugin that contains this Dimension.
+ *
+ * @return string
+ * @throws Exception if the Dimension is not located within a Plugin module.
+ * @api
+ */
+ public function getModule()
+ {
+ $id = $this->getId();
+ if (empty($id)) {
+ throw new Exception("Invalid dimension ID: '$id'.");
+ }
+
+ $parts = explode('.', $id);
+ return reset($parts);
+ }
+} \ No newline at end of file
diff --git a/core/DataTable.php b/core/DataTable.php
index 3049dbf966..5895e51f2a 100644
--- a/core/DataTable.php
+++ b/core/DataTable.php
@@ -1635,6 +1635,14 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
}
/**
+ * Unsets all queued filters.
+ */
+ public function clearQueuedFilters()
+ {
+ $this->queuedFilters = array();
+ }
+
+ /**
* @return \ArrayIterator|Row[]
*/
public function getIterator() {
diff --git a/core/DataTable/Filter/PivotByDimension.php b/core/DataTable/Filter/PivotByDimension.php
new file mode 100644
index 0000000000..1d0a7267f8
--- /dev/null
+++ b/core/DataTable/Filter/PivotByDimension.php
@@ -0,0 +1,453 @@
+<?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\Plugin\Report;
+use Piwik\Plugin\Segment;
+
+/**
+ * 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
+{
+ const DEFAULT_COLUMN_LIMIT = 7;
+
+ /**
+ * 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::DEFAULT_COLUMN_LIMIT;
+ $this->isFetchingBySegmentEnabled = $isFetchingBySegmentEnabled;
+
+ $namesToId = Metrics::getMappingFromIdToName();
+ $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);
+
+ // limit columns
+ if ($this->pivotByColumnLimit > 0) {
+ arsort($columnSet);
+ $columnSet = array_slice($columnSet, 0, $this->pivotByColumnLimit, $preserveKeys = true);
+ }
+
+ // sort columns by name (to ensure deterministic ordering)
+ ksort($columnSet);
+
+ // 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_merge(array('label' => false), $columnSet);
+
+ Log::debug("PivotByDimension::%s: processed pivoted columns: %s", __FUNCTION__, $columnSet);
+
+ // 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
+ $orderedColumns = $columnSet;
+ foreach ($row->getColumns() as $name => $value) {
+ if (isset($orderedColumns[$name])) {
+ $orderedColumns[$name] = $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.
+ }
+
+ /**
+ * 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 !empty($this->subtableDimension) && $this->subtableDimension->getId() == $this->pivotByDimension->getId();
+ }
+
+ private function loadSubtable(DataTable $table, Row $row)
+ {
+ $idSubtable = $row->getIdSubDataTable();
+ if ($idSubtable === null) {
+ return null;
+ }
+
+ if ($row->isSubtableLoaded()) {
+ $subtable = $row->getSubtable();
+ } else {
+ $subtable = $this->thisReport->fetchSubtable($idSubtable, $this->getRequestParamOverride($table));
+ }
+
+ if ($subtable === null) { // 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);
+
+ 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->subtableDimension->getId() !== $this->pivotByDimension->getId()) {
+ 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' => ''
+ );
+
+ $site = $table->getMetadata('site');
+ if (!empty($site)) {
+ $params['idSite'] = $site->getId();
+ }
+
+ $period = $table->getMetadata('period');
+ if (!empty($period)) {
+ $params['date'] = $period->getDateStart()->toString();
+ $params['period'] = $period->getLabel();
+ }
+
+ return $params;
+ }
+
+ /**
+ * 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)
+ {
+ $subtableDimension = $report->getSubtableDimension();
+ return !empty($subtableDimension) && $subtableDimension->getId() !== $report->getDimension()->getId();
+ }
+
+ /**
+ * 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'];
+ }
+} \ No newline at end of file
diff --git a/core/DataTable/Renderer/Csv.php b/core/DataTable/Renderer/Csv.php
index 064dbd6db1..25961ce8a9 100644
--- a/core/DataTable/Renderer/Csv.php
+++ b/core/DataTable/Renderer/Csv.php
@@ -278,6 +278,11 @@ class Csv extends Renderer
if ($this->translateColumnNames) {
$columnMetrics = $this->translateColumnNames($columnMetrics);
}
+
+ foreach ($columnMetrics as &$value) {
+ $value = $this->formatValue($value);
+ }
+
return implode($this->separator, $columnMetrics);
}
@@ -387,4 +392,14 @@ class Csv extends Renderer
return $name;
}
}
+
+ private function escapeCsvValue($value)
+ {
+ if (strpos($value, ',') !== false
+ || strpos($value, '"') !== false
+ ) {
+ return '"' . addslashes($value) . '"';
+ }
+ return $value;
+ }
}
diff --git a/core/DataTable/Renderer/Xml.php b/core/DataTable/Renderer/Xml.php
index 9eb7db914b..93c800fafa 100644
--- a/core/DataTable/Renderer/Xml.php
+++ b/core/DataTable/Renderer/Xml.php
@@ -154,8 +154,7 @@ 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) {
+ if (!self::isValidXmlTagName($key)) {
$prefix = "<row key=\"$key\">";
$suffix = "</row>";
$emptyNode = "<row key=\"$key\"/>";
@@ -338,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)) {
@@ -373,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";
@@ -399,15 +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;
}
-}
+
+ /**
+ * Returns true if a string is a valid XML tag name, false if otherwise.
+ *
+ * @param string $str
+ * @return bool
+ */
+ private static function isValidXmlTagName($str)
+ {
+ 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);
+ }
+} \ No newline at end of file
diff --git a/core/EventDispatcher.php b/core/EventDispatcher.php
index ce0815c6ba..d9d26b4377 100644
--- a/core/EventDispatcher.php
+++ b/core/EventDispatcher.php
@@ -201,12 +201,14 @@ class EventDispatcher extends Singleton
}
/**
- * TODO
+ * Returns the Plugin\Manager instance used by the event dispatcher.
+ *
+ * @return Plugin\Manager
*/
private function getPluginManager()
{
if ($this->pluginManager === null) {
- $this->pluginManager = \Piwik\Plugin\Manager::getInstance();
+ $this->pluginManager = Plugin\Manager::getInstance();
}
return $this->pluginManager;
}
diff --git a/core/Metrics.php b/core/Metrics.php
index c88bb13294..03b9e2e094 100644
--- a/core/Metrics.php
+++ b/core/Metrics.php
@@ -181,6 +181,7 @@ class Metrics
return $names;
}
+ // TODO: this method is named wrong
public static function getMappingFromIdToName()
{
$idToName = array_flip(self::$mappingFromIdToName);
diff --git a/core/Plugin/ComponentFactory.php b/core/Plugin/ComponentFactory.php
index 02cb6083e1..68415e9397 100644
--- a/core/Plugin/ComponentFactory.php
+++ b/core/Plugin/ComponentFactory.php
@@ -37,20 +37,14 @@ class ComponentFactory
public static function factory($pluginName, $componentClassSimpleName, $componentTypeClass)
{
if (empty($pluginName) || empty($componentClassSimpleName)) {
+ Log::debug("ComponentFactory::%s: empty plugin name or component simple name requested (%s, %s)",
+ __FUNCTION__, $pluginName, $componentClassSimpleName);
+
return null;
}
- $pluginManager = PluginManager::getInstance();
-
- try {
- if (!$pluginManager->isPluginActivated($pluginName)) {
- return null;
- }
-
- $plugin = $pluginManager->getLoadedPlugin($pluginName);
- } catch (Exception $e) {
- Log::debug($e);
-
+ $plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName);
+ if (empty($plugin)) {
return null;
}
@@ -63,6 +57,75 @@ class ComponentFactory
return new $class();
}
}
+
+ Log::debug("ComponentFactory::%s: Could not find requested component (args = ['%s', '%s', '%s']).",
+ __FUNCTION__, $pluginName, $componentClassSimpleName, $componentTypeClass);
+
+ return null;
+ }
+
+ /**
+ * Finds a component instance that satisfies a given predicate.
+ *
+ * @param string $componentTypeClass The fully qualified class name of the component type, eg,
+ * `"Piwik\Plugin\Report"`.
+ * @param string $pluginName|false The name of the plugin the component is expected to belong to,
+ * eg, `'UserSettings'`.
+ * @param callback $predicate
+ * @return mixed The component that satisfies $predicate or null if not found.
+ */
+ public static function getComponentIf($componentTypeClass, $pluginName, $predicate)
+ {
+ $pluginManager = PluginManager::getInstance();
+
+ // get components to search through
+ $subnamespace = $componentTypeClass::COMPONENT_SUBNAMESPACE;
+ if (empty($pluginName)) {
+ $components = $pluginManager->findMultipleComponents($subnamespace, $componentTypeClass);
+ } else {
+ $plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName);
+ if (empty($plugin)) {
+ return null;
+ }
+
+ $components = $plugin->findMultipleComponents($subnamespace, $componentTypeClass);
+ }
+
+ // find component that satisfieds predicate
+ foreach ($components as $class) {
+ $component = new $class();
+ if ($predicate($component)) {
+ return $component;
+ }
+ }
+
+ Log::debug("ComponentFactory::%s: Could not find component that satisfies predicate (args = ['%s', '%s', '%s']).",
+ __FUNCTION__, $componentTypeClass, $pluginName, get_class($predicate));
+
return null;
}
+
+ /**
+ * @param string $function
+ * @param string $pluginName
+ * @return null|\Piwik\Plugin
+ */
+ private static function getActivatedPlugin($function, $pluginName)
+ {
+ $pluginManager = PluginManager::getInstance();
+ try {
+ if (!$pluginManager->isPluginActivated($pluginName)) {
+ Log::debug("ComponentFactory::%s: component for deactivated plugin ('%s') requested.",
+ $function, $pluginName);
+
+ return null;
+ }
+
+ return $pluginManager->getLoadedPlugin($pluginName);
+ } catch (Exception $e) {
+ Log::debug($e);
+
+ return null;
+ }
+ }
} \ No newline at end of file
diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php
index d9ee86e5c3..a907b0f709 100644
--- a/core/Plugin/Report.php
+++ b/core/Plugin/Report.php
@@ -9,7 +9,10 @@
namespace Piwik\Plugin;
use Piwik\API\Proxy;
+use Piwik\API\Request;
use Piwik\Cache\LanguageAwareStaticCache;
+use Piwik\Columns\Dimension;
+use Piwik\DataTable;
use Piwik\Menu\MenuReporting;
use Piwik\Metrics;
use Piwik\Piwik;
@@ -576,6 +579,57 @@ class Report
}
/**
+ * Returns the Dimension instance of this report's subtable report.
+ *
+ * @return Dimension|null The subtable report's dimension or null if there is subtable report or
+ * no dimension for the subtable report.
+ * @api
+ */
+ public function getSubtableDimension()
+ {
+ if (empty($this->actionToLoadSubTables)) {
+ return null;
+ }
+
+ list($subtableReportModule, $subtableReportAction) = $this->getSubtableApiMethod();
+
+ $subtableReport = self::factory($subtableReportModule, $subtableReportAction);
+ if (empty($subtableReport)) {
+ return null;
+ }
+
+ return $subtableReport->getDimension();
+ }
+
+ /**
+ * Fetches the report represented by this instance.
+ *
+ * @param array $paramOverride Query parameter overrides.
+ * @return DataTable
+ * @api
+ */
+ public function fetch($paramOverride = array())
+ {
+ return Request::processRequest($this->module . '.' . $this->action, $paramOverride);
+ }
+
+ /**
+ * Fetches a subtable for the report represented by this instance.
+ *
+ * @param int $idSubtable The subtable ID.
+ * @param array $paramOverride Query parameter overrides.
+ * @return DataTable
+ * @api
+ */
+ public function fetchSubtable($idSubtable, $paramOverride = array())
+ {
+ $paramOverride = array('idSubtable' => $idSubtable) + $paramOverride;
+
+ list($module, $action) = $this->getSubtableApiMethod();
+ return Request::processRequest($module . '.' . $action, $paramOverride);
+ }
+
+ /**
* Get an instance of a specific report belonging to the given module and having the given action.
* @param string $module
* @param string $action
@@ -648,4 +702,29 @@ class Report
{
return 'menu' . ucfirst($this->action);
}
+
+ private function getSubtableApiMethod()
+ {
+ if (strpos($this->actionToLoadSubTables, '.') !== false) {
+ return explode('.', $this->actionToLoadSubTables);
+ } else {
+ return array($this->module, $this->actionToLoadSubTables);
+ }
+ }
+
+ /**
+ * Finds a top level report that provides stats for a specific Dimension.
+ *
+ * @param Dimension $dimension The dimension whose report we're looking for.
+ * @return Report|null The
+ * @api
+ */
+ public static function getForDimension(Dimension $dimension)
+ {
+ return ComponentFactory::getComponentIf(__CLASS__, $dimension->getModule(), function (Report $report) use ($dimension) {
+ return !$report->isSubtableReport
+ && $report->getDimension()
+ && $report->getDimension()->getId() == $dimension->getId();
+ });
+ }
}
diff --git a/core/Plugin/Segment.php b/core/Plugin/Segment.php
index cc3392eebc..795d4da157 100644
--- a/core/Plugin/Segment.php
+++ b/core/Plugin/Segment.php
@@ -187,6 +187,16 @@ class Segment
}
/**
+ * Returns the name of this segment as it should appear in segment expressions.
+ *
+ * @return string
+ */
+ public function getSegment()
+ {
+ return $this->segment;
+ }
+
+ /**
* Set callback which will be executed when user will call for suggested values for segment.
*
* @param callable $suggestedValuesCallback
diff --git a/core/ViewDataTable/Config.php b/core/ViewDataTable/Config.php
index 093f28b254..0389c213fb 100644
--- a/core/ViewDataTable/Config.php
+++ b/core/ViewDataTable/Config.php
@@ -9,7 +9,9 @@
namespace Piwik\ViewDataTable;
use Piwik\API\Request as ApiRequest;
+use Piwik\DataTable\Filter\PivotByDimension;
use Piwik\Metrics;
+use Piwik\Plugin\Report;
use Piwik\Plugins\API\API;
/**
@@ -83,7 +85,8 @@ class Config
* The list of ViewDataTable properties that are 'Client Side Properties'.
*/
public $clientSideProperties = array(
- 'show_limit_control'
+ 'show_limit_control',
+ 'pivot_by_dimension'
);
/**
@@ -93,6 +96,7 @@ class Config
'show_goals',
'show_exclude_low_population',
'show_flatten_table',
+ 'show_pivot_by_subtable',
'show_table',
'show_table_all_columns',
'show_footer',
@@ -185,6 +189,18 @@ class Config
public $show_flatten_table = true;
/**
+ * Whether to show the 'Pivot by subtable' option (visible in the popup that displays after clicking
+ * the 'cog' icon).
+ */
+ public $show_pivot_by_subtable;
+
+ /**
+ * The ID of the dimension to pivot by when the 'pivot by subtable' option is clicked. Defaults
+ * to the subtable dimension of the report being displayed.
+ */
+ public $pivot_by_dimension;
+
+ /**
* Controls whether the footer icon that allows users to switch to the 'normal' DataTable view
* is shown.
*/
@@ -447,6 +463,7 @@ class Config
$this->report_id = $controllerName . '.' . $controllerAction;
$this->loadDocumentation();
+ $this->setShouldShowPivotBySubtable();
}
/** Load documentation from the API */
@@ -635,4 +652,19 @@ class Config
$this->addTranslation($key, $translation);
}
}
+
+ private function setShouldShowPivotBySubtable()
+ {
+ $report = Report::factory($this->controllerName, $this->controllerAction);
+
+ if (empty($report)) {
+ $this->show_pivot_by_subtable = false;
+ $this->pivot_by_dimension = false;
+ } else {
+ $this->show_pivot_by_subtable = PivotByDimension::isPivotingReportBySubtableSupported($report);
+
+ $subtableDimension = $report->getSubtableDimension();
+ $this->pivot_by_dimension = $subtableDimension ? $subtableDimension->getId() : false;
+ }
+ }
}
diff --git a/plugins/CoreConsole/Commands/TestsSetupFixture.php b/plugins/CoreConsole/Commands/TestsSetupFixture.php
index 1d38f9f3bf..cc5481d7e4 100644
--- a/plugins/CoreConsole/Commands/TestsSetupFixture.php
+++ b/plugins/CoreConsole/Commands/TestsSetupFixture.php
@@ -134,7 +134,7 @@ class TestsSetupFixture extends ConsoleCommand
private function createSqlDump($sqlDumpPath, OutputInterface $output)
{
- $output->write("<info>Creating SQL dump...</info>");
+ $output->writeln("<info>Creating SQL dump...</info>");
$databaseConfig = Config::getInstance()->database;
$dbUser = $databaseConfig['username'];
@@ -143,6 +143,7 @@ class TestsSetupFixture extends ConsoleCommand
$dbName = $databaseConfig['dbname'];
$command = "mysqldump --user='$dbUser' --password='$dbPass' --host='$dbHost' '$dbName' > '$sqlDumpPath'";
+ $output->writeln("<info>Executing $command...</info>");
passthru($command);
$this->writeSuccessMessage($output, array("SQL dump created!"));
diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 64cead7bcd..61fd18d54b 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -238,5 +238,7 @@ class CoreHome extends \Piwik\Plugin
$translationKeys[] = 'General_Default';
$translationKeys[] = 'General_LoadingData';
$translationKeys[] = 'General_ErrorRequest';
+ $translationKeys[] = 'CoreHome_UndoPivotBySubtable';
+ $translationKeys[] = 'CoreHome_PivotBySubtable';
}
}
diff --git a/plugins/CoreHome/javascripts/dataTable.js b/plugins/CoreHome/javascripts/dataTable.js
index 4ba2607c14..11c1f79e2d 100644
--- a/plugins/CoreHome/javascripts/dataTable.js
+++ b/plugins/CoreHome/javascripts/dataTable.js
@@ -155,7 +155,8 @@ $.extend(DataTable.prototype, UIControl.prototype, {
'columns',
'flat',
'include_aggregate_rows',
- 'totalRows'
+ 'totalRows',
+ 'pivotBy'
];
for (var key = 0; key < filters.length; key++) {
@@ -1089,6 +1090,9 @@ $.extend(DataTable.prototype, UIControl.prototype, {
} else {
str += '&expanded=1';
}
+ if (self.param.pivotBy) {
+ str += '&pivotBy=' + self.param.pivotBy;
+ }
if (format == 'CSV' || format == 'TSV' || format == 'RSS') {
str += '&translateColumnNames=1&language=' + piwik.language;
}
@@ -1171,10 +1175,14 @@ $.extend(DataTable.prototype, UIControl.prototype, {
};
$('div.tableConfiguration', domElem).hover(open, close);
- var generateClickCallback = function (paramName, callbackAfterToggle) {
+ var generateClickCallback = function (paramName, callbackAfterToggle, setParamCallback) {
return function () {
close();
- self.param[paramName] = (1 - self.param[paramName]) + '';
+ if (setParamCallback) {
+ setParamCallback();
+ } else {
+ self.param[paramName] = (1 - self.param[paramName]) + '';
+ }
self.param.filter_offset = 0;
delete self.param.totalRows;
if (callbackAfterToggle) callbackAfterToggle();
@@ -1247,6 +1255,23 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
}));
+ // handle pivot by
+ $('.dataTablePivotBySubtable', domElem)
+ .each(function () {
+ if (self.param.pivotBy) {
+ $(this).html(getText('CoreHome_UndoPivotBySubtable', true));
+ } else {
+ $(this).html(getText('CoreHome_PivotBySubtable'));
+ }
+ })
+ .click(generateClickCallback('pivotBy', null, function () {
+ if (self.param.pivotBy) {
+ self.param.pivotBy = '';
+ } else {
+ self.param.pivotBy = self.props.pivot_by_dimension;
+ }
+ }));
+
// handle highlighted icon
if (iconHighlighted) {
icon.addClass('highlighted');
diff --git a/plugins/CoreHome/lang/en.json b/plugins/CoreHome/lang/en.json
index c976f2ad3a..71ed8e4d09 100644
--- a/plugins/CoreHome/lang/en.json
+++ b/plugins/CoreHome/lang/en.json
@@ -59,6 +59,8 @@
"ViewAllPiwikVideoTutorials": "View all Piwik Video Tutorials",
"WebAnalyticsReports": "Web Analytics Reports",
"YouAreUsingTheLatestVersion": "You are using the latest version of Piwik!",
- "ClickRowToExpandOrContract": "Click this row to expand or contract the subtable."
+ "ClickRowToExpandOrContract": "Click this row to expand or contract the subtable.",
+ "UndoPivotBySubtable": "This report has been pivoted %s Undo pivot",
+ "PivotBySubtable": "This report is not pivoted %s Pivot by subtable"
}
} \ No newline at end of file
diff --git a/plugins/CoreHome/templates/_dataTableFooter.twig b/plugins/CoreHome/templates/_dataTableFooter.twig
index 4c0a588487..0f673aa2a2 100644
--- a/plugins/CoreHome/templates/_dataTableFooter.twig
+++ b/plugins/CoreHome/templates/_dataTableFooter.twig
@@ -97,6 +97,11 @@
<div class="configItem dataTableExcludeLowPopulation"></div>
</li>
{% endif %}
+ {% if properties.show_pivot_by_subtable|default is not empty %}
+ <li>
+ <div class="configItem dataTablePivotBySubtable"></div>
+ </li>
+ {% endif %}
</ul>
</div>
{% if isPluginLoaded('Annotations') and not properties.hide_annotations_view %}
diff --git a/tests/PHPUnit/Core/Columns/DimensionTest.php b/tests/PHPUnit/Core/Columns/DimensionTest.php
index 477ce7114a..6952b3880f 100644
--- a/tests/PHPUnit/Core/Columns/DimensionTest.php
+++ b/tests/PHPUnit/Core/Columns/DimensionTest.php
@@ -171,5 +171,5 @@ namespace Piwik\Tests\Core\Columns
$dimension = Dimension::factory("ExampleTracker.ExampleDimension");
$this->assertInstanceOf("Piwik\\Plugins\\ExampleTracker\\Columns\\ExampleDimension", $dimension);
}
- }
+ }
} \ No newline at end of file
diff --git a/tests/PHPUnit/Core/DataTable/Filter/PivotByDimensionTest.php b/tests/PHPUnit/Core/DataTable/Filter/PivotByDimensionTest.php
new file mode 100644
index 0000000000..e20cdaf682
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/PivotByDimensionTest.php
@@ -0,0 +1,351 @@
+<?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\Tests\Core\DataTable\Filter;
+
+use Piwik\API\Proxy;
+use Piwik\DataTable;
+use Piwik\DataTable\Filter\PivotByDimension;
+use Piwik\DataTable\Row;
+use Piwik\Plugin\Manager as PluginManager;
+use PHPUnit_Framework_TestCase;
+use Exception;
+
+/**
+ * @group Core
+ * @group PivotByDimensionTest
+ */
+class PivotByDimensionTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * The number of segment tables that have been created. Used when injecting API results to make sure each
+ * segment table is different.
+ *
+ * @var int
+ */
+ private $segmentTableCount;
+
+ public function setUp()
+ {
+ $self = $this;
+
+ $proxyMock = $this->getMock('stdClass', array('call'));
+ $proxyMock->expects($this->any())->method('call')->willReturnCallback(function ($className, $methodName, $parameters) use ($self) {
+ if ($className == "\\Piwik\\Plugins\\UserCountry\\API"
+ && $methodName == 'getCity'
+ ) {
+ return $self->getSegmentTable();
+ } else {
+ throw new Exception("Unknown API request: $className::$methodName.");
+ }
+ });
+ Proxy::setSingletonInstance($proxyMock);
+
+ $this->segmentTableCount = 0;
+ }
+
+ public function tearDown()
+ {
+ PluginManager::unsetInstance();
+ Proxy::unsetInstance();
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage Unsupported pivot: report 'ExampleReport.ExampleReportName' has no subtable dimension.
+ */
+ public function testConstructionFailsWhenReportHasNoSubtableAndSegmentFetchingIsDisabled()
+ {
+ PluginManager::getInstance()->loadPlugins(array('ExampleReport', 'UserCountry'));
+
+ new PivotByDimension(new DataTable(), "ExampleReport.GetExampleReport", "UserCountry.City", 'nb_visits', $columnLimit = -1, $enableFetchBySegment = false);
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage Unsupported pivot: the subtable dimension for 'Referrers.Referrers_Keywords' does not match the requested pivotBy dimension.
+ */
+ public function testConstructionFailsWhenDimensionIsNotSubtableAndSegmentFetchingIsDisabled()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry'));
+
+ new PivotByDimension(new DataTable(), "Referrers.getKeywords", "UserCountry.City", "nb_visits", $columnLimit = -1, $enableFetchBySegment = false);
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage Unsupported pivot: No segment for dimension of report 'UserSettings.UserSettings_WidgetBrowserFamilies'
+ */
+ public function testConstructionFailsWhenDimensionIsNotSubtableAndSegmentFetchingIsEnabledButThereIsNoSegment()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserSettings'));
+
+ new PivotByDimension(new DataTable(), "UserSettings.getBrowserType", "Referrers.Keyword", "nb_visits");
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage Invalid dimension 'ExampleTracker.InvalidDimension'
+ */
+ public function testConstructionFailsWhenDimensionDoesNotExist()
+ {
+ PluginManager::getInstance()->loadPlugins(array('ExampleReport', 'ExampleTracker'));
+
+ new PivotByDimension(new DataTable(), "ExampleReport.GetExampleReport", "ExampleTracker.InvalidDimension", 'nb_visits');
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage Unsupported pivot: No report for pivot dimension 'ExampleTracker.ExampleDimension'
+ */
+ public function testConstructionFailsWhenThereIsNoReportForADimension()
+ {
+ PluginManager::getInstance()->loadPlugins(array('ExampleReport', 'ExampleTracker'));
+
+ new PivotByDimension(new DataTable(), "ExampleReport.GetExampleReport", "ExampleTracker.ExampleDimension", "nb_visits");
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage Unable to find report 'ExampleReport.InvalidReport'
+ */
+ public function testConstructionFailsWhenSpecifiedReportIsNotValid()
+ {
+ PluginManager::getInstance()->loadPlugins(array('ExampleReport', 'Referrers'));
+
+ new PivotByDimension(new DataTable(), "ExampleReport.InvalidReport", "Referrers.Keyword", "nb_visits");
+ }
+
+ public function testFilterReturnsEmptyResultWhenTableToFilterIsEmpty()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = new DataTable();
+
+ $pivotFilter = new PivotByDimension($table, "Referrers.getKeywords", "Referrers.SearchEngine", 'nb_visits');
+ $pivotFilter->filter($table);
+
+ $expectedRows = array();
+ $this->assertEquals($expectedRows, $table->getRows());
+ }
+
+ public function testFilterCorrectlyCreatesPivotTableUsingSubtableReport()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = $this->getTableToFilter(true);
+
+ $pivotFilter = new PivotByDimension($table, "Referrers.getKeywords", "Referrers.SearchEngine", 'nb_actions', $columnLimit = -1, $fetchBySegment = false);
+ $pivotFilter->filter($table);
+
+ $expectedRows = array(
+ array('label' => 'row 1', 'col 1' => 2, 'col 2' => false, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 2', 'col 1' => 4, 'col 2' => 6, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 3', 'col 1' => false, 'col 2' => 8, 'col 3' => 31, 'col 4' => 33)
+ );
+ $this->assertTableRowsEquals($expectedRows, $table);
+ }
+
+ public function testFilterCorrectlyCreatesPivotTableUsingSegment()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = $this->getTableToFilter(true);
+
+ $pivotFilter = new PivotByDimension($table, "Referrers.getKeywords", "UserCountry.City", 'nb_visits');
+ $pivotFilter->filter($table);
+
+ $expectedRows = array(
+ array('label' => 'row 1', 'col 0' => 2, 'col 1' => false, 'col 2' => false),
+ array('label' => 'row 2', 'col 0' => 2, 'col 1' => 4, 'col 2' => false),
+ array('label' => 'row 3', 'col 0' => 2, 'col 1' => 4, 'col 2' => 6)
+ );
+ $this->assertTableRowsEquals($expectedRows, $table);
+ }
+
+ public function testFilterCorrectlyCreatesPivotTableWhenPivotMetricDoesNotExistInTable()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = $this->getTableToFilter(true);
+
+ $pivotFilter = new PivotByDimension($table, "Referrers.getKeywords", "Referrers.SearchEngine", 'invalid_metric');
+ $pivotFilter->filter($table);
+
+ $expectedRows = array(
+ array('label' => 'row 1', 'col 1' => false, 'col 2' => false, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 2', 'col 1' => false, 'col 2' => false, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 3', 'col 1' => false, 'col 2' => false, 'col 3' => false, 'col 4' => false)
+ );
+ $this->assertTableRowsEquals($expectedRows, $table);
+ }
+
+ public function testFilterCorrectlyCreatesPivotTableWhenSubtablesHaveNoRows()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = $this->getTableToFilter(false);
+
+ $pivotFilter = new PivotByDimension($table, "CustomVariables.getCustomVariables", "CustomVariables.CustomVariableValue",
+ 'nb_visits', $fetchBySegment = false);
+ $pivotFilter->filter($table);
+
+ $expectedRows = array(
+ array('label' => 'row 1'),
+ array('label' => 'row 2'),
+ array('label' => 'row 3')
+ );
+ $this->assertTableRowsEquals($expectedRows, $table);
+ }
+
+ public function testFilterCorrectlyDefaultsPivotByColumnWhenNoneProvided()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = $this->getTableToFilter(true);
+
+ $pivotFilter = new PivotByDimension($table, "Referrers.getKeywords", "Referrers.SearchEngine", $column = false, $columnLimit = -1, $fetchBySegment = false);
+ $pivotFilter->filter($table);
+
+ $expectedRows = array(
+ array('label' => 'row 1', 'col 1' => 1, 'col 2' => false, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 2', 'col 1' => 3, 'col 2' => 5, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 3', 'col 1' => false, 'col 2' => 7, 'col 3' => 9, 'col 4' => 32)
+ );
+ $this->assertTableRowsEquals($expectedRows, $table);
+ }
+
+ public function testFilterCorrectlyLimitsTheColumnNumberWhenColumnLimitProvided()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers', 'UserCountry', 'CustomVariables'));
+
+ $table = $this->getTableToFilter(true);
+
+ $pivotFilter = new PivotByDimension($table, "Referrers.getKeywords", "Referrers.SearchEngine", $column = 'nb_visits', $columnLimit = 3, $fetchBySegment = false);
+ $pivotFilter->filter($table);
+
+ $expectedRows = array(
+ array('label' => 'row 1', 'col 2' => false, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 2', 'col 2' => 5, 'col 3' => false, 'col 4' => false),
+ array('label' => 'row 3', 'col 2' => 7, 'col 3' => 9, 'col 4' => 32)
+ );
+ $this->assertTableRowsEquals($expectedRows, $table);
+ }
+
+ private function getTableToFilter($addSubtables = false)
+ {
+ $row1 = new Row(array(Row::COLUMNS => array(
+ 'label' => 'row 1',
+ 'nb_visits' => 10,
+ 'nb_actions' => 15
+ )));
+ if ($addSubtables) {
+ $row1->setSubtable($this->getRow1Subtable());
+ }
+
+ $row2 = new Row(array(Row::COLUMNS => array(
+ 'label' => 'row 2',
+ 'nb_visits' => 13,
+ 'nb_actions' => 18
+ )));
+ if ($addSubtables) {
+ $row2->setSubtable($this->getRow2Subtable());
+ }
+
+ $row3 = new Row(array(Row::COLUMNS => array(
+ 'label' => 'row 3',
+ 'nb_visits' => 20,
+ 'nb_actions' => 25
+ )));
+ if ($addSubtables) {
+ $row3->setSubtable($this->getRow3Subtable());
+ }
+
+ $table = new DataTable();
+ $table->addRowsFromArray(array($row1, $row2, $row3));
+ return $table;
+ }
+
+ private function getRow1Subtable()
+ {
+ $table = new DataTable();
+ $table->addRowsFromArray(array(
+ new Row(array(Row::COLUMNS => array(
+ 'label' => 'col 1',
+ 'nb_visits' => 1,
+ 'nb_actions' => 2
+ )))
+ ));
+ return $table;
+ }
+
+ private function getRow2Subtable()
+ {
+ $table = new DataTable();
+ $table->addRowsFromArray(array(
+ new Row(array(Row::COLUMNS => array(
+ 'label' => 'col 1',
+ 'nb_visits' => 3,
+ 'nb_actions' => 4
+ ))),
+ new Row(array(Row::COLUMNS => array(
+ 'label' => 'col 2',
+ 'nb_visits' => 5,
+ 'nb_actions' => 6
+ )))
+ ));
+ return $table;
+ }
+
+ private function getRow3Subtable()
+ {
+ $table = new DataTable();
+ $table->addRowsFromArray(array(
+ new Row(array(Row::COLUMNS => array(
+ 'label' => 'col 2',
+ 'nb_visits' => 7,
+ 'nb_actions' => 8
+ ))),
+ new Row(array(Row::COLUMNS => array(
+ 'label' => 'col 3',
+ 'nb_visits' => 9,
+ 'nb_actions' => 31
+ ))),
+ new Row(array(Row::COLUMNS => array(
+ 'label' => 'col 4',
+ 'nb_visits' => 32,
+ 'nb_actions' => 33
+ )))
+ ));
+ return $table;
+ }
+
+ private function getSegmentTable()
+ {
+ ++$this->segmentTableCount;
+
+ $table = new DataTable();
+ for ($i = 0; $i != $this->segmentTableCount; ++$i) {
+ $row = new Row(array(Row::COLUMNS => array(
+ 'label' => 'col ' . $i,
+ 'nb_visits' => ($i + 1) * 2,
+ 'nb_actions' => ($i + 1) * 3
+ )));
+ $table->addRow($row);
+ }
+ return $table;
+ }
+
+ private function assertTableRowsEquals($expectedRows, $table)
+ {
+ $renderer = new DataTable\Renderer\Php();
+ $renderer->setSerialize(false);
+ $actualRows = $renderer->render($table);
+
+ $this->assertEquals($expectedRows, $actualRows);
+ }
+} \ No newline at end of file
diff --git a/tests/PHPUnit/Core/DataTable/Renderer/CSVTest.php b/tests/PHPUnit/Core/DataTable/Renderer/CSVTest.php
index 4c5c77a355..64ffcf6a06 100644
--- a/tests/PHPUnit/Core/DataTable/Renderer/CSVTest.php
+++ b/tests/PHPUnit/Core/DataTable/Renderer/CSVTest.php
@@ -179,6 +179,22 @@ class DataTable_Renderer_CSVTest extends PHPUnit_Framework_TestCase
}
/**
+ * @group Core
+ */
+ public function testCSVRendererCorrectlyEscapesHeadersAndValues()
+ {
+ $dataTable = $this->_getDataTableSimpleWithCommasInCells();
+ $render = new Csv();
+ $render->setTable($dataTable);
+ $render->convertToUnicode = false;
+
+ $expected = '"col,1","col,2"
+"val""1","val"",2"';
+ $actual = $render->render();
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
* DATA OF DATATABLE_ARRAY
* -------------------------
*/
@@ -440,4 +456,13 @@ b,d,f,g';
$this->assertEquals($expected, $render->render());
}
-}
+
+ private function _getDataTableSimpleWithCommasInCells()
+ {
+ $table = new DataTable();
+ $table->addRowsFromSimpleArray(array(
+ array("col,1" => "val\"1", "col,2" => "val\",2")
+ ));
+ return $table;
+ }
+} \ No newline at end of file
diff --git a/tests/PHPUnit/Core/DataTable/Renderer/XMLTest.php b/tests/PHPUnit/Core/DataTable/Renderer/XMLTest.php
index 40596e0e23..40b58bdc13 100644
--- a/tests/PHPUnit/Core/DataTable/Renderer/XMLTest.php
+++ b/tests/PHPUnit/Core/DataTable/Renderer/XMLTest.php
@@ -11,6 +11,9 @@ use Piwik\DataTable\Renderer\Xml;
use Piwik\DataTable\Row;
use Piwik\DataTable\Simple;
+/**
+ * @group Only
+ */
class DataTable_Renderer_XMLTest extends PHPUnit_Framework_TestCase
{
public function setUp()
@@ -222,6 +225,39 @@ class DataTable_Renderer_XMLTest extends PHPUnit_Framework_TestCase
}
/**
+ * @group Core
+ */
+ public function testXMLRendererSuccessfullyRendersWhenSimpleDataTableColumnsHaveInvalidXmlCharacters()
+ {
+ $dataTable = $this->_getDataTableSimpleWithInvalidChars();
+ $render = new Xml();
+ $render->setTable($dataTable);
+ $expected = '<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <col name="$%@(%">1</col>
+ <col name="avbs$">2</col>
+ <col name="b/">2</col>
+</result>';
+ $this->assertEquals($expected, $render->render());
+ }
+
+ public function testXMLRendererSuccessfullyRendersWhenDataTableColumnsHaveInvalidXmlCharacters()
+ {
+ $dataTable = $this->_getDataTableWithInvalidChars();
+ $render = new Xml();
+ $render->setTable($dataTable);
+ $expected = '<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <col name="$%@(%">1</col>
+ <col name="avbs$">2</col>
+ <col name="b/">2</col>
+ </row>
+</result>';
+ $this->assertEquals($expected, $render->render());
+ }
+
+ /**
* DATA OF DATATABLE_ARRAY
* -------------------------
*/
@@ -601,4 +637,22 @@ class DataTable_Renderer_XMLTest extends PHPUnit_Framework_TestCase
$this->assertEquals($expected, $render->render());
}
+
+ private function _getDataTableSimpleWithInvalidChars()
+ {
+ $table = new DataTable\Simple();
+ $table->addRowsFromSimpleArray(
+ array("$%@(%" => 1, "avbs$" => 2, "b/" => 2)
+ );
+ return $table;
+ }
+
+ private function _getDataTableWithInvalidChars()
+ {
+ $table = new DataTable();
+ $table->addRowsFromSimpleArray(
+ array("$%@(%" => 1, "avbs$" => 2, "b/" => 2)
+ );
+ return $table;
+ }
}
diff --git a/tests/PHPUnit/Core/Plugin/ComponentFactoryTest.php b/tests/PHPUnit/Core/Plugin/ComponentFactoryTest.php
index d41149c8cd..b7b0f30fc3 100644
--- a/tests/PHPUnit/Core/Plugin/ComponentFactoryTest.php
+++ b/tests/PHPUnit/Core/Plugin/ComponentFactoryTest.php
@@ -11,6 +11,7 @@ use PHPUnit_Framework_TestCase;
use Piwik\Config;
use Piwik\Plugin\ComponentFactory;
use Piwik\Plugin\Manager as PluginManager;
+use Piwik\Plugin\Report;
/**
* @group Core
@@ -74,6 +75,62 @@ class ComponentFactoryTest extends PHPUnit_Framework_TestCase
$this->assertNull($report);
}
+ public function test_getComponentIf_shouldNotFindAComponentIfComponentExistsButPluginIsNotLoaded()
+ {
+ $this->unloadAllPlugins();
+
+ $report = ComponentFactory::getComponentIf(self::REPORT_CLASS_NAME, 'ExampleReport', function (Report $report) {
+ return $report->getAction() == 'getExampleReport';
+ });
+
+ $this->assertNull($report);
+ }
+
+ public function test_getComponentIf_shouldFindAComponent_ThatExists()
+ {
+ $this->loadExampleReportPlugin();
+
+ $report = ComponentFactory::getComponentIf(self::REPORT_CLASS_NAME, 'ExampleReport', function (Report $report) {
+ return $report->getAction() == 'getExampleReport';
+ });
+
+ $this->assertInstanceOf('Piwik\Plugins\ExampleReport\Reports\GetExampleReport', $report);
+ }
+
+ public function test_getComponentIf_shouldNotFindAComponent_IfPluginIsActivatedButComponentNotExists()
+ {
+ $this->loadExampleReportPlugin();
+
+ $report = ComponentFactory::getComponentIf(self::REPORT_CLASS_NAME, 'ExampleReport', function (Report $report) {
+ return false;
+ });
+
+ $this->assertNull($report);
+ }
+
+ public function test_getComponentIf_shouldNotFindAComponent_IfPluginIsLoadedButNotActivated()
+ {
+ PluginManager::getInstance()->loadPlugin('ExampleReport');
+
+ $report = ComponentFactory::getComponentIf(self::REPORT_CLASS_NAME, 'ExampleReport', function (Report $report) {
+ return $report->getAction() == 'getExampleReport';
+ });
+
+ $this->assertNull($report);
+ }
+
+ public function test_getComponentIf_shouldSearchThroughAllPlugins_IfNoPluginNameIsSupplied()
+ {
+ PluginManager::getInstance()->loadPlugins(array('ExampleReport', 'Referrers'));
+
+ $reports = array();
+ ComponentFactory::getComponentIf(self::REPORT_CLASS_NAME, null, function (Report $report) use (&$reports) {
+ $reports[] = $report;
+ });
+
+ $this->assertGreaterThan(1, count($reports));
+ }
+
private function unloadAllPlugins()
{
PluginManager::getInstance()->loadPlugins(array());
diff --git a/tests/PHPUnit/Impl/TestRequestCollection.php b/tests/PHPUnit/Impl/TestRequestCollection.php
index 17ff280eb9..68cae0ff99 100644
--- a/tests/PHPUnit/Impl/TestRequestCollection.php
+++ b/tests/PHPUnit/Impl/TestRequestCollection.php
@@ -246,7 +246,6 @@ class TestRequestCollection
$parametersToSet['serialize'] = 1;
$exampleUrl = $apiMetadata->getExampleUrl($class, $methodName, $parametersToSet);
-
if ($exampleUrl === false) {
continue;
}
diff --git a/tests/PHPUnit/Integration/Core/ReportTest.php b/tests/PHPUnit/Integration/Core/ReportTest.php
index c1d0df37f1..f935416675 100644
--- a/tests/PHPUnit/Integration/Core/ReportTest.php
+++ b/tests/PHPUnit/Integration/Core/ReportTest.php
@@ -6,11 +6,14 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
+use Piwik\API\Proxy;
use Piwik\Plugin\Report;
use Piwik\Plugins\ExampleReport\Reports\GetExampleReport;
use Piwik\Plugins\Actions\Columns\ExitPageUrl;
use Piwik\Piwik;
use Piwik\Metrics;
+use Piwik\Plugins\ExampleTracker\Columns\ExampleDimension;
+use Piwik\Plugins\Referrers\Columns\Keyword;
use Piwik\WidgetsList;
use Piwik\Translate;
use Piwik\Menu\MenuReporting;
@@ -28,6 +31,7 @@ class GetBasicReport extends Report
$this->module = 'TestPlugin';
$this->action = 'getBasicReport';
$this->category = 'Goals_Goals';
+ $this->actionToLoadSubTables = 'invalidReport';
}
}
@@ -101,6 +105,8 @@ class Plugin_ReportTest extends DatabaseTestCase
$this->disabledReport = new GetDisabledReport();
$this->basicReport = new GetBasicReport();
$this->advancedReport = new GetAdvancedReport();
+
+ Proxy::unsetInstance();
}
public function tearDown()
@@ -338,6 +344,7 @@ class Plugin_ReportTest extends DatabaseTestCase
'bounce_rate' => 'General_ColumnBounceRate',
'conversion_rate' => 'General_ColumnConversionRate',
),
+ 'actionToLoadSubTables' => 'invalidReport',
'order' => 20
)
), $reports);
@@ -422,6 +429,97 @@ class Plugin_ReportTest extends DatabaseTestCase
}
}
+ public function test_getSubtableDimension_ShouldReturnNullIfNoSubtableActionExists()
+ {
+ $report = new GetExampleReport();
+ $this->assertNull($report->getSubtableDimension());
+ }
+
+ public function test_getSubtableDimension_ShouldReturnNullIfSubtableActionIsInvalid()
+ {
+ $report = new GetBasicReport();
+ $this->assertNull($report->getSubtableDimension());
+ }
+
+ public function test_getSubtableDimension_ShouldReturnCorrectDimensionIfSubtableActionIsDefinedAndCorrect()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers'));
+
+ $report = Report::factory('Referrers', 'getSearchEngines');
+ $subtableDimension = $report->getSubtableDimension();
+
+ $this->assertNotNull($subtableDimension);
+ $this->assertInstanceOf("Piwik\\Plugins\\Referrers\\Columns\\Keyword", $subtableDimension);
+ }
+
+ public function test_fetch_ShouldUseCorrectApiUrl()
+ {
+ PluginManager::getInstance()->loadPlugins(array('API', 'ExampleReport'));
+
+ $proxyMock = $this->getMock('stdClass', array('call', '__construct'));
+ $proxyMock->expects($this->once())->method('call')->with(
+ '\\Piwik\\Plugins\\ExampleReport\\API', 'getExampleReport', array(
+ 'idSite' => 1,
+ 'date' => '2012-01-02',
+ 'format' => 'original',
+ 'module' => 'API',
+ 'method' => 'ExampleReport.getExampleReport'
+ )
+ )->willReturn("result");
+ Proxy::setSingletonInstance($proxyMock);
+
+ $report = new GetExampleReport();
+ $result = $report->fetch(array('idSite' => 1, 'date' => '2012-01-02'));
+ $this->assertEquals("result", $result);
+ }
+
+ public function test_fetchSubtable_ShouldUseCorrectApiUrl()
+ {
+ PluginManager::getInstance()->loadPlugins(array('API', 'Referrers'));
+
+ $proxyMock = $this->getMock('stdClass', array('call', '__construct'));
+ $proxyMock->expects($this->once())->method('call')->with(
+ '\\Piwik\\Plugins\\Referrers\\API', 'getSearchEnginesFromKeywordId', array(
+ 'idSubtable' => 23,
+ 'idSite' => 1,
+ 'date' => '2012-01-02',
+ 'format' => 'original',
+ 'module' => 'API',
+ 'method' => 'Referrers.getSearchEnginesFromKeywordId'
+ )
+ )->willReturn("result");
+ Proxy::setSingletonInstance($proxyMock);
+
+ $report = new \Piwik\Plugins\Referrers\Reports\GetKeywords();
+ $result = $report->fetchSubtable(23, array('idSite' => 1, 'date' => '2012-01-02'));
+ $this->assertEquals("result", $result);
+ }
+
+ public function test_getForDimension_ShouldReturnCorrectInstanceTypeIfAssociatedReportExists()
+ {
+ PluginManager::getInstance()->loadPlugins(array('Referrers'));
+
+ $report = Report::getForDimension(new Keyword());
+ $this->assertInstanceOf("Piwik\\Plugins\\Referrers\\Reports\\GetKeywords", $report);
+ }
+
+ public function test_getForDimension_ShouldReturnNullIfNoReportExistsForDimension()
+ {
+ $this->loadExampleReportPlugin();
+ $this->loadMorePlugins();
+
+ $report = Report::getForDimension(new ExampleDimension());
+ $this->assertNull($report);
+ }
+
+ public function test_getForDimension_ShouldReturnNullIfReportPluginNotLoaded()
+ {
+ PluginManager::getInstance()->loadPlugins(array());
+
+ $report = Report::getForDimension(new Keyword());
+ $this->assertNull($report);
+ }
+
private function loadExampleReportPlugin()
{
PluginManager::getInstance()->loadPlugins(array('ExampleReport'));
@@ -441,6 +539,4 @@ class Plugin_ReportTest extends DatabaseTestCase
{
Translate::reloadLanguage('en');
}
-
-
} \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/PivotByQueryParamTest.php b/tests/PHPUnit/Integration/PivotByQueryParamTest.php
new file mode 100644
index 0000000000..a59ff9d1f3
--- /dev/null
+++ b/tests/PHPUnit/Integration/PivotByQueryParamTest.php
@@ -0,0 +1,167 @@
+<?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\Tests\Integration;
+
+use Piwik\Config;
+use Piwik\Date;
+use Piwik\Tests\Fixtures\ManyVisitsWithMockLocationProvider;
+use Piwik\Tests\IntegrationTestCase;
+
+/**
+ * @group Integration
+ * @group PivotByQueryParamTest
+ */
+class PivotByQueryParamTest extends IntegrationTestCase
+{
+ /**
+ * @var ManyVisitsWithMockLocationProvider
+ */
+ public static $fixture = null;
+
+ public static function setUpBeforeClass()
+ {
+ parent::setUpBeforeClass();
+
+ Config::getInstance()->General['pivot_by_filter_enable_fetch_by_segment'] = 1;
+ }
+
+ public function testPivotBySubtableDimensionCreatesCorrectPivotTable()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'Referrers.SearchEngine',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+
+ public function testPivotBySubtableDimensionCreatesCorrectPivotTableWhenEntireHirearchyIsNotLoaded()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'Referrers.SearchEngine',
+ 'pivotByColumn' => '', // also test default pivot column
+ 'pivotByColumnLimit' => -1,
+ 'expanded' => 0
+ ));
+ }
+
+ public function testPivotBySegmentCreatesCorrectPivotTable()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'UserCountry.City',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+
+ public function testPivotByParamPlaysNiceWithOtherQueryParams()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'Referrers.SearchEngine',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1,
+ 'flat' => 1,
+ 'totals' => 1,
+ 'disable_queued_filters' => 1,
+ 'disable_generic_filters' => 1,
+ 'showColumns' => 'Google,Bing'
+ ));
+ }
+
+ public function testPivotByParamPlaysNiceWithQueuedFilters()
+ {
+ // TODO: known issue: some segment/report relationships are more complicated; for example, UserCountry.GetCity labels are combinations
+ // of city, region & country dimensions, so the segment to get an intersected table needs all 3 of those.
+
+ $this->markTestSkipped("Not working right now.");
+
+ $this->assertApiResponseEqualsExpected("UserSettings.getBrowser", array( // should have logo metadata in output
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'UserCountry.City', // testing w/ report that has no subtable report
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+
+ public function testPivotByParamWorksWithReportWhoseSubtableIsSelf()
+ {
+ $this->assertApiResponseEqualsExpected("Actions.getPageUrls", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'Actions.PageUrl',
+ 'pivotByColumn' => 'nb_hits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+
+ public function testPivotByParamWorksWithColumnLimiting()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'pivotBy' => 'UserCountry.City',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => 2
+ ));
+ }
+
+ public function testPivotByParamWorksWithJsonOutput()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'format' => 'json',
+ 'pivotBy' => 'UserCountry.City',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+
+ public function testPivotByParamWorksWithCsvOutput()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => self::$fixture->idSite,
+ 'date' => Date::factory(self::$fixture->dateTime)->toString(),
+ 'period' => 'week',
+ 'format' => 'csv',
+ 'pivotBy' => 'UserCountry.City',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+
+ public function testPivotByParamPlaysNiceWithDataTableMaps()
+ {
+ $this->assertApiResponseEqualsExpected("Referrers.getKeywords", array(
+ 'idSite' => 'all',
+ 'date' => '2010-01-01,2010-01-07',
+ 'period' => 'day',
+ 'pivotBy' => 'UserCountry.City',
+ 'pivotByColumn' => 'nb_visits',
+ 'pivotByColumnLimit' => -1
+ ));
+ }
+}
+
+PivotByQueryParamTest::$fixture = new ManyVisitsWithMockLocationProvider(); \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithDataTableMaps__Referrers.getKeywords_day.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithDataTableMaps__Referrers.getKeywords_day.xml
new file mode 100644
index 0000000000..a60b9519fb
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithDataTableMaps__Referrers.getKeywords_day.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result idSite="1">
+ <result date="2010-01-01" />
+ <result date="2010-01-02" />
+ <result date="2010-01-03">
+ <row>
+ <col name="label">this search term</col>
+ <col name="Melbourne, Victoria, Australia">2</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">1</col>
+ </row>
+ <row>
+ <col name="label">search term 2</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">2</col>
+ </row>
+ <row>
+ <col name="label">search term 3</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">2</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">search term 4</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">2</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">that search term</col>
+ <col name="Melbourne, Victoria, Australia">2</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">search term 1</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">1</col>
+ </row>
+ </result>
+ <result date="2010-01-04" />
+ <result date="2010-01-05" />
+ <result date="2010-01-06" />
+ <result date="2010-01-07" />
+ </result>
+</results> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithOtherQueryParams__Referrers.getKeywords_week.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithOtherQueryParams__Referrers.getKeywords_week.xml
new file mode 100644
index 0000000000..777020b15e
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamPlaysNiceWithOtherQueryParams__Referrers.getKeywords_week.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>this search term</label>
+ <Bing>0</Bing>
+ <Google>1</Google>
+ </row>
+ <row>
+ <label>search term 2</label>
+ <Bing>0</Bing>
+ <Google>0</Google>
+ </row>
+ <row>
+ <label>search term 3</label>
+ <Bing>0</Bing>
+ <Google>1</Google>
+ </row>
+ <row>
+ <label>search term 4</label>
+ <Bing>1</Bing>
+ <Google>0</Google>
+ </row>
+ <row>
+ <label>that search term</label>
+ <Bing>0</Bing>
+ <Google>1</Google>
+ </row>
+ <row>
+ <label>search term 1</label>
+ <Bing>1</Bing>
+ <Google>0</Google>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithColumnLimiting__Referrers.getKeywords_week.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithColumnLimiting__Referrers.getKeywords_week.xml
new file mode 100644
index 0000000000..70bcfeed7d
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithColumnLimiting__Referrers.getKeywords_week.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <col name="label">this search term</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">1</col>
+ </row>
+ <row>
+ <col name="label">search term 2</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">2</col>
+ </row>
+ <row>
+ <col name="label">search term 3</col>
+ <col name="Toronto, Ontario, Canada">2</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">search term 4</col>
+ <col name="Toronto, Ontario, Canada">2</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">that search term</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">search term 1</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">1</col>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithCsvOutput__Referrers.getKeywords_week.csv b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithCsvOutput__Referrers.getKeywords_week.csv
new file mode 100644
index 0000000000..9b91feb5ee
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithCsvOutput__Referrers.getKeywords_week.csv
Binary files differ
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithJsonOutput__Referrers.getKeywords_week.json b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithJsonOutput__Referrers.getKeywords_week.json
new file mode 100644
index 0000000000..3ffebb1d65
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithJsonOutput__Referrers.getKeywords_week.json
@@ -0,0 +1 @@
+[{"label":"this search term","Melbourne, Victoria, Australia":2,"Toronto, Ontario, Canada":false,"Yokohama, Kanagawa, Japan":1},{"label":"search term 2","Melbourne, Victoria, Australia":false,"Toronto, Ontario, Canada":false,"Yokohama, Kanagawa, Japan":2},{"label":"search term 3","Melbourne, Victoria, Australia":false,"Toronto, Ontario, Canada":2,"Yokohama, Kanagawa, Japan":false},{"label":"search term 4","Melbourne, Victoria, Australia":false,"Toronto, Ontario, Canada":2,"Yokohama, Kanagawa, Japan":false},{"label":"that search term","Melbourne, Victoria, Australia":2,"Toronto, Ontario, Canada":false,"Yokohama, Kanagawa, Japan":false},{"label":"search term 1","Melbourne, Victoria, Australia":false,"Toronto, Ontario, Canada":false,"Yokohama, Kanagawa, Japan":1}] \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithReportWhoseSubtableIsSelf__Actions.getPageUrls_week.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithReportWhoseSubtableIsSelf__Actions.getPageUrls_week.xml
new file mode 100644
index 0000000000..45bd7630ea
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotByParamWorksWithReportWhoseSubtableIsSelf__Actions.getPageUrls_week.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <col name="label">0</col>
+ <col name="/0">1</col>
+ <col name="/1">1</col>
+ <col name="/10">0</col>
+ <col name="/11">0</col>
+ <col name="/12">0</col>
+ <col name="/13">0</col>
+ <col name="/14">0</col>
+ <col name="/15">0</col>
+ <col name="/16">0</col>
+ <col name="/17">0</col>
+ <col name="/18">0</col>
+ <col name="/19">0</col>
+ <col name="/2">1</col>
+ <col name="/3">1</col>
+ <col name="/4">0</col>
+ <col name="/5">0</col>
+ <col name="/6">0</col>
+ <col name="/7">0</col>
+ <col name="/8">0</col>
+ <col name="/9">0</col>
+ <col name="/index">1</col>
+ </row>
+ <row>
+ <col name="label">1</col>
+ <col name="/0">0</col>
+ <col name="/1">0</col>
+ <col name="/10">0</col>
+ <col name="/11">0</col>
+ <col name="/12">0</col>
+ <col name="/13">0</col>
+ <col name="/14">0</col>
+ <col name="/15">0</col>
+ <col name="/16">0</col>
+ <col name="/17">0</col>
+ <col name="/18">0</col>
+ <col name="/19">0</col>
+ <col name="/2">0</col>
+ <col name="/3">0</col>
+ <col name="/4">1</col>
+ <col name="/5">1</col>
+ <col name="/6">1</col>
+ <col name="/7">1</col>
+ <col name="/8">0</col>
+ <col name="/9">0</col>
+ <col name="/index">1</col>
+ </row>
+ <row>
+ <col name="label">2</col>
+ <col name="/0">0</col>
+ <col name="/1">0</col>
+ <col name="/10">1</col>
+ <col name="/11">1</col>
+ <col name="/12">0</col>
+ <col name="/13">0</col>
+ <col name="/14">0</col>
+ <col name="/15">0</col>
+ <col name="/16">0</col>
+ <col name="/17">0</col>
+ <col name="/18">0</col>
+ <col name="/19">0</col>
+ <col name="/2">0</col>
+ <col name="/3">0</col>
+ <col name="/4">0</col>
+ <col name="/5">0</col>
+ <col name="/6">0</col>
+ <col name="/7">0</col>
+ <col name="/8">1</col>
+ <col name="/9">1</col>
+ <col name="/index">1</col>
+ </row>
+ <row>
+ <col name="label">3</col>
+ <col name="/0">0</col>
+ <col name="/1">0</col>
+ <col name="/10">0</col>
+ <col name="/11">0</col>
+ <col name="/12">1</col>
+ <col name="/13">1</col>
+ <col name="/14">1</col>
+ <col name="/15">1</col>
+ <col name="/16">0</col>
+ <col name="/17">0</col>
+ <col name="/18">0</col>
+ <col name="/19">0</col>
+ <col name="/2">0</col>
+ <col name="/3">0</col>
+ <col name="/4">0</col>
+ <col name="/5">0</col>
+ <col name="/6">0</col>
+ <col name="/7">0</col>
+ <col name="/8">0</col>
+ <col name="/9">0</col>
+ <col name="/index">1</col>
+ </row>
+ <row>
+ <col name="label">4</col>
+ <col name="/0">0</col>
+ <col name="/1">0</col>
+ <col name="/10">0</col>
+ <col name="/11">0</col>
+ <col name="/12">0</col>
+ <col name="/13">0</col>
+ <col name="/14">0</col>
+ <col name="/15">0</col>
+ <col name="/16">1</col>
+ <col name="/17">1</col>
+ <col name="/18">1</col>
+ <col name="/19">1</col>
+ <col name="/2">0</col>
+ <col name="/3">0</col>
+ <col name="/4">0</col>
+ <col name="/5">0</col>
+ <col name="/6">0</col>
+ <col name="/7">0</col>
+ <col name="/8">0</col>
+ <col name="/9">0</col>
+ <col name="/index">1</col>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySegmentCreatesCorrectPivotTable__Referrers.getKeywords_week.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySegmentCreatesCorrectPivotTable__Referrers.getKeywords_week.xml
new file mode 100644
index 0000000000..99086f82e8
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySegmentCreatesCorrectPivotTable__Referrers.getKeywords_week.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <col name="label">this search term</col>
+ <col name="Melbourne, Victoria, Australia">2</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">1</col>
+ </row>
+ <row>
+ <col name="label">search term 2</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">2</col>
+ </row>
+ <row>
+ <col name="label">search term 3</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">2</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">search term 4</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">2</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">that search term</col>
+ <col name="Melbourne, Victoria, Australia">2</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">0</col>
+ </row>
+ <row>
+ <col name="label">search term 1</col>
+ <col name="Melbourne, Victoria, Australia">0</col>
+ <col name="Toronto, Ontario, Canada">0</col>
+ <col name="Yokohama, Kanagawa, Japan">1</col>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTableWhenEntireHirearchyIsNotLoaded__Referrers.getKeywords_week.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTableWhenEntireHirearchyIsNotLoaded__Referrers.getKeywords_week.xml
new file mode 100644
index 0000000000..4c9fe03b6f
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTableWhenEntireHirearchyIsNotLoaded__Referrers.getKeywords_week.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <col name="label">this search term</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">1</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">0</col>
+ <col name="Google">1</col>
+ <col name="Yahoo!">1</col>
+ </row>
+ <row>
+ <col name="label">search term 2</col>
+ <col name="Alexa">1</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">1</col>
+ <col name="Bing">0</col>
+ <col name="Google">0</col>
+ <col name="Yahoo!">0</col>
+ </row>
+ <row>
+ <col name="label">search term 3</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">1</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">0</col>
+ <col name="Google">1</col>
+ <col name="Yahoo!">0</col>
+ </row>
+ <row>
+ <col name="label">search term 4</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">1</col>
+ <col name="Google">0</col>
+ <col name="Yahoo!">1</col>
+ </row>
+ <row>
+ <col name="label">that search term</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">0</col>
+ <col name="Google">1</col>
+ <col name="Yahoo!">1</col>
+ </row>
+ <row>
+ <col name="label">search term 1</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">1</col>
+ <col name="Google">0</col>
+ <col name="Yahoo!">0</col>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTable__Referrers.getKeywords_week.xml b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTable__Referrers.getKeywords_week.xml
new file mode 100644
index 0000000000..4c9fe03b6f
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_PivotByQueryParamTest_testPivotBySubtableDimensionCreatesCorrectPivotTable__Referrers.getKeywords_week.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <col name="label">this search term</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">1</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">0</col>
+ <col name="Google">1</col>
+ <col name="Yahoo!">1</col>
+ </row>
+ <row>
+ <col name="label">search term 2</col>
+ <col name="Alexa">1</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">1</col>
+ <col name="Bing">0</col>
+ <col name="Google">0</col>
+ <col name="Yahoo!">0</col>
+ </row>
+ <row>
+ <col name="label">search term 3</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">1</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">0</col>
+ <col name="Google">1</col>
+ <col name="Yahoo!">0</col>
+ </row>
+ <row>
+ <col name="label">search term 4</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">1</col>
+ <col name="Google">0</col>
+ <col name="Yahoo!">1</col>
+ </row>
+ <row>
+ <col name="label">that search term</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">0</col>
+ <col name="Google">1</col>
+ <col name="Yahoo!">1</col>
+ </row>
+ <row>
+ <col name="label">search term 1</col>
+ <col name="Alexa">0</col>
+ <col name="Ask">0</col>
+ <col name="Babylon">0</col>
+ <col name="Bing">1</col>
+ <col name="Google">0</col>
+ <col name="Yahoo!">0</col>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/IntegrationTestCase.php b/tests/PHPUnit/IntegrationTestCase.php
index 59160c7c30..ae8a674ed2 100755
--- a/tests/PHPUnit/IntegrationTestCase.php
+++ b/tests/PHPUnit/IntegrationTestCase.php
@@ -274,11 +274,17 @@ abstract class IntegrationTestCase extends PHPUnit_Framework_TestCase
list($processedFilePath, $expectedFilePath) =
$this->getProcessedAndExpectedPaths($testName, $apiId, $format = null, $compareAgainst);
+ $originalGET = $_GET;
+ $_GET = $requestUrl;
+ unset($_GET['serialize']);
+
$processedResponse = TestRequestResponse::loadFromApi($params, $requestUrl);
if (empty($compareAgainst)) {
$processedResponse->save($processedFilePath);
}
+ $_GET = $originalGET;
+
try {
$expectedResponse = TestRequestResponse::loadFromFile($expectedFilePath, $params, $requestUrl);
} catch (Exception $ex) {
@@ -395,6 +401,7 @@ abstract class IntegrationTestCase extends PHPUnit_Framework_TestCase
'idSite' => $queryParams['idSite'],
'date' => $queryParams['date'],
'periods' => $queryParams['period'],
+ 'format' => isset($queryParams['format']) ? $queryParams['format'] : 'xml',
'testSuffix' => '_' . $this->getName(), // TODO: instead of using a test suffix, the whole file name should just be the test method
'otherRequestParameters' => $queryParams
));