diff options
Diffstat (limited to 'core')
40 files changed, 682 insertions, 708 deletions
diff --git a/core/API/DataTableManipulator/Flattener.php b/core/API/DataTableManipulator/Flattener.php index cfee16e6ba..a976b7a62e 100644 --- a/core/API/DataTableManipulator/Flattener.php +++ b/core/API/DataTableManipulator/Flattener.php @@ -38,17 +38,16 @@ class Flattener extends DataTableManipulator * Separator for building recursive labels (or paths) * @var string */ - public $recursiveLabelSeparator = ' - '; + public $recursiveLabelSeparator = ''; /** * @param DataTable $dataTable + * @param string $recursiveLabelSeparator * @return DataTable|DataTable\Map */ - public function flatten($dataTable) + public function flatten($dataTable, $recursiveLabelSeparator) { - if ($this->apiModule == 'Actions' || $this->apiMethod == 'getWebsites') { - $this->recursiveLabelSeparator = '/'; - } + $this->recursiveLabelSeparator = $recursiveLabelSeparator; return $this->manipulate($dataTable); } @@ -72,9 +71,10 @@ class Flattener extends DataTableManipulator } $newDataTable = $dataTable->getEmptyClone($keepFilters); - foreach ($dataTable->getRows() as $row) { - $this->flattenRow($row, $newDataTable); + foreach ($dataTable->getRows() as $rowId => $row) { + $this->flattenRow($row, $rowId, $newDataTable); } + return $newDataTable; } @@ -84,15 +84,21 @@ class Flattener extends DataTableManipulator * @param string $labelPrefix * @param bool $parentLogo */ - private function flattenRow(Row $row, DataTable $dataTable, + private function flattenRow(Row $row, $rowId, DataTable $dataTable, $labelPrefix = '', $parentLogo = false) { $label = $row->getColumn('label'); if ($label !== false) { $label = trim($label); - if (substr($label, 0, 1) == '/' && $this->recursiveLabelSeparator == '/') { - $label = substr($label, 1); + + if ($this->recursiveLabelSeparator == '/') { + if (substr($label, 0, 1) == '/') { + $label = substr($label, 1); + } elseif ($rowId === DataTable::ID_SUMMARY_ROW && $labelPrefix && $label != DataTable::LABEL_SUMMARY_ROW) { + $label = ' - ' . $label; + } } + $label = $labelPrefix . $label; $row->setColumn('label', $label); } @@ -103,7 +109,16 @@ class Flattener extends DataTableManipulator $row->setMetadata('logo', $logo); } - $subTable = $this->loadSubtable($dataTable, $row); + /** @var DataTable $subTable */ + $subTable = $row->getSubtable(); + + if ($subTable) { + $subTable->applyQueuedFilters(); + $row->deleteMetadata('idsubdatatable_in_db'); + } else { + $subTable = $this->loadSubtable($dataTable, $row); + } + $row->removeSubtable(); if ($subTable === null) { @@ -117,8 +132,8 @@ class Flattener extends DataTableManipulator $dataTable->addRow($row); } $prefix = $label . $this->recursiveLabelSeparator; - foreach ($subTable->getRows() as $row) { - $this->flattenRow($row, $dataTable, $prefix, $logo); + foreach ($subTable->getRows() as $rowId => $row) { + $this->flattenRow($row, $rowId, $dataTable, $prefix, $logo); } } } diff --git a/core/API/DataTableManipulator/LabelFilter.php b/core/API/DataTableManipulator/LabelFilter.php index cd8300991b..0d4e11772a 100644 --- a/core/API/DataTableManipulator/LabelFilter.php +++ b/core/API/DataTableManipulator/LabelFilter.php @@ -147,6 +147,8 @@ class LabelFilter extends DataTableManipulator } $variations[] = $label; + $variations = array_unique($variations); + return $variations; } diff --git a/core/API/DataTableManipulator/ReportTotalsCalculator.php b/core/API/DataTableManipulator/ReportTotalsCalculator.php index b6b82effad..4703c36c7d 100644 --- a/core/API/DataTableManipulator/ReportTotalsCalculator.php +++ b/core/API/DataTableManipulator/ReportTotalsCalculator.php @@ -66,14 +66,19 @@ class ReportTotalsCalculator extends DataTableManipulator $firstLevelTable = $this->makeSureToWorkOnFirstLevelDataTable($dataTable); $metricsToCalculate = Metrics::getMetricIdsToProcessReportTotal(); + $realMetricNames = array(); foreach ($metricsToCalculate as $metricId) { - $realMetricName = $this->hasDataTableMetric($firstLevelTable, $metricId); - if (empty($realMetricName)) { - continue; + $metricName = Metrics::getReadableColumnName($metricId); + $realMetricName = $this->hasDataTableMetric($firstLevelTable, $metricId, $metricName); + if (!empty($realMetricName)) { + $realMetricNames[$metricName] = $realMetricName; } + } - foreach ($firstLevelTable->getRows() as $row) { - $totalValues = $this->sumColumnValueToTotal($row, $metricId, $realMetricName, $totalValues); + foreach ($firstLevelTable->getRows() as $row) { + $columns = $row->getColumns(); + foreach ($realMetricNames as $metricName => $realMetricName) { + $totalValues = $this->sumColumnValueToTotal($columns, $metricName, $realMetricName, $totalValues); } } @@ -82,7 +87,7 @@ class ReportTotalsCalculator extends DataTableManipulator return $dataTable; } - private function hasDataTableMetric(DataTable $dataTable, $metricId) + private function hasDataTableMetric(DataTable $dataTable, $metricId, $readableColumnName) { $firstRow = $dataTable->getFirstRow(); @@ -90,7 +95,6 @@ class ReportTotalsCalculator extends DataTableManipulator return false; } - $readableColumnName = Metrics::getReadableColumnName($metricId); $columnAlternatives = array( $metricId, $readableColumnName, @@ -151,17 +155,18 @@ class ReportTotalsCalculator extends DataTableManipulator return $table; } - private function sumColumnValueToTotal(Row $row, $metricId, $realMetricId, $totalValues) + private function sumColumnValueToTotal($columns, $metricName, $realMetricId, $totalValues) { - $value = $row->getColumn($realMetricId); + $value = false; + if (array_key_exists($realMetricId, $columns)) { + $value = $columns[$realMetricId]; + } if (false === $value) { return $totalValues; } - $metricName = Metrics::getReadableColumnName($metricId); - if (array_key_exists($metricName, $totalValues)) { $totalValues[$metricName] += $value; } else { diff --git a/core/API/DataTablePostProcessor.php b/core/API/DataTablePostProcessor.php index 999b3fe339..dfa0434da6 100644 --- a/core/API/DataTablePostProcessor.php +++ b/core/API/DataTablePostProcessor.php @@ -59,6 +59,9 @@ class DataTablePostProcessor */ private $formatter; + private $callbackBeforeGenericFilters; + private $callbackAfterGenericFilters; + /** * Constructor. */ @@ -66,11 +69,31 @@ class DataTablePostProcessor { $this->apiModule = $apiModule; $this->apiMethod = $apiMethod; - $this->request = $request; + $this->setRequest($request); $this->report = Report::factory($apiModule, $apiMethod); $this->apiInconsistencies = new Inconsistencies(); - $this->formatter = new Formatter(); + $this->setFormatter(new Formatter()); + } + + public function setFormatter(Formatter $formatter) + { + $this->formatter = $formatter; + } + + public function setRequest($request) + { + $this->request = $request; + } + + public function setCallbackBeforeGenericFilters($callbackBeforeGenericFilters) + { + $this->callbackBeforeGenericFilters = $callbackBeforeGenericFilters; + } + + public function setCallbackAfterGenericFilters($callbackAfterGenericFilters) + { + $this->callbackAfterGenericFilters = $callbackAfterGenericFilters; } /** @@ -85,22 +108,27 @@ class DataTablePostProcessor // this is non-trivial since it will require, eg, to make sure processed metrics aren't added // after pivotBy is handled. $dataTable = $this->applyPivotByFilter($dataTable); - $dataTable = $this->applyFlattener($dataTable); $dataTable = $this->applyTotalsCalculator($dataTable); + $dataTable = $this->applyFlattener($dataTable); - $dataTable = $this->applyGenericFilters($dataTable); + if ($this->callbackBeforeGenericFilters) { + call_user_func($this->callbackBeforeGenericFilters, $dataTable); + } + $dataTable = $this->applyGenericFilters($dataTable); $this->applyComputeProcessedMetrics($dataTable); + if ($this->callbackAfterGenericFilters) { + call_user_func($this->callbackAfterGenericFilters, $dataTable); + } + // we automatically safe decode all datatable labels (against xss) $dataTable->queueFilter('SafeDecodeLabel'); - $dataTable = $this->convertSegmentValueToSegment($dataTable); $dataTable = $this->applyQueuedFilters($dataTable); $dataTable = $this->applyRequestedColumnDeletion($dataTable); $dataTable = $this->applyLabelFilter($dataTable); $dataTable = $this->applyMetricsFormatting($dataTable); - return $dataTable; } @@ -145,7 +173,13 @@ class DataTablePostProcessor if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') { $flattener->includeAggregateRows(); } - $dataTable = $flattener->flatten($dataTable); + + $recursiveLabelSeparator = ' - '; + if ($this->report) { + $recursiveLabelSeparator = $this->report->getRecursiveLabelSeparator(); + } + + $dataTable = $flattener->flatten($dataTable, $recursiveLabelSeparator); } return $dataTable; } diff --git a/core/API/Proxy.php b/core/API/Proxy.php index 0345693b62..0f8667d0d8 100644 --- a/core/API/Proxy.php +++ b/core/API/Proxy.php @@ -490,7 +490,7 @@ class Proxy extends Singleton $hideLine = trim($hideLine); $hideLine .= ' '; - $token = trim(strtok($hideLine, " "), "\n"); + $token = trim(strtok($hideLine, " "), "\n"); $hide = false; @@ -529,18 +529,6 @@ class Proxy extends Singleton } /** - * Returns the number of required parameters (parameters without default values). - * - * @param string $class The class name - * @param string $name The method name - * @return int The number of required parameters - */ - private function getNumberOfRequiredParameters($class, $name) - { - return $this->metadataArray[$class][$name]['numberOfRequiredParameters']; - } - - /** * Returns true if the method is found in the API of the given class name. * * @param string $className The class name diff --git a/core/API/Request.php b/core/API/Request.php index 1db4009606..2ed5dfc567 100644 --- a/core/API/Request.php +++ b/core/API/Request.php @@ -18,6 +18,7 @@ use Piwik\SettingsServer; use Piwik\Url; use Piwik\UrlHelper; use Piwik\Log; +use Piwik\Plugin\Manager as PluginManager; /** * Dispatches API requests to the appropriate API method. @@ -219,10 +220,9 @@ class Request list($module, $method) = $this->extractModuleAndMethod($moduleMethod); list($module, $method) = self::getRenamedModuleAndAction($module, $method); + + PluginManager::getInstance()->checkIsPluginActivated($module); - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) { - throw new PluginDeactivatedException($module); - } $apiClassName = self::getClassNameAPI($module); self::reloadAuthUsingTokenAuth($this->request); diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php index 4ebe50305d..68e448b0b4 100644 --- a/core/API/ResponseBuilder.php +++ b/core/API/ResponseBuilder.php @@ -25,6 +25,7 @@ class ResponseBuilder private $apiRenderer = null; private $request = null; private $sendHeader = true; + private $postProcessDataTable = true; private $apiModule = false; private $apiMethod = false; @@ -45,6 +46,11 @@ class ResponseBuilder $this->sendHeader = false; } + public function disableDataTablePostProcessor() + { + $this->postProcessDataTable = false; + } + /** * This method processes the data resulting from the API call. * @@ -164,8 +170,10 @@ class ResponseBuilder private function handleDataTable(DataTableInterface $datatable) { - $postProcessor = new DataTablePostProcessor($this->apiModule, $this->apiMethod, $this->request); - $datatable = $postProcessor->process($datatable); + if ($this->postProcessDataTable) { + $postProcessor = new DataTablePostProcessor($this->apiModule, $this->apiMethod, $this->request); + $datatable = $postProcessor->process($datatable); + } return $this->apiRenderer->renderDataTable($datatable); } diff --git a/core/Archive.php b/core/Archive.php index 1931b6a343..c7eb6e3106 100644 --- a/core/Archive.php +++ b/core/Archive.php @@ -452,6 +452,7 @@ class Archive * @return DataTable|DataTable\Map See {@link getDataTable()} and * {@link getDataTableExpanded()} for more * information + * @deprecated Since Piwik 2.12.0 Use Archive::createDataTableFromArchive() instead */ public static function getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable = null, $depth = null) @@ -474,6 +475,38 @@ class Archive return $dataTable; } + /** + * Helper function that creates an Archive instance and queries for report data using + * query parameter data. API methods can use this method to reduce code redundancy. + * + * @param string $recordName The name of the report to return. + * @param int|string|array $idSite @see {@link build()} + * @param string $period @see {@link build()} + * @param string $date @see {@link build()} + * @param string $segment @see {@link build()} + * @param bool $expanded If true, loads all subtables. See {@link getDataTableExpanded()} + * @param bool $flat If true, loads all subtables and disabled all recursive filters. + * @param int|null $idSubtable See {@link getDataTableExpanded()} + * @param int|null $depth See {@link getDataTableExpanded()} + * @return DataTable|DataTable\Map + */ + public static function createDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null, $depth = null) + { + if ($flat && !$idSubtable) { + $expanded = true; + } + + $dataTable = self::getDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); + + $dataTable->filter('ReplaceColumnNames'); + + if ($flat) { + $dataTable->disableRecursiveFilters(); + } + + return $dataTable; + } + private function appendIdSubtable($recordName, $id) { return $recordName . "_" . $id; diff --git a/core/Common.php b/core/Common.php index 7e3bcbc7af..1ed8d48935 100644 --- a/core/Common.php +++ b/core/Common.php @@ -638,26 +638,6 @@ class Common } /** - * Convert IP address (in network address format) to presentation format. - * This is a backward compatibility function for code that only expects - * IPv4 addresses (i.e., doesn't support IPv6). - * - * @see IP::N2P() - * - * This function does not support the long (or its string representation) - * returned by the built-in ip2long() function, from Piwik 1.3 and earlier. - * - * @deprecated 1.4 - * - * @param string $ip IP address in network address format - * @return string - */ - public static function long2ip($ip) - { - return IP::long2ip($ip); - } - - /** * JSON encode wrapper * - missing or broken in some php 5.x versions * diff --git a/core/Config.php b/core/Config.php index 014424c119..e28f581ea8 100644 --- a/core/Config.php +++ b/core/Config.php @@ -455,10 +455,7 @@ class Config extends Singleton : $this->configLocal[$name]; } - if ($section === null && $name == 'superuser') { - $user = $this->getConfigSuperUserForBackwardCompatibility(); - return $user; - } else if ($section === null) { + if ($section === null) { $section = array(); } @@ -469,28 +466,6 @@ class Config extends Singleton return $tmp; } - /** - * @deprecated since version 2.0.4 - */ - public function getConfigSuperUserForBackwardCompatibility() - { - try { - $db = Db::get(); - $user = $db->fetchRow("SELECT login, email, password - FROM " . Common::prefixTable("user") . " - WHERE superuser_access = 1 - ORDER BY date_registered ASC LIMIT 1"); - - if (!empty($user)) { - $user['bridge'] = 1; - return $user; - } - - } catch (Exception $e) {} - - return array(); - } - public function getFromGlobalConfig($name) { if (isset($this->configGlobal[$name])) { diff --git a/core/Console.php b/core/Console.php index de51756f39..02e5f86871 100644 --- a/core/Console.php +++ b/core/Console.php @@ -45,6 +45,8 @@ class Console extends Application self::initPlugins(); } catch(\Exception $e) { // Piwik not installed yet, no config file? + + Log::debug("Could not initialize plugins: " . $e->getMessage() . "\n" . $e->getTraceAsString()); } $commands = $this->getAvailableCommands(); @@ -139,7 +141,6 @@ class Console extends Application try { $config->checkLocalConfigFound(); return $config; - } catch (\Exception $e) { $output->writeln($e->getMessage() . "\n"); } @@ -163,6 +164,7 @@ class Console extends Application public static function initPlugins() { Plugin\Manager::getInstance()->loadActivatedPlugins(); + Plugin\Manager::getInstance()->loadPluginTranslations(); } private function getDefaultPiwikCommands() diff --git a/core/DataTable.php b/core/DataTable.php index b060a87245..0b0d9845f0 100644 --- a/core/DataTable.php +++ b/core/DataTable.php @@ -342,6 +342,17 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + * does not update the summary row! + */ + public function setRows($rows) + { + unset($this->rows); + $this->rows = $rows; + $this->indexNotUpToDate = true; + } + + /** * Sorts the DataTable rows using the supplied callback function. * * @param string $functionCallback A comparison callback compatible with {@link usort}. @@ -350,11 +361,11 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess */ public function sort($functionCallback, $columnSortedBy) { - $this->indexNotUpToDate = true; - $this->tableSortedBy = $columnSortedBy; + $this->setTableSortedBy($columnSortedBy); + usort($this->rows, $functionCallback); - if ($this->enableRecursiveSort === true) { + if ($this->isSortRecursiveEnabled()) { foreach ($this->getRows() as $row) { $subTable = $row->getSubtable(); @@ -388,6 +399,23 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + */ + public function isSortRecursiveEnabled() + { + return $this->enableRecursiveSort === true; + } + + /** + * @ignore + */ + public function setTableSortedBy($column) + { + $this->indexNotUpToDate = true; + $this->tableSortedBy = $column; + } + + /** * Enables recursive filtering. If this method is called then the {@link filter()} method * will apply filters to every subtable in addition to this instance. */ @@ -397,6 +425,14 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + */ + public function disableRecursiveFilters() + { + $this->enableRecursiveFilters = false; + } + + /** * Applies a filter to this datatable. * * If {@link enableRecursiveFilters()} was called, the filter will be applied @@ -434,6 +470,25 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * Applies a filter to all subtables but not to this datatable. + * + * @param string|Closure $className Class name, eg. `"Sort"` or "Piwik\DataTable\Filters\Sort"`. If no + * namespace is supplied, `Piwik\DataTable\BaseFilter` is assumed. This parameter + * can also be a closure that takes a DataTable as its first parameter. + * @param array $parameters Array of extra parameters to pass to the filter. + */ + public function filterSubtables($className, $parameters = array()) + { + foreach ($this->getRows() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $subtable->filter($className, $parameters); + $subtable->filterSubtables($className, $parameters); + } + } + } + + /** * Adds a filter and a list of parameters to the list of queued filters. These filters will be * executed when {@link applyQueuedFilters()} is called. * @@ -730,6 +785,14 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + */ + public function getRowsWithoutSummaryRow() + { + return $this->rows; + } + + /** * Returns an array containing all column values for the requested column. * * @param string $name The column name. diff --git a/core/DataTable/Filter/ColumnCallbackAddColumn.php b/core/DataTable/Filter/ColumnCallbackAddColumn.php index 27746c4f28..1d81f189be 100755 --- a/core/DataTable/Filter/ColumnCallbackAddColumn.php +++ b/core/DataTable/Filter/ColumnCallbackAddColumn.php @@ -10,6 +10,7 @@ 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. @@ -83,20 +84,29 @@ class ColumnCallbackAddColumn extends BaseFilter $functionParams = $this->functionParameters; $functionToApply = $this->functionToApply; - foreach ($table->getRows() as $row) { + $extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME); + + if (empty($extraProcessedMetrics)) { + $extraProcessedMetrics = array(); + } - $row->setColumn($this->columnToAdd, function (DataTable\Row $row) use ($columns, $functionParams, $functionToApply) { + $metric = new CallableProcessedMetric($this->columnToAdd, function (DataTable\Row $row) use ($columns, $functionParams, $functionToApply) { - $columnValues = array(); - foreach ($columns as $column) { - $columnValues[] = $row->getColumn($column); - } + $columnValues = array(); + foreach ($columns as $column) { + $columnValues[] = $row->getColumn($column); + } - $parameters = array_merge($columnValues, $functionParams); + $parameters = array_merge($columnValues, $functionParams); - return call_user_func_array($functionToApply, $parameters); - }); + 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); } } diff --git a/core/DataTable/Filter/PivotByDimension.php b/core/DataTable/Filter/PivotByDimension.php index 61e68423e8..3eeff39b44 100644 --- a/core/DataTable/Filter/PivotByDimension.php +++ b/core/DataTable/Filter/PivotByDimension.php @@ -286,13 +286,12 @@ class PivotByDimension extends BaseFilter return null; } - if ($row->isSubtableLoaded()) { - $subtable = $row->getSubtable(); - } else { + $subtable = $row->getSubtable(); + if (!$subtable) { $subtable = $this->thisReport->fetchSubtable($idSubtable, $this->getRequestParamOverride($table)); } - if ($subtable === null) { // sanity check + if (!$subtable) { // sanity check throw new Exception("Unexpected error: could not load subtable '$idSubtable'."); } diff --git a/core/DataTable/Filter/Sort.php b/core/DataTable/Filter/Sort.php index 42441c8e30..632da35dc8 100644 --- a/core/DataTable/Filter/Sort.php +++ b/core/DataTable/Filter/Sort.php @@ -21,7 +21,8 @@ use Piwik\Metrics; * * @api */ -class Sort extends BaseFilter +class +Sort extends BaseFilter { protected $columnToSort; protected $order; @@ -71,34 +72,21 @@ class Sort extends BaseFilter * @param Row $b * @return int */ - public function numberSort($a, $b) + public function numberSort($rowA, $rowB) { - $valA = $this->getColumnValue($a); - $valB = $this->getColumnValue($b); + 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])) { + return -1; + } elseif (!isset($rowA[0])) { + return 1; + } - return !isset($valA) - && !isset($valB) - ? 0 - : ( - !isset($valA) - ? 1 - : ( - !isset($valB) - ? -1 - : (($valA != $valB - || !isset($a->c[Row::COLUMNS]['label'])) - ? ($this->sign * ( - $valA - < $valB - ? -1 - : 1) - ) - : -1 * $this->sign * strnatcasecmp( - $a->c[Row::COLUMNS]['label'], - $b->c[Row::COLUMNS]['label']) - ) - ) - ); + return 0; } /** @@ -108,10 +96,10 @@ class Sort extends BaseFilter * @param mixed $b * @return int */ - function naturalSort($a, $b) + function naturalSort($rowA, $rowB) { - $valA = $this->getColumnValue($a); - $valB = $this->getColumnValue($b); + $valA = $rowA[0]; + $valB = $rowB[0]; return !isset($valA) && !isset($valB) @@ -135,10 +123,10 @@ class Sort extends BaseFilter * @param mixed $b * @return int */ - function sortString($a, $b) + function sortString($rowA, $rowB) { - $valA = $this->getColumnValue($a); - $valB = $this->getColumnValue($b); + $valA = $rowA[0]; + $valB = $rowB[0]; return !isset($valA) && !isset($valB) @@ -243,6 +231,51 @@ class Sort extends BaseFilter } } - $table->sort(array($this, $methodToUse), $this->columnToSort); + $this->sort($table, $methodToUse); } + + /** + * 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(); + foreach ($rows as $key => $row) { + $values[$key] = array($this->getColumnValue($row), $row->getColumn('label')); + } + + uasort($values, array($this, $functionCallback)); + + $sortedRows = array(); + foreach ($values as $key => $value) { + $sortedRows[$key] = $rows[$key]; + } + + $table->setRows(array_values($sortedRows)); + + unset($rows); + unset($sortedRows); + + if ($table->isSortRecursiveEnabled()) { + foreach ($table->getRows() as $row) { + + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->enableRecursiveSort(); + $this->sort($subTable, $functionCallback); + } + } + } + + } + } diff --git a/core/DataTable/Filter/Truncate.php b/core/DataTable/Filter/Truncate.php index 632eb2755e..a8fa0bd08f 100644 --- a/core/DataTable/Filter/Truncate.php +++ b/core/DataTable/Filter/Truncate.php @@ -96,7 +96,7 @@ class Truncate extends BaseFilter return; } - $rows = $table->getRows(); + $rows = array_values($table->getRows()); $count = $table->getRowsCount(); $newRow = new Row(array(Row::COLUMNS => array('label' => DataTable::LABEL_SUMMARY_ROW))); diff --git a/core/DataTable/Map.php b/core/DataTable/Map.php index 8795eac134..8787b06404 100644 --- a/core/DataTable/Map.php +++ b/core/DataTable/Map.php @@ -110,6 +110,19 @@ class Map implements DataTableInterface } /** + * 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); + } + } + + /** * Returns the array of DataTables contained by this class. * * @return DataTable[]|Map[] @@ -185,6 +198,26 @@ 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()}. diff --git a/core/DataTable/Row.php b/core/DataTable/Row.php index 5f21f3fdec..71dde68031 100644 --- a/core/DataTable/Row.php +++ b/core/DataTable/Row.php @@ -209,37 +209,9 @@ class Row implements \ArrayAccess, \IteratorAggregate return false; } - if ($this->isColumnValueCallable($this->c[self::COLUMNS][$name])) { - $value = $this->resolveCallableColumn($name); - - if (!isset($value)) { - return false; - } - - return $value; - } - return $this->c[self::COLUMNS][$name]; } - private function isColumnValueCallable($name) - { - if (! is_callable($name)) { - return false; - } - - if (is_object($name) && ($name instanceof \Closure)) { - return true; - } - - return is_array($name) && isset($name[0]) && is_object($name[0]); - } - - private function resolveCallableColumn($columnName) - { - return call_user_func($this->c[self::COLUMNS][$columnName], $this); - } - /** * Returns the array of all metadata, or one requested metadata value. * @@ -287,16 +259,7 @@ class Row implements \ArrayAccess, \IteratorAggregate */ public function getColumns() { - $values = array(); - foreach ($this->c[self::COLUMNS] as $columnName => $val) { - if ($this->isColumnValueCallable($val)) { - $values[$columnName] = $this->resolveCallableColumn($columnName); - } else { - $values[$columnName] = $val; - } - } - - return $values; + return $this->c[self::COLUMNS]; } /** @@ -517,11 +480,6 @@ class Row implements \ArrayAccess, \IteratorAggregate continue; } - if ($this->isColumnValueCallable($columnToSumValue)) { - $this->setColumn($columnToSumName, $columnToSumValue); - continue; - } - $thisColumnValue = $this->getColumn($columnToSumName); $operation = 'sum'; diff --git a/core/FrontController.php b/core/FrontController.php index bd473bba8f..7c3740d93d 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -14,10 +14,9 @@ use Piwik\API\Request; use Piwik\API\ResponseBuilder; use Piwik\Container\StaticContainer; use Piwik\Exception\AuthenticationFailedException; +use Piwik\Http\ControllerResolver; use Piwik\Http\Router; -use Piwik\Plugin\Controller; use Piwik\Plugin\Report; -use Piwik\Plugin\Widgets; use Piwik\Session; /** @@ -110,83 +109,6 @@ class FrontController extends Singleton } } - protected function makeController($module, $action, &$parameters) - { - $container = StaticContainer::getContainer(); - - $controllerClassName = $this->getClassNameController($module); - - // TRY TO FIND ACTION IN CONTROLLER - if (class_exists($controllerClassName)) { - - $class = $this->getClassNameController($module); - /** @var $controller Controller */ - $controller = $container->make($class); - - $controllerAction = $action; - if ($controllerAction === false) { - $controllerAction = $controller->getDefaultAction(); - } - - if (is_callable(array($controller, $controllerAction))) { - - return array($controller, $controllerAction); - } - - if ($action === false) { - $this->triggerControllerActionNotFoundError($module, $controllerAction); - } - - } - - // TRY TO FIND ACTION IN WIDGET - $widget = Widgets::factory($module, $action); - - if (!empty($widget)) { - - $parameters['widgetModule'] = $module; - $parameters['widgetMethod'] = $action; - - return array($container->make('Piwik\Plugins\CoreHome\Controller'), 'renderWidget'); - } - - // TRY TO FIND ACTION IN REPORT - $report = Report::factory($module, $action); - - if (!empty($report)) { - - $parameters['reportModule'] = $module; - $parameters['reportAction'] = $action; - - return array($container->make('Piwik\Plugins\CoreHome\Controller'), 'renderReportWidget'); - } - - if (!empty($action) && Report::PREFIX_ACTION_IN_MENU === substr($action, 0, strlen(Report - ::PREFIX_ACTION_IN_MENU))) { - $reportAction = lcfirst(substr($action, 4)); // menuGetPageUrls => getPageUrls - $report = Report::factory($module, $reportAction); - - if (!empty($report)) { - $parameters['reportModule'] = $module; - $parameters['reportAction'] = $reportAction; - - return array($container->make('Piwik\Plugins\CoreHome\Controller'), 'renderReportMenu'); - } - } - - $this->triggerControllerActionNotFoundError($module, $action); - } - - protected function triggerControllerActionNotFoundError($module, $action) - { - throw new Exception("Action '$action' not found in the module '$module'."); - } - - protected function getClassNameController($module) - { - return "\\Piwik\\Plugins\\$module\\Controller"; - } - /** * Executes the requested plugin controller method and returns the data, capturing anything the * method `echo`s. @@ -195,7 +117,7 @@ class FrontController extends Singleton * of whatever is in the output buffer._ * * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`. - * @param string $action The controller action name, eg, `'realtimeMap'`. + * @param string $actionName The controller action name, eg, `'realtimeMap'`. * @param array $parameters Array of parameters to pass to the controller action method. * @return string The `echo`'d data or the return value of the controller action. * @deprecated @@ -586,7 +508,10 @@ class FrontController extends Singleton */ Piwik::postEvent('Request.dispatch', array(&$module, &$action, &$parameters)); - list($controller, $actionToCall) = $this->makeController($module, $action, $parameters); + /** @var ControllerResolver $controllerResolver */ + $controllerResolver = StaticContainer::get('Piwik\Http\ControllerResolver'); + + $controller = $controllerResolver->getController($module, $action, $parameters); /** * Triggered directly before controller actions are dispatched. @@ -601,7 +526,7 @@ class FrontController extends Singleton */ Piwik::postEvent(sprintf('Controller.%s.%s', $module, $action), array(&$parameters)); - $result = call_user_func_array(array($controller, $actionToCall), $parameters); + $result = call_user_func_array($controller, $parameters); /** * Triggered after a controller action is successfully called. @@ -627,6 +552,7 @@ class FrontController extends Singleton * @param array $parameters The arguments passed to the controller action. */ Piwik::postEvent('Request.dispatch.end', array(&$result, $module, $action, $parameters)); + return $result; } diff --git a/core/Http.php b/core/Http.php index 25d6d59566..08bcbcef15 100644 --- a/core/Http.php +++ b/core/Http.php @@ -179,7 +179,7 @@ class Http throw new Exception('Malformed URL: ' . $aUrl); } - if ($url['scheme'] != 'http') { + if ($url['scheme'] != 'http' && $url['scheme'] != 'https') { throw new Exception('Invalid protocol/scheme: ' . $url['scheme']); } $host = $url['host']; @@ -405,7 +405,11 @@ class Http } fclose($handle); } else { - $response = file_get_contents($aUrl, 0, $ctx); + $response = @file_get_contents($aUrl, 0, $ctx); + if ($response === false) { + $error = error_get_last(); + throw new \Exception($error['message']); + } $fileLength = strlen($response); } diff --git a/core/Http/ControllerResolver.php b/core/Http/ControllerResolver.php new file mode 100644 index 0000000000..8d58f0ed73 --- /dev/null +++ b/core/Http/ControllerResolver.php @@ -0,0 +1,142 @@ +<?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\Http; + +use DI\FactoryInterface; +use Exception; +use Piwik\Plugin\Controller; +use Piwik\Plugin\Report; +use Piwik\Plugin\Widgets; +use Piwik\Session; + +/** + * Resolves the controller that will handle the request. + * + * A controller is a PHP callable. + */ +class ControllerResolver +{ + /** + * @var FactoryInterface + */ + private $abstractFactory; + + public function __construct(FactoryInterface $abstractFactory) + { + $this->abstractFactory = $abstractFactory; + } + + /** + * @param string $module + * @param string|null $action + * @param array $parameters + * @throws Exception Controller not found. + * @return callable The controller is a PHP callable. + */ + public function getController($module, $action, array &$parameters) + { + $controller = $this->createPluginController($module, $action); + if ($controller) { + return $controller; + } + + $controller = $this->createWidgetController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + $controller = $this->createReportController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + $controller = $this->createReportMenuController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + throw new Exception(sprintf("Action '%s' not found in the module '%s'", $action, $module)); + } + + private function createPluginController($module, $action) + { + $controllerClass = "Piwik\\Plugins\\$module\\Controller"; + if (!class_exists($controllerClass)) { + return null; + } + + /** @var $controller Controller */ + $controller = $this->abstractFactory->make($controllerClass); + + $action = $action ?: $controller->getDefaultAction(); + + if (!is_callable(array($controller, $action))) { + return null; + } + + return array($controller, $action); + } + + private function createWidgetController($module, $action, array &$parameters) + { + $widget = Widgets::factory($module, $action); + + if (!$widget) { + return null; + } + + $parameters['widget'] = $widget; + $parameters['method'] = $action; + + return array($this->createCoreHomeController(), 'renderWidget'); + } + + private function createReportController($module, $action, array &$parameters) + { + $report = Report::factory($module, $action); + + if (!$report) { + return null; + } + + $parameters['report'] = $report; + + return array($this->createCoreHomeController(), 'renderReportWidget'); + } + + private function createReportMenuController($module, $action, array &$parameters) + { + if (!$this->isReportMenuAction($action)) { + return null; + } + + $action = lcfirst(substr($action, 4)); // menuGetPageUrls => getPageUrls + $report = Report::factory($module, $action); + + if (!$report) { + return null; + } + + $parameters['report'] = $report; + + return array($this->createCoreHomeController(), 'renderReportMenu'); + } + + private function isReportMenuAction($action) + { + $startsWithMenu = (Report::PREFIX_ACTION_IN_MENU === substr($action, 0, strlen(Report::PREFIX_ACTION_IN_MENU))); + + return !empty($action) && $startsWithMenu; + } + + private function createCoreHomeController() + { + return $this->abstractFactory->make('Piwik\Plugins\CoreHome\Controller'); + } +} diff --git a/core/IP.php b/core/IP.php index e952ddbe27..b6ec2c2d22 100644 --- a/core/IP.php +++ b/core/IP.php @@ -10,8 +10,6 @@ namespace Piwik; use Piwik\Network\IPUtils; -use Piwik\Network\IPv4; -use Piwik\Network\IPv6; /** * Contains IP address helper functions (for both IPv4 and IPv6). @@ -40,223 +38,6 @@ use Piwik\Network\IPv6; class IP { /** - * Removes the port and the last portion of a CIDR IP address. - * - * @param string $ipString The IP address to sanitize. - * @return string - * - * @deprecated Use IPUtils::sanitizeIp() instead - * @see \Piwik\Network\IPUtils - */ - public static function sanitizeIp($ipString) - { - return IPUtils::sanitizeIp($ipString); - } - - /** - * Sanitize human-readable (user-supplied) IP address range. - * - * Accepts the following formats for $ipRange: - * - single IPv4 address, e.g., 127.0.0.1 - * - single IPv6 address, e.g., ::1/128 - * - IPv4 block using CIDR notation, e.g., 192.168.0.0/22 represents the IPv4 addresses from 192.168.0.0 to 192.168.3.255 - * - IPv6 block using CIDR notation, e.g., 2001:DB8::/48 represents the IPv6 addresses from 2001:DB8:0:0:0:0:0:0 to 2001:DB8:0:FFFF:FFFF:FFFF:FFFF:FFFF - * - wildcards, e.g., 192.168.0.* - * - * @param string $ipRangeString IP address range - * @return string|bool IP address range in CIDR notation OR false - * - * @deprecated Use IPUtils::sanitizeIpRange() instead - * @see \Piwik\Network\IPUtils - */ - public static function sanitizeIpRange($ipRangeString) - { - $result = IPUtils::sanitizeIpRange($ipRangeString); - - return $result === null ? false : $result; - } - - /** - * Converts an IP address in presentation format to network address format. - * - * @param string $ipString IP address, either IPv4 or IPv6, e.g., `"127.0.0.1"`. - * @return string Binary-safe string, e.g., `"\x7F\x00\x00\x01"`. - * - * @deprecated Use IPUtils::stringToBinaryIP() instead - * @see \Piwik\Network\IPUtils - */ - public static function P2N($ipString) - { - return IPUtils::stringToBinaryIP($ipString); - } - - /** - * Convert network address format to presentation format. - * - * See also {@link prettyPrint()}. - * - * @param string $ip IP address in network address format. - * @return string IP address in presentation format. - * - * @deprecated Use IPUtils::binaryToStringIP() instead - */ - public static function N2P($ip) - { - return IPUtils::binaryToStringIP($ip); - } - - /** - * Alias for {@link N2P()}. - * - * @param string $ip IP address in network address format. - * @return string IP address in presentation format. - * - * @deprecated Will be removed - */ - public static function prettyPrint($ip) - { - return IPUtils::binaryToStringIP($ip); - } - - /** - * Returns true if `$ip` is an IPv4, IPv4-compat, or IPv4-mapped address, false - * if otherwise. - * - * @param string $ip IP address in network address format. - * @return bool True if IPv4, else false. - * - * @deprecated Will be removed - * @see \Piwik\Network\IP - */ - public static function isIPv4($ip) - { - $ip = Network\IP::fromBinaryIP($ip); - - return $ip instanceof IPv4; - } - - /** - * Converts an IP address (in network address format) to presentation format. - * This is a backward compatibility function for code that only expects - * IPv4 addresses (i.e., doesn't support IPv6). - * - * This function does not support the long (or its string representation) - * returned by the built-in ip2long() function, from Piwik 1.3 and earlier. - * - * @param string $ip IPv4 address in network address format. - * @return string IP address in presentation format. - * - * @deprecated This method was kept for backward compatibility and doesn't seem used - */ - public static function long2ip($ip) - { - // IPv4 - if (strlen($ip) == 4) { - return IPUtils::binaryToStringIP($ip); - } - - // IPv6 - transitional address? - if (strlen($ip) == 16) { - if (substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 0, 12) === 0 - || substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 12) === 0 - ) { - // remap 128-bit IPv4-mapped and IPv4-compat addresses - return IPUtils::binaryToStringIP(substr($ip, 12)); - } - } - - return '0.0.0.0'; - } - - /** - * Returns true if $ip is an IPv6 address, false if otherwise. This function does - * a naive check. It assumes that whatever format $ip is in, it is well-formed. - * - * @param string $ip - * @return bool - * - * @deprecated Will be removed - * @see \Piwik\Network\IP - */ - public static function isIPv6($ip) - { - $ip = Network\IP::fromBinaryIP($ip); - - return $ip instanceof IPv6; - } - - /** - * Returns true if $ip is a IPv4 mapped address, false if otherwise. - * - * @param string $ip - * @return bool - * - * @deprecated Will be removed - * @see \Piwik\Network\IP - */ - public static function isMappedIPv4($ip) - { - $ip = Network\IP::fromStringIP($ip); - - if (! $ip instanceof IPv6) { - return false; - } - - return $ip->isMappedIPv4(); - } - - /** - * Returns an IPv4 address from a 'mapped' IPv6 address. - * - * @param string $ip eg, `'::ffff:192.0.2.128'` - * @return string eg, `'192.0.2.128'` - * - * @deprecated Use Piwik\Network\IP::toIPv4String() instead - * @see \Piwik\Network\IP - */ - public static function getIPv4FromMappedIPv6($ip) - { - $ip = Network\IP::fromStringIP($ip); - - return $ip->toIPv4String(); - } - - /** - * Get low and high IP addresses for a specified range. - * - * @param array $ipRange An IP address range in presentation format. - * @return array|bool Array `array($lowIp, $highIp)` in network address format, or false on failure. - * - * @deprecated Use Piwik\Network\IPUtils::getIPRangeBounds() instead - * @see \Piwik\Network\IPUtils - */ - public static function getIpsForRange($ipRange) - { - $result = IPUtils::getIPRangeBounds($ipRange); - - return $result === null ? false : $result; - } - - /** - * Determines if an IP address is in a specified IP address range. - * - * An IPv4-mapped address should be range checked with an IPv4-mapped address range. - * - * @param string $ip IP address in network address format - * @param array $ipRanges List of IP address ranges - * @return bool True if in any of the specified IP address ranges; else false. - * - * @deprecated Use Piwik\Network\IP::isInRanges() instead - * @see \Piwik\Network\IP - */ - public static function isIpInRange($ip, $ipRanges) - { - $ip = Network\IP::fromBinaryIP($ip); - - return $ip->isInRanges($ipRanges); - } - - /** * Returns the most accurate IP address availble for the current user, in * IPv4 format. This could be the proxy client's IP address. * @@ -333,22 +114,4 @@ class IP } return trim(Common::sanitizeInputValue($csv)); } - - /** - * Returns the hostname for a given IP address. - * - * @param string $ipStr Human-readable IP address. - * @return string The hostname or unmodified $ipStr on failure. - * - * @deprecated Use Piwik\Network\IP::getHostname() instead - * @see \Piwik\Network\IP - */ - public static function getHostByAddr($ipStr) - { - $ip = Network\IP::fromStringIP($ipStr); - - $host = $ip->getHostname(); - - return $host === null ? $ipStr : $host; - } } diff --git a/core/Menu/MenuAdmin.php b/core/Menu/MenuAdmin.php index 5335091285..9c05734e5f 100644 --- a/core/Menu/MenuAdmin.php +++ b/core/Menu/MenuAdmin.php @@ -33,24 +33,6 @@ use Piwik\Piwik; class MenuAdmin extends MenuAbstract { /** - * Adds a new AdminMenu entry under the 'Settings' category. - * - * @param string $adminMenuName The name of the admin menu entry. Can be a translation token. - * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters - * that can be used to build the URL. - * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the - * current user. If false, the entry will not be added. - * @param int $order The order hint. - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function addEntry($adminMenuName, $url, $displayedForCurrentUser = true, $order = 20) - { - if ($displayedForCurrentUser) { - self::getInstance()->addItem('General_Settings', $adminMenuName, $url, $order); - } - } - - /** * See {@link add()}. Adds a new menu item to the development section of the admin menu. * @param string $menuName * @param array $url @@ -142,12 +124,4 @@ class MenuAdmin extends MenuAbstract return parent::getMenu(); } - - /** - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function removeEntry($menuName, $subMenuName = false) - { - MenuAdmin::getInstance()->remove($menuName, $subMenuName); - } } diff --git a/core/Menu/MenuTop.php b/core/Menu/MenuTop.php index 55c09c57af..87ccb3c5f8 100644 --- a/core/Menu/MenuTop.php +++ b/core/Menu/MenuTop.php @@ -32,37 +32,6 @@ use Piwik\Piwik; class MenuTop extends MenuAbstract { /** - * Adds a new entry to the TopMenu. - * - * @param string $topMenuName The menu item name. Can be a translation token. - * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters - * that can be used to build the URL. If `$isHTML` is true, this can be a string with - * HTML that is simply embedded. - * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the - * current user. If false, the entry will not be added. - * @param int $order The order hint. - * @param bool $isHTML Whether `$url` is an HTML string or a URL that will be rendered as a link. - * @param bool|string $tooltip Optional tooltip to display. - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function addEntry($topMenuName, $url, $displayedForCurrentUser = true, $order = 10, $isHTML = false, $tooltip = false) - { - if ($isHTML) { - MenuTop::getInstance()->addHtml($topMenuName, $url, $displayedForCurrentUser, $order, $tooltip); - } else { - MenuTop::getInstance()->add($topMenuName, null, $url, $displayedForCurrentUser, $order, $tooltip); - } - } - - /** - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function removeEntry($menuName, $subMenuName = false) - { - MenuTop::getInstance()->remove($menuName, $subMenuName); - } - - /** * Directly adds a menu entry containing html. * * @param string $menuName diff --git a/core/Period.php b/core/Period.php index d4044ed68e..b924e981c8 100644 --- a/core/Period.php +++ b/core/Period.php @@ -9,7 +9,6 @@ namespace Piwik; use Piwik\Container\StaticContainer; -use Piwik\Period\Factory as PeriodFactory; use Piwik\Period\Range; use Piwik\Translation\Translator; @@ -66,17 +65,6 @@ abstract class Period } /** - * @deprecated Use Factory::build instead - * @param $period - * @param $date - * @return Period - */ - public static function factory($period, $date) - { - return PeriodFactory::build($period, $date); - } - - /** * Returns true if `$dateString` and `$period` represent multiple periods. * * Will return true for date/period combinations where date references multiple diff --git a/core/Plugin/API.php b/core/Plugin/API.php index cc16cfb64c..4e5095a3aa 100644 --- a/core/Plugin/API.php +++ b/core/Plugin/API.php @@ -41,5 +41,5 @@ use Piwik\Singleton; */ abstract class API extends Singleton { - + } diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php index 6e0d5ba91a..fb11f0a664 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -21,6 +21,7 @@ use Piwik\Log; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugin; +use Piwik\PluginDeactivatedException; use Piwik\Singleton; use Piwik\Theme; use Piwik\Tracker; @@ -266,6 +267,19 @@ class Manager extends Singleton } /** + * Checks whether the given plugin is activated, if not triggers an exception. + * + * @param string $pluginName + * @throws PluginDeactivatedException + */ + public function checkIsPluginActivated($pluginName) + { + if (!$this->isPluginActivated($pluginName)) { + throw new PluginDeactivatedException($pluginName); + } + } + + /** * Returns `true` if plugin is loaded (in memory). * * @param string $name Name of plugin, eg, `'Acions'`. diff --git a/core/Plugin/Metric.php b/core/Plugin/Metric.php index e475eb837f..4122dfeedf 100644 --- a/core/Plugin/Metric.php +++ b/core/Plugin/Metric.php @@ -108,29 +108,44 @@ abstract class Metric */ public static function getMetric($row, $columnName, $mappingNameToId = null) { - if (empty($mappingNameToId)) { - $mappingNameToId = Metrics::getMappingFromNameToId(); - } - if ($row instanceof Row) { $value = $row->getColumn($columnName); - if ($value === false - && isset($mappingNameToId[$columnName]) - ) { - $value = $row->getColumn($mappingNameToId[$columnName]); - } - } else { - $value = @$row[$columnName]; - if ($value === null - && isset($mappingNameToId[$columnName]) - ) { - $columnName = $mappingNameToId[$columnName]; - $value = @$row[$columnName]; + + if ($value === false) { + + if (empty($mappingNameToId)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + if (isset($mappingNameToId[$columnName])) { + return $row->getColumn($mappingNameToId[$columnName]); + } } + return $value; + + } elseif (!empty($row)) { + + if (array_key_exists($columnName, $row)) { + return $row[$columnName]; + + } else { + + if (empty($mappingNameToId)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + if (isset($mappingNameToId[$columnName])) { + $columnName = $mappingNameToId[$columnName]; + + if (array_key_exists($columnName, $row)) { + return $row[$columnName]; + } + } + } } - return $value; + return null; } /** diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php index 62ed594f8c..b9c407e10e 100644 --- a/core/Plugin/Report.php +++ b/core/Plugin/Report.php @@ -182,6 +182,13 @@ class Report protected $order = 1; /** + * Separator for building recursive labels (or paths) + * @var string + * @api + */ + protected $recursiveLabelSeparator = ' - '; + + /** * @var array * @ignore */ @@ -357,6 +364,15 @@ class Report } /** + * @ignore + * @see $recursiveLabelSeparator + */ + public function getRecursiveLabelSeparator() + { + return $this->recursiveLabelSeparator; + } + + /** * Returns an array of supported metrics and their corresponding translations. Eg `array('nb_visits' => 'Visits')`. * By default the given {@link $metrics} are used and their corresponding translations are looked up automatically. * If a metric is not translated, you should add the default metric translation for this metric using diff --git a/core/Plugin/ViewDataTable.php b/core/Plugin/ViewDataTable.php index 4f3b854127..a49358e48b 100644 --- a/core/Plugin/ViewDataTable.php +++ b/core/Plugin/ViewDataTable.php @@ -316,7 +316,7 @@ abstract class ViewDataTable implements ViewInterface return new VizRequest(); } - protected function loadDataTableFromAPI($fixedRequestParams = array()) + protected function loadDataTableFromAPI() { if (!is_null($this->dataTable)) { // data table is already there @@ -324,7 +324,7 @@ abstract class ViewDataTable implements ViewInterface return $this->dataTable; } - $this->dataTable = $this->request->loadDataTableFromAPI($fixedRequestParams); + $this->dataTable = $this->request->loadDataTableFromAPI(); return $this->dataTable; } diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php index 15b468e362..3d48c24d15 100644 --- a/core/Plugin/Visualization.php +++ b/core/Plugin/Visualization.php @@ -10,6 +10,8 @@ namespace Piwik\Plugin; use Piwik\API\DataTablePostProcessor; +use Piwik\API\Proxy; +use Piwik\API\ResponseBuilder; use Piwik\Common; use Piwik\DataTable; use Piwik\Date; @@ -23,6 +25,8 @@ use Piwik\Plugins\API\API as ApiApi; use Piwik\Plugins\PrivacyManager\PrivacyManager; use Piwik\View; use Piwik\ViewDataTable\Manager as ViewDataTableManager; +use Piwik\Plugin\Manager as PluginManager; +use Piwik\API\Request as ApiRequest; /** * The base class for report visualizations that output HTML and use JavaScript. @@ -173,8 +177,7 @@ class Visualization extends ViewDataTable try { $this->beforeLoadDataTable(); - - $this->loadDataTableFromAPI(array('disable_generic_filters' => 1, 'format_metrics' => 0)); + $this->loadDataTableFromAPI(); $this->postDataTableLoadedFromAPI(); $requestPropertiesAfterLoadDataTable = $this->requestConfig->getProperties(); @@ -233,6 +236,35 @@ class Visualization extends ViewDataTable return $view; } + /** + * @internal + */ + protected function loadDataTableFromAPI() + { + if (!is_null($this->dataTable)) { + // data table is already there + // this happens when setDataTable has been used + return $this->dataTable; + } + + // we build the request (URL) to call the API + $request = $this->buildApiRequestArray(); + + $module = $this->requestConfig->getApiModuleToRequest(); + $method = $this->requestConfig->getApiMethodToRequest(); + + PluginManager::getInstance()->checkIsPluginActivated($module); + + $class = ApiRequest::getClassNameAPI($module); + $dataTable = Proxy::getInstance()->call($class, $method, $request); + + $response = new ResponseBuilder($format = 'original', $request); + $response->disableSendHeader(); + $response->disableDataTablePostProcessor(); + + $this->dataTable = $response->getResponse($dataTable, $module, $method); + } + private function getReportMetadata() { $request = $this->request->getRequestArray() + $_GET + $_POST; @@ -255,11 +287,16 @@ class Visualization extends ViewDataTable $this->config->footer_icons = ViewDataTableManager::configureFooterIcons($this); } - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals')) { + if (!$this->isPluginActivated('Goals')) { $this->config->show_goals = false; } } + private function isPluginActivated($pluginName) + { + return PluginManager::getInstance()->isPluginActivated($pluginName); + } + /** * Assigns a template variable making it available in the Twig template specified by * {@link TEMPLATE_FILE}. @@ -357,48 +394,39 @@ class Visualization extends ViewDataTable private function applyFilters() { - list($priorityFilters, $otherFilters) = $this->config->getFiltersToRun(); + $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final + $self = $this; - // First, filters that delete rows - foreach ($priorityFilters as $filter) { - $this->dataTable->filter($filter[0], $filter[1]); - } + $postProcessor->setCallbackBeforeGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self, $postProcessor) { - $this->beforeGenericFiltersAreAppliedToLoadedDataTable(); + // First, filters that delete rows + foreach ($self->config->getPriorityFilters() as $filter) { + $dataTable->filter($filter[0], $filter[1]); + } - if (!in_array($this->requestConfig->filter_sort_column, $this->config->columns_to_display)) { - $hasNbUniqVisitors = in_array('nb_uniq_visitors', $this->config->columns_to_display); - $this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors, $this->dataTable->getColumns()); - } + $self->beforeGenericFiltersAreAppliedToLoadedDataTable(); - $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final + if (!in_array($self->requestConfig->filter_sort_column, $self->config->columns_to_display)) { + $hasNbUniqVisitors = in_array('nb_uniq_visitors', $self->config->columns_to_display); + $columns = $dataTable->getColumns(); + $self->requestConfig->setDefaultSort($self->config->columns_to_display, $hasNbUniqVisitors, $columns); + } - if (!$this->requestConfig->areGenericFiltersDisabled()) { - $this->dataTable = $postProcessor->applyGenericFilters($this->dataTable); - } + $postProcessor->setRequest($self->buildApiRequestArray()); + }); - $postProcessor->applyComputeProcessedMetrics($this->dataTable); + $postProcessor->setCallbackAfterGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self) { - $this->afterGenericFiltersAreAppliedToLoadedDataTable(); + $self->afterGenericFiltersAreAppliedToLoadedDataTable(); - // queue other filters so they can be applied later if queued filters are disabled - foreach ($otherFilters as $filter) { - $this->dataTable->queueFilter($filter[0], $filter[1]); - } + // queue other filters so they can be applied later if queued filters are disabled + foreach ($self->config->getPresentationFilters() as $filter) { + $dataTable->queueFilter($filter[0], $filter[1]); + } - // Finally, apply datatable filters that were queued (should be 'presentation' filters that - // do not affect the number of rows) - if (!$this->requestConfig->areQueuedFiltersDisabled()) { - $this->dataTable->applyQueuedFilters(); - } + }); - if ($this->requestConfig->shouldFormatMetrics()) { - $formatter = $this->metricsFormatter; - $report = $this->report; - $this->dataTable->filter(function (DataTable $table) use ($formatter, $report) { - $formatter->formatMetrics($table, $report); - }); - } + $this->dataTable = $postProcessor->process($this->dataTable); } private function removeEmptyColumnsFromDisplay() @@ -456,7 +484,7 @@ class Visualization extends ViewDataTable */ private function hasReportBeenPurged() { - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('PrivacyManager')) { + if (!$this->isPluginActivated('PrivacyManager')) { return false; } @@ -629,15 +657,14 @@ class Visualization extends ViewDataTable private function makeDataTablePostProcessor() { - $requestArray = $this->request->getRequestArray(); - $request = \Piwik\API\Request::getRequestArrayFromString($requestArray); + $request = $this->buildApiRequestArray(); + $module = $this->requestConfig->getApiModuleToRequest(); + $method = $this->requestConfig->getApiMethodToRequest(); - if (false === $this->config->enable_sort) { - $request['filter_sort_column'] = ''; - $request['filter_sort_order'] = ''; - } + $processor = new DataTablePostProcessor($module, $method, $request); + $processor->setFormatter($this->metricsFormatter); - return new DataTablePostProcessor($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest(), $request); + return $processor; } private function logMessageIfRequestPropertiesHaveChanged(array $requestPropertiesBefore) @@ -685,4 +712,30 @@ class Visualization extends ViewDataTable return $result; } + + /** + * @internal + * + * @return array + */ + public function buildApiRequestArray() + { + $requestArray = $this->request->getRequestArray(); + $request = APIRequest::getRequestArrayFromString($requestArray); + + if (false === $this->config->enable_sort) { + $request['filter_sort_column'] = ''; + $request['filter_sort_order'] = ''; + } + + if (!array_key_exists('format_metrics', $request) || $request['format_metrics'] === 'bc') { + $request['format_metrics'] = '1'; + } + + if (!$this->requestConfig->disable_queued_filters && array_key_exists('disable_queued_filters', $request)) { + unset($request['disable_queued_filters']); + } + + return $request; + } } diff --git a/core/Plugin/Widgets.php b/core/Plugin/Widgets.php index 3199e7a80b..36081fe2d2 100644 --- a/core/Plugin/Widgets.php +++ b/core/Plugin/Widgets.php @@ -125,6 +125,7 @@ class Widgets /** * @ignore + * @return Widgets|null */ public static function factory($module, $action) { diff --git a/core/Session.php b/core/Session.php index 9e49987072..9733593205 100644 --- a/core/Session.php +++ b/core/Session.php @@ -124,8 +124,7 @@ class Session extends Zend_Session we recommend that you <a href='http://piwik.org/faq/how-to-install/#faq_133' rel='noreferrer' target='_blank'>enable database session storage</a>."; } - $pathToSessions = Filechecks::getErrorMessageMissingPermissions(Filesystem::getPathToPiwikRoot() . '/tmp/sessions/'); - $pathToSessions = SettingsPiwik::rewriteTmpPathWithInstanceId($pathToSessions); + $pathToSessions = Filechecks::getErrorMessageMissingPermissions(self::getSessionsDirectory()); $message = sprintf("Error: %s %s %s\n<pre>Debug: the original error was \n%s</pre>", Piwik::translate('General_ExceptionUnableToStartSession'), $pathToSessions, @@ -147,8 +146,7 @@ class Session extends Zend_Session */ public static function getSessionsDirectory() { - $path = PIWIK_USER_PATH . '/tmp/sessions'; - return SettingsPiwik::rewriteTmpPathWithInstanceId($path); + return StaticContainer::get('path.tmp') . '/sessions'; } public static function close() diff --git a/core/SettingsPiwik.php b/core/SettingsPiwik.php index 6a30ddfecb..7a731b9b98 100644 --- a/core/SettingsPiwik.php +++ b/core/SettingsPiwik.php @@ -267,21 +267,6 @@ class SettingsPiwik } /** - * If Piwik uses per-domain config file, also make tmp/ folder per-domain - * @param $path - * @return string - * @throws \Exception - * - * @deprecated Get the 'path.tmp' config from the container instead. - */ - public static function rewriteTmpPathWithInstanceId($path) - { - $tmp = '/tmp/'; - $path = self::rewritePathAppendPiwikInstanceId($path, $tmp); - return $path; - } - - /** * If Piwik uses per-domain config file, make sure CustomLogo is unique * @param $path * @return mixed diff --git a/core/Tracker/VisitExcluded.php b/core/Tracker/VisitExcluded.php index f3d8d0cab2..ae380cac42 100644 --- a/core/Tracker/VisitExcluded.php +++ b/core/Tracker/VisitExcluded.php @@ -11,7 +11,7 @@ namespace Piwik\Tracker; use Piwik\Common; use Piwik\Config; use Piwik\DeviceDetectorFactory; -use Piwik\IP; +use Piwik\Network\IP; use Piwik\Piwik; /** @@ -158,9 +158,10 @@ class VisitExcluded $deviceDetector = DeviceDetectorFactory::getInstance($this->userAgent); + $ip = IP::fromBinaryIP($this->ip); + return !$allowBots - && ($deviceDetector->isBot() - || IP::isIpInRange($this->ip, $this->getBotIpRanges())); + && ($deviceDetector->isBot() || $ip->isInRanges($this->getBotIpRanges())); } protected function getBotIpRanges() @@ -223,7 +224,7 @@ class VisitExcluded $websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite); if (!empty($websiteAttributes['excluded_ips'])) { - $ip = \Piwik\Network\IP::fromBinaryIP($this->ip); + $ip = IP::fromBinaryIP($this->ip); if ($ip->isInRanges($websiteAttributes['excluded_ips'])) { Common::printDebug('Visitor IP ' . $ip->toString() . ' is excluded from being tracked'); return true; diff --git a/core/UrlHelper.php b/core/UrlHelper.php index a0bc340bbd..78682acbfc 100644 --- a/core/UrlHelper.php +++ b/core/UrlHelper.php @@ -231,7 +231,10 @@ class UrlHelper $parsedUrl = parse_url($url); $result = ''; if (isset($parsedUrl['path'])) { - $result .= substr($parsedUrl['path'], 1); + if (substr($parsedUrl['path'], 0, 1) == '/') { + $parsedUrl['path'] = substr($parsedUrl['path'], 1); + } + $result .= $parsedUrl['path']; } if (isset($parsedUrl['query'])) { $result .= '?' . $parsedUrl['query']; diff --git a/core/Version.php b/core/Version.php index 5831690419..0d5a9e14b9 100644 --- a/core/Version.php +++ b/core/Version.php @@ -20,7 +20,7 @@ final class Version * The current Piwik version. * @var string */ - const VERSION = '2.11.2'; + const VERSION = '2.12.0-b1'; public function isStableVersion($version) { diff --git a/core/ViewDataTable/Config.php b/core/ViewDataTable/Config.php index 220a253518..f4eef3c639 100644 --- a/core/ViewDataTable/Config.php +++ b/core/ViewDataTable/Config.php @@ -557,7 +557,7 @@ class Config /** * @ignore */ - public function getFiltersToRun() + private function getFiltersToRun() { $priorityFilters = array(); $presentationFilters = array(); @@ -581,6 +581,20 @@ class Config return array($priorityFilters, $presentationFilters); } + public function getPriorityFilters() + { + $filters = $this->getFiltersToRun(); + + return $filters[0]; + } + + public function getPresentationFilters() + { + $filters = $this->getFiltersToRun(); + + return $filters[1]; + } + /** * Adds a related report to the {@link $related_reports} property. If the report * references the one that is currently being displayed, it will not be added to the related diff --git a/core/ViewDataTable/Request.php b/core/ViewDataTable/Request.php index 352ce25899..259d4caaa1 100644 --- a/core/ViewDataTable/Request.php +++ b/core/ViewDataTable/Request.php @@ -32,15 +32,11 @@ class Request * It builds the API request string and uses Request to call the API. * The requested DataTable object is stored in $this->dataTable. */ - public function loadDataTableFromAPI($fixedRequestParams = array()) + public function loadDataTableFromAPI() { // we build the request (URL) to call the API $requestArray = $this->getRequestArray(); - foreach ($fixedRequestParams as $key => $value) { - $requestArray[$key] = $value; - } - // we make the request to the API $request = new ApiRequest($requestArray); @@ -104,6 +100,14 @@ class Request unset($requestArray['filter_limit']); } + if ($this->requestConfig->disable_generic_filters) { + $requestArray['disable_generic_filters'] = '0'; + } + + if ($this->requestConfig->disable_queued_filters) { + $requestArray['disable_queued_filters'] = 0; + } + return $requestArray; } diff --git a/core/ViewDataTable/RequestConfig.php b/core/ViewDataTable/RequestConfig.php index 753ee55b98..a6c9b7bed8 100644 --- a/core/ViewDataTable/RequestConfig.php +++ b/core/ViewDataTable/RequestConfig.php @@ -314,35 +314,6 @@ class RequestConfig $this->filter_sort_order = 'desc'; } - /** - * Returns `true` if queued filters have been disabled, `false` if otherwise. - * - * @return bool - */ - public function areQueuedFiltersDisabled() - { - return isset($this->disable_queued_filters) && $this->disable_queued_filters; - } - - /** - * Returns `true` if generic filters have been disabled, `false` if otherwise. - * - * @return bool - */ - public function areGenericFiltersDisabled() - { - // if disable_generic_filters query param is set to '1', generic filters are disabled - if (Common::getRequestVar('disable_generic_filters', '0', 'string') == 1) { - return true; - } - - if (isset($this->disable_generic_filters) && true === $this->disable_generic_filters) { - return true; - } - - return false; - } - public function getApiModuleToRequest() { list($module, $method) = explode('.', $this->apiMethodToRequestDataTable); @@ -356,9 +327,4 @@ class RequestConfig return $method; } - - public function shouldFormatMetrics() - { - return Common::getRequestVar('format_metrics', '1', 'string', $this->request_parameters_to_modify) != 0; - } } |