diff options
author | diosmosis <benaka@piwik.pro> | 2015-03-10 18:09:47 +0300 |
---|---|---|
committer | diosmosis <benaka@piwik.pro> | 2015-03-10 18:09:47 +0300 |
commit | ef1d21ec0453684054f4262d50444355b881f9dd (patch) | |
tree | 8ee757ff9af0d93f98b82aa612e528667d496a22 /plugins | |
parent | d77bfb0195657c674f343ff8b9c96fcfefd52a0f (diff) | |
parent | 42da3f8768472598c33be76ddfcd72a6759d3dbc (diff) |
Merge branch 'master' into geo-attribution-task
Diffstat (limited to 'plugins')
35 files changed, 392 insertions, 143 deletions
diff --git a/plugins/API/API.php b/plugins/API/API.php index 2f5f21f4e9..06f04447e2 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -196,7 +196,7 @@ class API extends \Piwik\Plugin\API 'segment' => 'visitIp', 'acceptedValues' => '13.54.122.1. </code>Select IP ranges with notation: <code>visitIp>13.54.122.0;visitIp<13.54.122.255', 'sqlSegment' => 'log_visit.location_ip', - 'sqlFilterValue' => array('Piwik\IP', 'P2N'), + 'sqlFilterValue' => array('Piwik\Network\IPUtils', 'stringToBinaryIP'), 'permission' => $isAuthenticatedWithViewAccess, ); diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index 91f1d70c45..7204ddfa50 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -9,7 +9,6 @@ namespace Piwik\Plugins\Actions; use Exception; -use Piwik\API\Request; use Piwik\Archive; use Piwik\Common; use Piwik\DataTable; @@ -84,14 +83,17 @@ class API extends \Piwik\Plugin\API * @param bool $expanded * @param bool|int $idSubtable * @param bool|int $depth + * @param bool|int $flat * * @return DataTable|DataTable\Map */ public function getPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, - $depth = false) + $depth = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); - $this->filterActionsDataTable($dataTable, $expanded); + $dataTable = Archive::createDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable, $depth); + + $this->filterActionsDataTable($dataTable); + return $dataTable; } @@ -167,17 +169,19 @@ class API extends \Piwik\Plugin\API public function getPageUrl($pageUrl, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Action::TYPE_PAGE_URL); $this->addPageProcessedMetrics($dataTable); $this->filterActionsDataTable($dataTable); return $dataTable; } - public function getPageTitles($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + public function getPageTitles($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterActionsDataTable($dataTable, $expanded); + $dataTable = Archive::createDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); + + $this->filterActionsDataTable($dataTable); + return $dataTable; } @@ -207,38 +211,38 @@ class API extends \Piwik\Plugin\API public function getPageTitle($pageName, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Action::TYPE_PAGE_TITLE); $this->addPageProcessedMetrics($dataTable); $this->filterActionsDataTable($dataTable); return $dataTable; } - public function getDownloads($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + public function getDownloads($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $dataTable = Archive::createDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); $this->filterActionsDataTable($dataTable, $expanded); return $dataTable; } public function getDownload($downloadUrl, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $downloadUrl, Action::TYPE_DOWNLOAD); $this->filterActionsDataTable($dataTable); return $dataTable; } - public function getOutlinks($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + public function getOutlinks($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $dataTable = Archive::createDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); $this->filterActionsDataTable($dataTable, $expanded); return $dataTable; } public function getOutlink($outlinkUrl, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $outlinkUrl, Action::TYPE_OUTLINK); $this->filterActionsDataTable($dataTable); return $dataTable; @@ -249,6 +253,7 @@ class API extends \Piwik\Plugin\API $dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment); $dataTable->deleteColumn(PiwikMetrics::INDEX_SITE_SEARCH_HAS_NO_RESULT); $this->filterActionsDataTable($dataTable); + $dataTable->filter('ReplaceColumnNames'); $this->addPagesPerSearchColumn($dataTable); return $dataTable; } @@ -266,7 +271,7 @@ class API extends \Piwik\Plugin\API protected function getSiteSearchKeywordsRaw($idSite, $period, $date, $segment) { - $dataTable = $this->getDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false); + $dataTable = Archive::createDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false); return $dataTable; } @@ -284,6 +289,7 @@ class API extends \Piwik\Plugin\API $dataTable->deleteRow(DataTable::ID_SUMMARY_ROW); $dataTable->deleteColumn(PiwikMetrics::INDEX_SITE_SEARCH_HAS_NO_RESULT); $this->filterActionsDataTable($dataTable); + $dataTable->filter('ReplaceColumnNames'); $this->addPagesPerSearchColumn($dataTable); return $dataTable; } @@ -332,6 +338,7 @@ class API extends \Piwik\Plugin\API } } $this->filterActionsDataTable($dataTable); + $dataTable->filter('ReplaceColumnNames'); $this->addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_actions'); return $dataTable; } @@ -361,7 +368,7 @@ class API extends \Piwik\Plugin\API if ($table === false) { // fetch the data table - $table = call_user_func_array(array($this, 'getDataTableFromArchive'), $callBackParameters); + $table = call_user_func_array('\Piwik\Archive::createDataTableFromArchive', $callBackParameters); if ($table instanceof DataTable\Map) { // search an array of tables, e.g. when using date=last30 @@ -424,7 +431,7 @@ class API extends \Piwik\Plugin\API // match found on this level and more levels remaining: go deeper $idSubTable = $row->getIdSubDataTable(); - $callBackParameters[6] = $idSubTable; + $callBackParameters[7] = $idSubTable; /** * @var \Piwik\Period $period @@ -434,7 +441,7 @@ class API extends \Piwik\Plugin\API $callBackParameters[3] = $period->getDateStart() . ',' . $period->getDateEnd(); } - $table = call_user_func_array(array($this, 'getDataTableFromArchive'), $callBackParameters); + $table = call_user_func_array('\Piwik\Archive::createDataTableFromArchive', $callBackParameters); return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree); } @@ -445,28 +452,15 @@ class API extends \Piwik\Plugin\API * Common filters for all Actions API * * @param DataTable|DataTable\Simple|DataTable\Map $dataTable - * @param bool $expanded */ - protected function filterActionsDataTable($dataTable, $expanded = false) + private function filterActionsDataTable($dataTable) { // Must be applied before Sort in this case, since the DataTable can contain both int and strings indexes // (in the transition period between pre 1.2 and post 1.2 datatable structure) - $dataTable->filter('ReplaceColumnNames'); - $dataTable->filter('Sort', array('nb_visits', 'desc', $naturalSort = false, $expanded)); - $dataTable->filter(function (DataTable $dataTable) { - foreach ($dataTable->getRows() as $row) { - $url = $row->getMetadata('url'); - if ($url) { - $row->setMetadata('segmentValue', urldecode($url)); - } - } - }); - $dataTable->filter('GroupBy', array('label', function ($label) { - return urldecode($label); - })); + $dataTable->filter('Piwik\Plugins\Actions\DataTable\Filter\Actions'); - $dataTable->queueFilter('ReplaceSummaryRowLabel'); + return $dataTable; } /** @@ -500,11 +494,6 @@ class API extends \Piwik\Plugin\API ); } - protected function getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $depth = null) - { - return Archive::getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); - } - private function addPageProcessedMetrics(DataTable\DataTableInterface $dataTable) { $dataTable->filter(function (DataTable $table) { diff --git a/plugins/Actions/DataTable/Filter/Actions.php b/plugins/Actions/DataTable/Filter/Actions.php new file mode 100644 index 0000000000..71abe4190b --- /dev/null +++ b/plugins/Actions/DataTable/Filter/Actions.php @@ -0,0 +1,53 @@ +<?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\Plugins\Actions\DataTable\Filter; + +use Piwik\DataTable\BaseFilter; +use Piwik\DataTable\Row; +use Piwik\DataTable; + +class Actions extends BaseFilter +{ + /** + * Constructor. + * + * @param DataTable $table The table to eventually filter. + */ + public function __construct($table) + { + parent::__construct($table); + } + + /** + * @param DataTable $table + */ + public function filter($table) + { + $table->filter(function (DataTable $dataTable) { + foreach ($dataTable->getRows() as $row) { + $url = $row->getMetadata('url'); + if ($url) { + $row->setMetadata('segmentValue', urldecode($url)); + } + } + }); + + // TODO can we remove this one again? + $table->queueFilter('GroupBy', array('label', function ($label) { + return urldecode($label); + })); + + foreach ($table->getRows() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $this->filter($subtable); + } + } + } +}
\ No newline at end of file diff --git a/plugins/Actions/Reports/Base.php b/plugins/Actions/Reports/Base.php index 68bca741a3..6d7146ae73 100644 --- a/plugins/Actions/Reports/Base.php +++ b/plugins/Actions/Reports/Base.php @@ -22,6 +22,7 @@ abstract class Base extends \Piwik\Plugin\Report { $this->category = 'General_Actions'; $this->processedMetrics = false; + $this->recursiveLabelSeparator = '/'; } protected function addBaseDisplayProperties(ViewDataTable $view) diff --git a/plugins/CoreAdminHome/Model/DuplicateActionRemover.php b/plugins/CoreAdminHome/Model/DuplicateActionRemover.php index 21a5613616..bd3f143b09 100644 --- a/plugins/CoreAdminHome/Model/DuplicateActionRemover.php +++ b/plugins/CoreAdminHome/Model/DuplicateActionRemover.php @@ -56,7 +56,7 @@ class DuplicateActionRemover * @param TableMetadata $tableMetadataAccess * @param LoggerInterface $logger */ - public function __construct($tableMetadataAccess = null, $logger = null) + public function __construct(TableMetadata $tableMetadataAccess = null, LoggerInterface $logger = null) { $this->tableMetadataAccess = $tableMetadataAccess ?: new TableMetadata(); $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); diff --git a/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php b/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php new file mode 100644 index 0000000000..646c48e862 --- /dev/null +++ b/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php @@ -0,0 +1,47 @@ +<?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\Plugins\CoreHome\Columns\Metrics; + +use Piwik\DataTable\Row; +use Piwik\Plugin\ProcessedMetric; + +class CallableProcessedMetric extends ProcessedMetric +{ + private $name; + private $callback; + private $dependentMetrics; + + public function __construct($name, $callback, $dependentMetrics = array()) + { + $this->name = $name; + $this->callback = $callback; + $this->dependentMetrics = $dependentMetrics; + } + + public function getName() + { + return $this->name; + } + + public function compute(Row $row) + { + if ($this->callback) { + return call_user_func($this->callback, $row); + } + } + + public function getTranslatedName() + { + return ''; + } + + public function getDependentMetrics() + { + return $this->dependentMetrics; + } +}
\ No newline at end of file diff --git a/plugins/CoreHome/Controller.php b/plugins/CoreHome/Controller.php index 7fdfe1d814..38474e5bb7 100644 --- a/plugins/CoreHome/Controller.php +++ b/plugins/CoreHome/Controller.php @@ -49,17 +49,11 @@ class Controller extends \Piwik\Plugin\Controller return 'redirectToCoreHomeIndex'; } - public function renderReportMenu($reportModule = null, $reportAction = null) + public function renderReportMenu(Report $report) { Piwik::checkUserHasSomeViewAccess(); $this->checkSitePermission(); - $report = Report::factory($reportModule, $reportAction); - - if (empty($report)) { - throw new Exception($this->translator->translate('General_ExceptionReportNotFound')); - } - $report->checkIsEnabled(); $menuTitle = $report->getMenuTitle(); @@ -69,38 +63,26 @@ class Controller extends \Piwik\Plugin\Controller } $menuTitle = $this->translator->translate($menuTitle); - $content = $this->renderReportWidget($reportModule, $reportAction); + $content = $this->renderReportWidget($report); return View::singleReport($menuTitle, $content); } - public function renderReportWidget($reportModule = null, $reportAction = null) + public function renderReportWidget(Report $report) { Piwik::checkUserHasSomeViewAccess(); $this->checkSitePermission(); - $report = Report::factory($reportModule, $reportAction); - - if (empty($report)) { - throw new Exception($this->translator->translate('General_ExceptionReportNotFound')); - } - $report->checkIsEnabled(); return $report->render(); } - public function renderWidget($widgetModule = null, $widgetAction = null) + public function renderWidget(PluginWidgets $widget, $method) { Piwik::checkUserHasSomeViewAccess(); - $widget = PluginWidgets::factory($widgetModule, $widgetAction); - - if (!empty($widget)) { - return $widget->$widgetAction(); - } - - throw new Exception($this->translator->translate('General_ExceptionWidgetNotFound')); + return $widget->$method(); } function redirectToCoreHomeIndex() diff --git a/plugins/CoreHome/DataTableRowAction/RowEvolution.php b/plugins/CoreHome/DataTableRowAction/RowEvolution.php index e1bc8d5d4f..85fa98bc3e 100644 --- a/plugins/CoreHome/DataTableRowAction/RowEvolution.php +++ b/plugins/CoreHome/DataTableRowAction/RowEvolution.php @@ -198,6 +198,7 @@ class RowEvolution $view->config->columns_to_display = array_keys($metrics ? : $this->graphMetrics); } + $view->requestConfig->request_parameters_to_modify['label'] = ''; $view->config->show_goals = false; $view->config->show_search = false; $view->config->show_all_views_icons = false; diff --git a/plugins/CorePluginsAdmin/MarketplaceApiClient.php b/plugins/CorePluginsAdmin/MarketplaceApiClient.php index 6472da3dea..37020ef8cd 100644 --- a/plugins/CorePluginsAdmin/MarketplaceApiClient.php +++ b/plugins/CorePluginsAdmin/MarketplaceApiClient.php @@ -18,7 +18,7 @@ use Piwik\Version; class MarketplaceApiClient { const CACHE_TIMEOUT_IN_SECONDS = 1200; - const HTTP_REQUEST_TIMEOUT = 3; + const HTTP_REQUEST_TIMEOUT = 10; private $domain = 'http://plugins.piwik.org'; diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php index 90b0d13aa5..c8f3e927b5 100644 --- a/plugins/CoreUpdater/Controller.php +++ b/plugins/CoreUpdater/Controller.php @@ -37,6 +37,8 @@ use Piwik\View; class Controller extends \Piwik\Plugin\Controller { const PATH_TO_EXTRACT_LATEST_VERSION = '/latest/'; + const LATEST_VERSION_URL = '://builds.piwik.org/piwik.zip'; + const LATEST_BETA_VERSION_URL = '://builds.piwik.org/piwik-%s.zip'; private $coreError = false; private $warningMessages = array(); @@ -48,9 +50,18 @@ class Controller extends \Piwik\Plugin\Controller protected static function getLatestZipUrl($newVersion) { if (@Config::getInstance()->Debug['allow_upgrades_to_beta']) { - return 'http://builds.piwik.org/piwik-' . $newVersion . '.zip'; + $url = sprintf(self::LATEST_BETA_VERSION_URL, $newVersion); + } else { + $url = self::LATEST_VERSION_URL; + } + + if (self::isUpdatingOverHttps()) { + $url = 'https' . $url; + } else { + $url = 'http' . $url; } - return Config::getInstance()->General['latest_version_url']; + + return $url; } public function newVersionAvailable() @@ -426,4 +437,11 @@ class Controller extends \Piwik\Plugin\Controller return PluginManager::getInstance()->getIncompatiblePlugins($piwikVersion); } + public static function isUpdatingOverHttps() + { + $openSslEnabled = extension_loaded('openssl'); + $usingMethodSupportingHttps = (Http::getTransportMethod() !== 'socket'); + + return $openSslEnabled && $usingMethodSupportingHttps; + } } diff --git a/plugins/CoreVisualizations/CoreVisualizations.php b/plugins/CoreVisualizations/CoreVisualizations.php index 54aa127c91..da8fbe9f94 100644 --- a/plugins/CoreVisualizations/CoreVisualizations.php +++ b/plugins/CoreVisualizations/CoreVisualizations.php @@ -9,6 +9,7 @@ namespace Piwik\Plugins\CoreVisualizations; +use Piwik\Common; use Piwik\ViewDataTable\Manager as ViewDataTableManager; require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/JqplotDataGenerator.php'; @@ -28,7 +29,8 @@ class CoreVisualizations extends \Piwik\Plugin 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles', 'AssetManager.getJavaScriptFiles' => 'getJsFiles', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', - 'UsersManager.deleteUser' => 'deleteUser' + 'UsersManager.deleteUser' => 'deleteUser', + 'ViewDataTable.addViewDataTable' => 'addViewDataTable' ); } @@ -37,6 +39,23 @@ class CoreVisualizations extends \Piwik\Plugin ViewDataTableManager::clearUserViewDataTableParameters($userLogin); } + public function addViewDataTable(&$viewDataTable) + { + // Both are the same HtmlTable, just the Pivot one has some extra logic in case Pivot is used. + // We don't want to use the same HtmlTable twice in the UI. Therefore we always need to remove one. + if (Common::getRequestVar('pivotBy', '')) { + $tableToRemove = 'Visualizations\HtmlTable'; + } else { + $tableToRemove = 'HtmlTable\PivotBy'; + } + + foreach ($viewDataTable as $index => $table) { + if (Common::stringEndsWith($table, $tableToRemove)) { + unset($viewDataTable[$index]); + } + } + } + public function getStylesheetFiles(&$stylesheets) { $stylesheets[] = "plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less"; diff --git a/plugins/CoreVisualizations/Visualizations/HtmlTable.php b/plugins/CoreVisualizations/Visualizations/HtmlTable.php index dcd2374df9..66cb4c26e9 100644 --- a/plugins/CoreVisualizations/Visualizations/HtmlTable.php +++ b/plugins/CoreVisualizations/Visualizations/HtmlTable.php @@ -70,10 +70,6 @@ class HtmlTable extends Visualization $dataTable = $request->process(); $this->assignTemplateVar('siteSummary', $dataTable); } - - if ($this->requestConfig->pivotBy) { - $this->config->columns_to_display = $this->dataTable->getColumns(); - } } } diff --git a/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php b/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php new file mode 100644 index 0000000000..1703988599 --- /dev/null +++ b/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php @@ -0,0 +1,36 @@ +<?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\Plugins\CoreVisualizations\Visualizations\HtmlTable; + +use Piwik\DataTable; +use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable; +use Piwik\View; + +/** + * DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true. + */ +class PivotBy extends HtmlTable +{ + public function beforeGenericFiltersAreAppliedToLoadedDataTable() + { + $this->config->columns_to_display = $this->dataTable->getColumns(); + + $this->dataTable->applyQueuedFilters(); + + parent::beforeGenericFiltersAreAppliedToLoadedDataTable(); + } + + public function beforeRender() + { + parent::beforeRender(); + + $this->config->columns_to_display = $this->dataTable->getColumns(); + } +} diff --git a/plugins/CustomAlerts b/plugins/CustomAlerts -Subproject 40db402aeacae2d911331ec94df5d86df6f8713 +Subproject 628d2086db2742a29f39173c8be2fd348a64c6a diff --git a/plugins/Events/API.php b/plugins/Events/API.php index bb6584b777..7950cd2637 100644 --- a/plugins/Events/API.php +++ b/plugins/Events/API.php @@ -148,14 +148,17 @@ class API extends \Piwik\Plugin\API } } - protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $secondaryDimension = false) + protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $secondaryDimension = false, $flat = false) { Piwik::checkUserHasViewAccess($idSite); $this->checkSecondaryDimension($name, $secondaryDimension); $recordName = $this->getRecordNameForAction($name, $secondaryDimension); - $dataTable = Archive::getDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $idSubtable); - if (empty($idSubtable)) { + $dataTable = Archive::createDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); + + if ($flat) { + $dataTable->filterSubtables('Piwik\Plugins\Events\DataTable\Filter\ReplaceEventNameNotSet'); + } else { $dataTable->filter('AddSegmentValue', array(function ($label) { if ($label === Archiver::EVENT_NAME_NOT_SET) { return false; @@ -165,23 +168,24 @@ class API extends \Piwik\Plugin\API })); } - $this->filterDataTable($dataTable); + $dataTable->filter('Piwik\Plugins\Events\DataTable\Filter\ReplaceEventNameNotSet'); + return $dataTable; } - public function getCategory($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false) + public function getCategory($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false, $flat = false) { - return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension); + return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension, $flat); } - public function getAction($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false) + public function getAction($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false, $flat = false) { - return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension); + return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension, $flat); } - public function getName($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false) + public function getName($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false, $flat = false) { - return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension); + return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension, $flat); } public function getActionFromCategoryId($idSite, $period, $date, $idSubtable, $segment = false) @@ -213,20 +217,4 @@ class API extends \Piwik\Plugin\API { return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); } - - /** - * @param DataTable $dataTable - */ - protected function filterDataTable($dataTable) - { - $dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS)); - $dataTable->queueFilter('ReplaceColumnNames'); - $dataTable->queueFilter('ReplaceSummaryRowLabel'); - $dataTable->filter(function (DataTable $table) { - $row = $table->getRowFromLabel(Archiver::EVENT_NAME_NOT_SET); - if ($row) { - $row->setColumn('label', Piwik::translate('General_NotDefined', Piwik::translate('Events_EventName'))); - } - }); - } }
\ No newline at end of file diff --git a/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php b/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php new file mode 100644 index 0000000000..a07695b5f8 --- /dev/null +++ b/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php @@ -0,0 +1,39 @@ +<?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\Plugins\Events\DataTable\Filter; + +use Piwik\DataTable\BaseFilter; +use Piwik\DataTable\Row; +use Piwik\DataTable; +use Piwik\Piwik; +use Piwik\Plugins\Events\Archiver; + +class ReplaceEventNameNotSet extends BaseFilter +{ + /** + * Constructor. + * + * @param DataTable $table The table to eventually filter. + */ + public function __construct($table) + { + parent::__construct($table); + } + + /** + * @param DataTable $table + */ + public function filter($table) + { + $row = $table->getRowFromLabel(Archiver::EVENT_NAME_NOT_SET); + if ($row) { + $row->setColumn('label', Piwik::translate('General_NotDefined', Piwik::translate('Events_EventName'))); + } + } +}
\ No newline at end of file diff --git a/plugins/ExampleUI/Controller.php b/plugins/ExampleUI/Controller.php index 9bfd517aa0..210093c8b5 100644 --- a/plugins/ExampleUI/Controller.php +++ b/plugins/ExampleUI/Controller.php @@ -90,6 +90,7 @@ class Controller extends \Piwik\Plugin\Controller $view = $this->getLastUnitGraphAcrossPlugins($this->pluginName, __FUNCTION__, $columns, $selectableColumns = array('server1', 'server2'), 'My documentation', 'ExampleUI.getTemperaturesEvolution'); $view->requestConfig->filter_sort_column = 'label'; + $view->requestConfig->filter_sort_order = 'asc'; if (empty($view->config->columns_to_display) && !empty($defaultColumns)) { $view->config->columns_to_display = $defaultColumns; diff --git a/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php b/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php index 1ae1c41149..d32290bfdc 100644 --- a/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php +++ b/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php @@ -9,9 +9,11 @@ namespace Piwik\Plugins\Goals\Columns\Metrics; use Piwik\Common; use Piwik\DataTable\Row; +use Piwik\Metrics; use Piwik\Piwik; use Piwik\Plugin\ProcessedMetric; use Piwik\Plugins\Goals\API as GoalsAPI; +use Piwik\Tracker\GoalManager; /** * Base class for processed metrics that are calculated using metrics that are @@ -60,6 +62,16 @@ abstract class GoalSpecificProcessedMetric extends ProcessedMetric $alternateKey = 'idgoal=' . $this->idGoal; if (isset($allGoalMetrics[$alternateKey])) { return $allGoalMetrics[$alternateKey]; + } elseif ($this->idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { + $alternateKey = GoalManager::IDGOAL_ORDER; + if (isset($allGoalMetrics[$alternateKey])) { + return $allGoalMetrics[$alternateKey]; + } + } elseif ($this->idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { + $alternateKey = GoalManager::IDGOAL_CART; + if (isset($allGoalMetrics[$alternateKey])) { + return $allGoalMetrics[$alternateKey]; + } } else { return array(); } diff --git a/plugins/Installation/SystemCheck.php b/plugins/Installation/SystemCheck.php index de55506884..93ed1ac75f 100644 --- a/plugins/Installation/SystemCheck.php +++ b/plugins/Installation/SystemCheck.php @@ -19,6 +19,7 @@ use Piwik\Filechecks; use Piwik\Filesystem; use Piwik\Http; use Piwik\Piwik; +use Piwik\Plugins\CoreUpdater; use Piwik\Plugins\UserCountry\LocationProvider; use Piwik\SettingsServer; use Piwik\Url; @@ -102,6 +103,9 @@ class SystemCheck $infos['tracker_status'] = Common::getRequestVar('trackerStatus', 0, 'int'); $infos['is_nfs'] = Filesystem::checkIfFileSystemIsNFS(); + + $infos['https_update'] = CoreUpdater\Controller::isUpdatingOverHttps(); + $infos = self::enrichSystemChecks($infos); return $infos; diff --git a/plugins/Installation/lang/en.json b/plugins/Installation/lang/en.json index 9711d997c3..2d3afbf8c9 100644 --- a/plugins/Installation/lang/en.json +++ b/plugins/Installation/lang/en.json @@ -115,6 +115,8 @@ "SystemCheckCronArchiveProcess": "Archive Cron", "SystemCheckCronArchiveProcessCLI": "Managing processes via CLI", "SystemCheckPhpSetting": "To prevent some critical issue, you must set the following in your php.ini file: %s", + "SystemCheckUpdateHttps": "Update over HTTPS", + "SystemCheckUpdateHttpsNotSupported": "Piwik cannot use HTTPS to update, it will fall back to the insecure HTTP update. Check that CURL or allow_url_fopen is supported and that the openssl PHP extension is installed: http://piwik.org/faq/troubleshooting/faq_177/.", "NotSupported": "not supported", "Tables": "Creating the Tables", "TablesCreatedSuccess": "Tables created with success!", diff --git a/plugins/Installation/templates/_systemCheckSection.twig b/plugins/Installation/templates/_systemCheckSection.twig index f797d18969..b56db9b672 100755 --- a/plugins/Installation/templates/_systemCheckSection.twig +++ b/plugins/Installation/templates/_systemCheckSection.twig @@ -356,6 +356,17 @@ </tr> {% endif %} + <tr> + <td class="label">{{ 'Installation_SystemCheckUpdateHttps'|translate }}</td> + <td> + {% if infos.https_update %} + {{ ok }} + {% else %} + {{ warning }} {{ 'Installation_SystemCheckUpdateHttpsNotSupported'|translate }} + {% endif %} + </td> + </tr> + </table> {% include "@Installation/_integrityDetails.twig" %} diff --git a/plugins/Provider/Columns/Provider.php b/plugins/Provider/Columns/Provider.php index 24ab6cecd2..b7ca57e9c9 100644 --- a/plugins/Provider/Columns/Provider.php +++ b/plugins/Provider/Columns/Provider.php @@ -9,7 +9,7 @@ namespace Piwik\Plugins\Provider\Columns; use Piwik\Common; -use Piwik\IP; +use Piwik\Network\IP; use Piwik\Network\IPUtils; use Piwik\Piwik; use Piwik\Plugin\Dimension\VisitDimension; @@ -87,12 +87,17 @@ class Provider extends VisitDimension /** * Returns the hostname given the IP address string * - * @param string $ip IP Address + * @param string $ipStr IP Address * @return string hostname (or human-readable IP address) */ - private function getHost($ip) + private function getHost($ipStr) { - return trim(strtolower(@IP::getHostByAddr($ip))); + $ip = IP::fromStringIP($ipStr); + + $host = $ip->getHostname(); + $host = ($host === null ? $ipStr : $host); + + return trim(strtolower($host)); } public function getName() diff --git a/plugins/QueuedTracking b/plugins/QueuedTracking -Subproject ff1228265cc695fbec8b2997f442ed0bb90f289 +Subproject bc39d75e4c9df58c865ae44f6eae23676e7baf9 diff --git a/plugins/Referrers/API.php b/plugins/Referrers/API.php index 4ac28978e3..aa1a3e0419 100644 --- a/plugins/Referrers/API.php +++ b/plugins/Referrers/API.php @@ -43,7 +43,6 @@ class API extends \Piwik\Plugin\API protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null) { $dataTable = Archive::getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable); - $dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); $dataTable->queueFilter('ReplaceColumnNames'); return $dataTable; } @@ -296,22 +295,24 @@ class API extends \Piwik\Plugin\API return $dataTable; } - public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false) + public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false, $flat = false) { - $dataTable = $this->getDataTable(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); - $dataTable->filter('AddSegmentByLabel', array('referrerName')); + $dataTable = Archive::createDataTableFromArchive(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable = null); + + if ($flat) { + $dataTable->filterSubtables('Piwik\Plugins\Referrers\DataTable\Filter\UrlsFromWebsiteId'); + } else { + $dataTable->filter('AddSegmentByLabel', array('referrerName')); + } + return $dataTable; } public function getUrlsFromWebsiteId($idSite, $period, $date, $idSubtable, $segment = false) { $dataTable = $this->getDataTable(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); - // the htmlspecialchars_decode call is for BC for before 1.1 - // as the Referrer URL was previously encoded in the log tables, but is now recorded raw - $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', function ($label) { - return htmlspecialchars_decode($label); - })); - $dataTable->queueFilter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\getPathFromUrl')); + $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\UrlsFromWebsiteId'); + return $dataTable; } diff --git a/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php b/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php new file mode 100644 index 0000000000..c35d60b463 --- /dev/null +++ b/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php @@ -0,0 +1,46 @@ +<?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\Plugins\Referrers\DataTable\Filter; + +use Piwik\DataTable\BaseFilter; +use Piwik\DataTable\Row; +use Piwik\DataTable; + +class UrlsFromWebsiteId extends BaseFilter +{ + /** + * Constructor. + * + * @param DataTable $table The table to eventually filter. + */ + public function __construct($table) + { + parent::__construct($table); + } + + /** + * @param DataTable $table + */ + public function filter($table) + { + // the htmlspecialchars_decode call is for BC for before 1.1 + // as the Referrer URL was previously encoded in the log tables, but is now recorded raw + $table->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', function ($label) { + return htmlspecialchars_decode($label); + })); + $table->queueFilter('ColumnCallbackReplace', array('label', 'Piwik\Plugins\Referrers\getPathFromUrl')); + + foreach ($table->getRows() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $this->filter($subtable); + } + } + } +}
\ No newline at end of file diff --git a/plugins/Referrers/Reports/GetWebsites.php b/plugins/Referrers/Reports/GetWebsites.php index e394eb43d9..18f9336a63 100644 --- a/plugins/Referrers/Reports/GetWebsites.php +++ b/plugins/Referrers/Reports/GetWebsites.php @@ -21,6 +21,7 @@ class GetWebsites extends Base $this->dimension = new Website(); $this->name = Piwik::translate('CorePluginsAdmin_Websites'); $this->documentation = Piwik::translate('Referrers_WebsitesReportDocumentation', '<br />'); + $this->recursiveLabelSeparator = '/'; $this->actionToLoadSubTables = 'getUrlsFromWebsiteId'; $this->hasGoalMetrics = true; $this->order = 5; diff --git a/plugins/SecurityInfo b/plugins/SecurityInfo -Subproject a7e2d59ac96e39e1141808fd944e863dc9588e8 +Subproject f037ffc0d6402d084ff1b6525b4c010f56e4276 diff --git a/plugins/TasksTimetable b/plugins/TasksTimetable -Subproject 4af9daf3388f59159cf0ff9eb60b8bc38461149 +Subproject a0f38fec7cf905d34059ec1b6a207156adf7579 diff --git a/plugins/TestRunner/Aws/config.ini.php b/plugins/TestRunner/Aws/config.ini.php index a6111e5685..65c4498b89 100644 --- a/plugins/TestRunner/Aws/config.ini.php +++ b/plugins/TestRunner/Aws/config.ini.php @@ -12,6 +12,7 @@ charset = "utf8" request_uri = "/" [database_tests] +username = "root" password = "secure" tables_prefix = "" diff --git a/plugins/TestRunner/Commands/TestsRunOnAws.php b/plugins/TestRunner/Commands/TestsRunOnAws.php index 8ff81fa0a5..65bce267e0 100644 --- a/plugins/TestRunner/Commands/TestsRunOnAws.php +++ b/plugins/TestRunner/Commands/TestsRunOnAws.php @@ -35,9 +35,10 @@ class TestsRunOnAws extends ConsoleCommand { $this->setName('tests:run-aws'); $this->addArgument('testsuite', InputArgument::OPTIONAL, 'Allowed values: ' . implode(', ', $this->allowedTestSuites)); + $this->addArgument('arguments', InputArgument::IS_ARRAY, 'Any additional argument will be passed to the test command.'); $this->addOption('launch-only', null, InputOption::VALUE_NONE, 'Only launches an instance and outputs the connection parameters. Useful if you want to connect via SSH.'); $this->addOption('update-only', null, InputOption::VALUE_NONE, 'Launches an instance, outputs the connection parameters and prepares the instance for a test run but does not actually run the tests. It will also checkout the specified version.'); - $this->addOption('one-instance-per-testsuite', null, InputOption::VALUE_NONE, 'Launches an instance, outputs the connection parameters and prepares the instance for a test run but does not actually run the tests. It will also checkout the specified version.'); + $this->addOption('one-instance-per-testsuite', null, InputOption::VALUE_NONE, 'Launches one instance for system tests and one for ui tests.'); $this->addOption('checkout', null, InputOption::VALUE_REQUIRED, 'Git hash, tag or branch to checkout. Defaults to current hash', $this->getCurrentGitHash()); $this->addOption('patch-file', null, InputOption::VALUE_REQUIRED, 'Apply the given patch file after performing a checkout'); $this->setDescription('Run a specific testsuite on AWS'); @@ -57,15 +58,15 @@ If you want to apply a patch on top of the checked out version you can apply the <comment>./console tests:run-aws --patch-file=test.patch ui</comment> This will checkout the same revision as you are currently on and then apply the patch. To generate a diff use for instance the command <comment>git diff > test.patch</comment>. This feature is still beta and there might be problems with pictures and/or binaries etc. + +You can also pass any argument to the command and they will be forwarded to the test command, for example to run a specific UI test: <comment>./console tests:run-aws ui Dashboard</comment>. '); } - /** - * Execute command like: ./console core:clear-caches - */ protected function execute(InputInterface $input, OutputInterface $output) { $testSuite = $this->getTestSuite($input); + $arguments = $input->getArgument('arguments'); $patchFile = $this->getPatchFile($input); $launchOnly = $input->getOption('launch-only'); $updateOnly = $input->getOption('update-only'); @@ -102,7 +103,7 @@ This feature is still beta and there might be problems with pictures and/or bina return 0; } - $testRunner->runTests($host, $testSuite); + $testRunner->runTests($host, $testSuite, $arguments); $message = $this->buildFinishedMessage($testSuite, $host); $output->writeln("\n$message\n"); diff --git a/plugins/TestRunner/Runner/Remote.php b/plugins/TestRunner/Runner/Remote.php index c0a5c5d8e1..d85c4a268a 100644 --- a/plugins/TestRunner/Runner/Remote.php +++ b/plugins/TestRunner/Runner/Remote.php @@ -55,11 +55,11 @@ class Remote } } - public function runTests($host, $testSuite) + public function runTests($host, $testSuite, array $arguments) { $this->prepareTestRun($host); $this->printVersionInfo(); - $this->doRunTests($testSuite); + $this->doRunTests($testSuite, $arguments); } private function prepareTestRun($host) @@ -74,17 +74,19 @@ class Remote $this->ssh->exec('phantomjs --version'); } - private function doRunTests($testSuite) + private function doRunTests($testSuite, array $arguments) { + $arguments = implode(' ', $arguments); + $this->ssh->exec("ps -ef | grep \"php console tests:run\" | grep -v grep | awk '{print $2}' | xargs kill -9"); if ('all' === $testSuite) { - $this->ssh->exec('php console tests:run --options="--colors"'); + $this->ssh->exec('php console tests:run --options="--colors" ' . $arguments); } elseif ('ui' === $testSuite) { - $this->ssh->exec('php console tests:run-ui --persist-fixture-data --assume-artifacts'); + $this->ssh->exec('php console tests:run-ui --persist-fixture-data --assume-artifacts ' . $arguments); } else { - $this->ssh->exec('php console tests:run --options="--colors" --testsuite="unit"'); - $this->ssh->exec('php console tests:run --options="--colors" --testsuite="' . $testSuite . '"'); + $this->ssh->exec('php console tests:run --options="--colors" --testsuite="unit" ' . $arguments); + $this->ssh->exec('php console tests:run --options="--colors" --testsuite="' . $testSuite . '" ' . $arguments); } if ('system' === $testSuite) { diff --git a/plugins/TestRunner/templates/travis.yml.twig b/plugins/TestRunner/templates/travis.yml.twig index 1971674108..b70a9bf345 100644 --- a/plugins/TestRunner/templates/travis.yml.twig +++ b/plugins/TestRunner/templates/travis.yml.twig @@ -14,7 +14,7 @@ language: php {% if phpVersions|default is empty %} php: - 5.6 - - 5.3 + - 5.3.3 # - hhvm {% else %} php: @@ -102,10 +102,6 @@ install: - rm -rf plugins/$PLUGIN_NAME - mv ../$PLUGIN_NAME plugins - # copy .coveralls.yml if none exists - - if [ ! -f ../coveralls.yml ]; - then cp .coveralls.yml ../coveralls.yml || true; - fi {% endif %} # make sure travis test scripts are always latest (so in older releases/branches, the latest scripts will still be used) @@ -125,11 +121,7 @@ before_script: {% endif %} - ./tests/travis/install_mysql_5.6.sh - - if ([ -z "$TEST_SUITE" ] || [ -n "$PLUGIN_NAME" ]); - then composer require satooshi/php-coveralls dev-master; - else - phpenv config-rm xdebug.ini; - fi + - phpenv config-rm xdebug.ini; # add always_populate_raw_post_data=-1 to php.ini - echo "always_populate_raw_post_data=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini @@ -191,10 +183,6 @@ after_script: # change directory back to root travis dir - cd $PIWIK_ROOT_DIR - - if ([ -z "$TEST_SUITE" ] || [ -n "$PLUGIN_NAME" ]); - then php vendor/bin/coveralls -v; - fi - # output contents of files w/ debugging info to screen - cat /var/log/nginx/error.log - cat $PIWIK_ROOT_DIR/tmp/php-fpm.log diff --git a/plugins/TreemapVisualization b/plugins/TreemapVisualization -Subproject 5dbb90f027b6d214aebfa4f222975d32e99d783 +Subproject bfd020cb3b25bbdb2bd44747f1c5a09396779d6 diff --git a/plugins/UserCountry/Columns/Country.php b/plugins/UserCountry/Columns/Country.php index db8026ba6b..458e92b068 100644 --- a/plugins/UserCountry/Columns/Country.php +++ b/plugins/UserCountry/Columns/Country.php @@ -12,7 +12,7 @@ use Piwik\Common; use Piwik\Config; use Piwik\Container\StaticContainer; use Piwik\Intl\Data\Provider\RegionDataProvider; -use Piwik\IP; +use Piwik\Network\IP; use Piwik\Piwik; use Piwik\Plugin\Manager; use Piwik\Plugins\Provider\Provider as ProviderProvider; @@ -107,12 +107,17 @@ class Country extends Base /** * Returns the hostname given the IP address string * - * @param string $ip IP Address + * @param string $ipStr IP Address * @return string hostname (or human-readable IP address) */ - private function getHost($ip) + private function getHost($ipStr) { - return trim(strtolower(@IP::getHostByAddr($ip))); + $ip = IP::fromStringIP($ipStr); + + $host = $ip->getHostname(); + $host = ($host === null ? $ipStr : $host); + + return trim(strtolower($host)); } /** diff --git a/plugins/VisitorGenerator b/plugins/VisitorGenerator -Subproject 6a9cf2957920aa19b84ab10b348edbd80c58835 +Subproject 34d796330e256e947ff976f57e350c775dc061d |