diff options
author | Fabian Becker <halfdan@xnorfz.de> | 2013-06-22 23:47:29 +0400 |
---|---|---|
committer | Fabian Becker <halfdan@xnorfz.de> | 2013-06-22 23:47:29 +0400 |
commit | 165ac7aeec855234e8c60bd758fad39bd6659adf (patch) | |
tree | e42ace9f7f45e420f883245a8df1beefa8eac9b0 /plugins | |
parent | 89d41940a87d449114f529df115b62d3987f88ce (diff) | |
parent | e2e5176279255b7ff2d80f0f511a1ef5f3748ebe (diff) |
Huge merge! Lets see how the tests run..
Merge branch 'master' into 2.x-twig
Conflicts:
core/ReportRenderer/Html.php
core/SmartyPlugins/function.ajaxLoadingDiv.php
plugins/CoreAdminHome/templates/jsTrackingGenerator.tpl
plugins/CoreHome/templates/donate.tpl
plugins/CoreHome/templates/html_report_header.tpl
plugins/CoreHome/templates/menu.tpl
plugins/CoreHome/templates/period_select.tpl
plugins/CoreHome/templates/reports_by_dimension.tpl
plugins/Feedback/templates/index.tpl
plugins/Goals/Controller.php
plugins/Goals/templates/overview.tpl
plugins/Live/API.php
plugins/Live/templates/lastVisits.tpl
plugins/Live/templates/visitorLog.tpl
plugins/MobileMessaging/templates/SMSReport.tpl
plugins/PDFReports/templates/add.tpl
plugins/PDFReports/templates/list.tpl
plugins/PDFReports/templates/report_parameters.tpl
plugins/SEO/templates/index.tpl
plugins/SegmentEditor/templates/selector.twig
plugins/UserCountry/javascripts/userCountry.js
plugins/Zeitgeist/stylesheets/common.css
tests/PHPUnit/Integration/expected/test_ManyVisitorsOneWebsiteTest__UserCountry.getCity_month.xml
tests/PHPUnit/Integration/expected/test_ManyVisitorsOneWebsiteTest__UserCountry.getCountry_month.xml
tests/PHPUnit/Integration/expected/test_ManyVisitorsOneWebsiteTest__UserCountry.getRegion_month.xml
tests/PHPUnit/Integration/expected/test_ManyVisitorsOneWebsiteTest_segment_continent__UserCountry.getCountry_month.xml
Diffstat (limited to 'plugins')
223 files changed, 7321 insertions, 3336 deletions
diff --git a/plugins/API/API.php b/plugins/API/API.php index df4e74e831..3f8d51d82e 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -36,7 +36,7 @@ class Piwik_API extends Piwik_Plugin public function addTopMenu() { - $apiUrlParams = array('module' => 'API', 'action' => 'listAllAPI'); + $apiUrlParams = array('module' => 'API', 'action' => 'listAllAPI', 'segment' => false); $tooltip = Piwik_Translate('API_TopLinkTooltip'); Piwik_AddTopMenu('General_API', $apiUrlParams, true, 7, $isHTML = false, $tooltip); @@ -269,35 +269,38 @@ class Piwik_API_API $segments = array(); Piwik_PostEvent('API.getSegmentsMetadata', $segments, $idSites); + $isAuthenticatedWithViewAccess = Piwik::isUserHasViewAccess($idSites) && !Piwik::isUserIsAnonymous(); + $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_VisitorIP', 'segment' => 'visitIp', 'acceptedValues' => '13.54.122.1, etc.', 'sqlSegment' => 'log_visit.location_ip', 'sqlFilter' => array('Piwik_IP', 'P2N'), - 'permission' => Piwik::isUserHasAdminAccess($idSites), + 'permission' => $isAuthenticatedWithViewAccess, ); $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_VisitorID', 'segment' => 'visitorId', 'acceptedValues' => '34c31e04394bdc63 - any 16 Hexadecimal chars ID, which can be fetched using the Tracking API function getVisitorId()', 'sqlSegment' => 'log_visit.idvisitor', 'sqlFilter' => array('Piwik_Common', 'convertVisitorIdToBin'), + 'permission' => $isAuthenticatedWithViewAccess, ); $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_NbActions', 'segment' => 'actions', 'sqlSegment' => 'log_visit.visit_total_actions', ); $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_NbSearches', 'segment' => 'searches', 'sqlSegment' => 'log_visit.visit_total_searches', @@ -305,14 +308,14 @@ class Piwik_API_API ); $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_ColumnVisitDuration', 'segment' => 'visitDuration', 'sqlSegment' => 'log_visit.visit_total_time', ); $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => Piwik_Translate('General_VisitType') , 'segment' => 'visitorType', 'acceptedValues' => 'new, returning, returningCustomer' . ". " . Piwik_Translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"'), @@ -321,21 +324,21 @@ class Piwik_API_API ); $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_DaysSinceLastVisit', 'segment' => 'daysSinceLastVisit', 'sqlSegment' => 'log_visit.visitor_days_since_last', ); $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_DaysSinceFirstVisit', 'segment' => 'daysSinceFirstVisit', 'sqlSegment' => 'log_visit.visitor_days_since_first', ); $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_NumberOfVisits', 'segment' => 'visitCount', 'sqlSegment' => 'log_visit.visitor_count_visits', @@ -343,7 +346,7 @@ class Piwik_API_API $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_VisitConvertedGoal', 'segment' => 'visitConverted', 'acceptedValues' => '0, 1', @@ -352,7 +355,7 @@ class Piwik_API_API $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => Piwik_Translate('General_EcommerceVisitStatusDesc'), 'segment' => 'visitEcommerceStatus', 'acceptedValues' => implode(", ", self::$visitEcommerceStatus) @@ -363,7 +366,7 @@ class Piwik_API_API $segments[] = array( 'type' => 'metric', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => 'General_DaysSinceLastEcommerceOrder', 'segment' => 'daysSinceLastEcommerceOrder', 'sqlSegment' => 'log_visit.visitor_days_since_order', @@ -527,7 +530,7 @@ class Piwik_API_API $reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports); foreach ($reportsMetadata as $report) { - // See ArchiveProcessing/Period.php - unique visitors are not processed for period != day + // See ArchiveProcessor/Period.php - unique visitors are not processed for period != day if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) { unset($report['metrics']['nb_uniq_visitors']); } @@ -740,7 +743,7 @@ class Piwik_API_API /** @var Piwik_DataTable */ $dataTable = $request->process(); } catch (Exception $e) { - throw new Exception("API returned an error: " . $e->getMessage() . "\n"); + throw new Exception("API returned an error: " . $e->getMessage() . " at " . basename($e->getFile()). ":" . $e->getLine() . "\n"); } list($newReport, $columns, $rowsMetadata) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, isset($reportMetadata['dimension']), $showRawMetrics); @@ -1035,6 +1038,7 @@ class Piwik_API_API Piwik_Translate('Referers_Referers'), Piwik_Translate('Goals_Goals'), Piwik_Translate('General_Visitors'), + Piwik_Translate('DevicesDetection_DevicesDetection'), Piwik_Translate('UserSettings_VisitorSettings'), ); } @@ -1152,20 +1156,16 @@ class Piwik_API_API public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true) { // validation of requested $period & $date - if ($period == 'range') { + if ($period == 'range') { // load days in the range $period = 'day'; } - if (!Piwik_Archive::isMultiplePeriod($date, $period)) { + if (!Piwik_Period::isMultiplePeriod($date, $period)) { throw new Exception("Row evolutions can not be processed with this combination of \'date\' and \'period\' parameters."); } - // this is needed because Piwik_API_Proxy uses Piwik_Common::getRequestVar which in turn - // uses Piwik_Common::sanitizeInputValue. This causes the > that separates recursive labels - // to become > and we need to undo that here. - $label = Piwik_Common::unsanitizeInputValue($label); - + $label = Piwik_API_ResponseBuilder::unsanitizeLabelParameter($label); if ($label) { $labels = explode(',', $label); $labels = array_unique($labels); @@ -1353,7 +1353,7 @@ class Piwik_API_API // add "processed metrics" like actions per visit or bounce rate // note: some reports should not be filtered with AddColumnProcessedMetrics - // specifically, reports without the Piwik_Archive::INDEX_NB_VISITS metric such as Goals.getVisitsUntilConversion & Goal.getDaysToConversion + // specifically, reports without the Piwik_Metrics::INDEX_NB_VISITS metric such as Goals.getVisitsUntilConversion & Goal.getDaysToConversion // this is because the AddColumnProcessedMetrics filter removes all datable rows lacking this metric if ( @@ -1618,11 +1618,12 @@ class Piwik_API_API return array(); } - $urls = Piwik_Common::unsanitizeInputValues($urls); + $urls = array_map('urldecode', $urls); + $urls = array_map(array('Piwik_Common','unsanitizeInputValue'), $urls); $result = array(); foreach ($urls as $url) { - $req = new Piwik_API_Request($url); + $req = new Piwik_API_Request($url . '&format=php&serialize=0'); $result[] = $req->process(); } return $result; @@ -1662,7 +1663,7 @@ class Piwik_API_API // Select non empty fields only // Note: this optimization has only a very minor impact - $requestLastVisits.= "&segment=$segmentName" . Piwik_SegmentExpression::MATCH_IS_NOT_NULL . "null"; + $requestLastVisits.= "&segment=$segmentName".urlencode('!='); // By default Live fetches all actions for all visitors, but we'd rather do this only when required if($this->doesSegmentNeedActionsData($segmentName)) { diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index 1904b3b72a..8eec6d2d1b 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -72,17 +72,8 @@ class Piwik_Actions_API Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - $metrics = array( - 'Actions_nb_pageviews' => 'nb_pageviews', - 'Actions_nb_uniq_pageviews' => 'nb_uniq_pageviews', - 'Actions_nb_downloads' => 'nb_downloads', - 'Actions_nb_uniq_downloads' => 'nb_uniq_downloads', - 'Actions_nb_outlinks' => 'nb_outlinks', - 'Actions_nb_uniq_outlinks' => 'nb_uniq_outlinks', - 'Actions_nb_searches' => 'nb_searches', - 'Actions_nb_keywords' => 'nb_keywords', - 'Actions_avg_time_generation' => 'avg_time_generation' - ); + $metrics = Piwik_Actions_Archiver::$actionsAggregateMetrics; + $metrics['Actions_avg_time_generation'] = 'avg_time_generation'; // get requested columns $columns = Piwik::getArrayFromApiParameter($columns); @@ -96,38 +87,39 @@ class Piwik_Actions_API $columns[$i] = $fullColumn; $nameReplace[$fullColumn] = $column; } - - if (false !== ($avgGenerationTimeRequested = array_search('Actions_avg_time_generation', $columns))) { - unset($columns[$avgGenerationTimeRequested]); - $avgGenerationTimeRequested = true; - } + + if (false !== ($avgGenerationTimeRequested = array_search('Actions_avg_time_generation', $columns))) { + unset($columns[$avgGenerationTimeRequested]); + $avgGenerationTimeRequested = true; + } } else { // get all columns - unset($metrics['Actions_avg_time_generation']); + unset($metrics['Actions_avg_time_generation']); $columns = array_keys($metrics); $nameReplace = & $metrics; - $avgGenerationTimeRequested = true; + $avgGenerationTimeRequested = true; + } + + if ($avgGenerationTimeRequested) { + $tempColumns[] = Piwik_Actions_Archiver::METRIC_SUM_TIME_RECORD_NAME; + $tempColumns[] = Piwik_Actions_Archiver::METRIC_HITS_TIMED_RECORD_NAME; + $columns = array_merge($columns, $tempColumns); + $columns = array_unique($columns); + + $nameReplace[Piwik_Actions_Archiver::METRIC_SUM_TIME_RECORD_NAME] = 'sum_time_generation'; + $nameReplace[Piwik_Actions_Archiver::METRIC_HITS_TIMED_RECORD_NAME] = 'nb_hits_with_time_generation'; } - - if ($avgGenerationTimeRequested) { - $tempColumns[] = 'Actions_sum_time_generation'; - $tempColumns[] = 'Actions_nb_hits_with_time_generation'; - $nameReplace['Actions_sum_time_generation'] = 'sum_time_generation'; - $nameReplace['Actions_nb_hits_with_time_generation'] = 'nb_hits_with_time_generation'; - $columns = array_merge($columns, $tempColumns); - $columns = array_unique($columns); - } - + $table = $archive->getDataTableFromNumeric($columns); // replace labels (remove Actions_) $table->filter('ReplaceColumnNames', array($nameReplace)); - - // compute avg generation time - if ($avgGenerationTimeRequested) { - $table->filter('ColumnCallbackAddColumnQuotient', array('avg_time_generation', 'sum_time_generation', 'nb_hits_with_time_generation', 3)); - $table->deleteColumns(array('sum_time_generation', 'nb_hits_with_time_generation')); - } + + // compute avg generation time + if ($avgGenerationTimeRequested) { + $table->filter('ColumnCallbackAddColumnQuotient', array('avg_time_generation', 'sum_time_generation', 'nb_hits_with_time_generation', 3)); + $table->deleteColumns(array('sum_time_generation', 'nb_hits_with_time_generation')); + } return $table; } @@ -301,7 +293,7 @@ class Piwik_Actions_API public function getSiteSearchKeywords($idSite, $period, $date, $segment = false) { $dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment); - $dataTable->deleteColumn(Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT); + $dataTable->deleteColumn(Piwik_Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT); $this->filterPageDatatable($dataTable); $this->filterActionsDataTable($dataTable); $this->addPagesPerSearchColumn($dataTable); @@ -326,11 +318,11 @@ class Piwik_Actions_API // Delete all rows that have some results $dataTable->filter('ColumnCallbackDeleteRow', array( - Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT, + Piwik_Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT, create_function('$value', 'return $value >= 1;') )); $dataTable->deleteRow(Piwik_DataTable::ID_SUMMARY_ROW); - $dataTable->deleteColumn(Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT); + $dataTable->deleteColumn(Piwik_Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT); $this->filterPageDatatable($dataTable); $this->filterActionsDataTable($dataTable); $this->addPagesPerSearchColumn($dataTable); @@ -354,7 +346,7 @@ class Piwik_Actions_API $dataTable = new Piwik_DataTable(); // Handle case where date=last30&period=day - // TODO: this logic should really be refactored somewhere, this is ugly! + // FIXMEA: this logic should really be refactored somewhere, this is ugly! if ($customVariables instanceof Piwik_DataTable_Array) { $dataTable = $customVariables->getEmptyClone(); @@ -496,7 +488,7 @@ class Piwik_Actions_API $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('exit_rate', 'exit_nb_visits', 'nb_visits', 0)); // Handle performance analytics - $hasTimeGeneration = (array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION)) > 0); + $hasTimeGeneration = (array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION)) > 0); if ($hasTimeGeneration) { // Average generation time = total generation time / number of pageviews $precisionAvgTimeGeneration = 3; @@ -506,12 +498,12 @@ class Piwik_Actions_API // No generation time: remove it from the API output and add it to empty_columns metadata, so that // the columns can also be removed from the view $dataTable->filter('ColumnDelete', array(array( - Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION, - Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, - Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION, - Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION - ))); - + Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION, + Piwik_Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, + Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION, + Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION + ))); + if ($dataTable instanceof Piwik_DataTable) { $emptyColumns = $dataTable->getMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME); if (!is_array($emptyColumns)) { diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php index 8a9be015d7..d00ed25905 100644 --- a/plugins/Actions/Actions.php +++ b/plugins/Actions/Actions.php @@ -564,20 +564,6 @@ class Piwik_Actions extends Piwik_Plugin /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ - function archivePeriod($notification) - { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $actionsArchiving = new Piwik_Actions_Archiving($archiveProcessing->idsite); - return $actionsArchiving->archivePeriod($archiveProcessing); - } - - /** * Compute all the actions along with their hierarchies. * * For each action we process the "interest statistics" : @@ -587,13 +573,23 @@ class Piwik_Actions extends Piwik_Plugin */ public function archiveDay($notification) { - /* @var $archiveProcessing Piwik_ArchiveProcessing_Day */ - $archiveProcessing = $notification->getNotificationObject(); + /* @var $archiveProcessor Piwik_ArchiveProcessor_Day */ + $archiveProcessor = $notification->getNotificationObject(); - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; + $archiving = new Piwik_Actions_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); + } + } - $actionsArchiving = new Piwik_Actions_Archiving($archiveProcessing->idsite); - return $actionsArchiving->archiveDay($archiveProcessing); + function archivePeriod($notification) + { + $archiveProcessor = $notification->getNotificationObject(); + + $archiving = new Piwik_Actions_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); + } } static public function checkCustomVariablesPluginEnabled() diff --git a/plugins/Actions/Archiving.php b/plugins/Actions/Archiver.php index fa514b6ef9..b677e29eec 100644 --- a/plugins/Actions/Archiving.php +++ b/plugins/Actions/Archiver.php @@ -14,9 +14,36 @@ * * @package Piwik_Actions */ -class Piwik_Actions_Archiving +class Piwik_Actions_Archiver extends Piwik_PluginsArchiver { - protected $actionsTablesByType = null; + const DOWNLOADS_RECORD_NAME = 'Actions_downloads'; + const OUTLINKS_RECORD_NAME = 'Actions_outlink'; + const PAGE_TITLES_RECORD_NAME = 'Actions_actions'; + const SITE_SEARCH_RECORD_NAME = 'Actions_sitesearch'; + const PAGE_URLS_RECORD_NAME = 'Actions_actions_url'; + + const METRIC_PAGEVIEWS_RECORD_NAME = 'Actions_nb_pageviews'; + const METRIC_UNIQ_PAGEVIEWS_RECORD_NAME = 'Actions_nb_uniq_pageviews'; + const METRIC_SUM_TIME_RECORD_NAME = 'Actions_sum_time_generation'; + const METRIC_HITS_TIMED_RECORD_NAME = 'Actions_nb_hits_with_time_generation'; + const METRIC_DOWNLOADS_RECORD_NAME = 'Actions_nb_downloads'; + const METRIC_UNIQ_DOWNLOADS_RECORD_NAME = 'Actions_nb_uniq_downloads'; + const METRIC_OUTLINKS_RECORD_NAME = 'Actions_nb_outlinks'; + const METRIC_UNIQ_OUTLINKS_RECORD_NAME = 'Actions_nb_uniq_outlinks'; + const METRIC_SEARCHES_RECORD_NAME = 'Actions_nb_searches'; + const METRIC_KEYWORDS_RECORD_NAME = 'Actions_nb_keywords'; + + /* Metrics in use by the API Actions.get */ + public static $actionsAggregateMetrics = array( + self::METRIC_PAGEVIEWS_RECORD_NAME => 'nb_pageviews', + self::METRIC_UNIQ_PAGEVIEWS_RECORD_NAME => 'nb_uniq_pageviews', + self::METRIC_DOWNLOADS_RECORD_NAME => 'nb_downloads', + self::METRIC_UNIQ_DOWNLOADS_RECORD_NAME => 'nb_uniq_downloads', + self::METRIC_OUTLINKS_RECORD_NAME => 'nb_outlinks', + self::METRIC_UNIQ_OUTLINKS_RECORD_NAME => 'nb_uniq_outlinks', + self::METRIC_SEARCHES_RECORD_NAME => 'nb_searches', + self::METRIC_KEYWORDS_RECORD_NAME => 'nb_keywords', + ); public static $actionTypes = array( Piwik_Tracker_Action::TYPE_ACTION_URL, @@ -25,88 +52,39 @@ class Piwik_Actions_Archiving Piwik_Tracker_Action::TYPE_ACTION_NAME, Piwik_Tracker_Action::TYPE_SITE_SEARCH, ); - - private static $actionColumnAggregationOperations = array( - Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION => 'max', - Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION => 'min' - ); - static protected $invalidSummedColumnNameToRenamedNameFromPeriodArchive = array( - Piwik_Archive::INDEX_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_SUM_DAILY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_NB_UNIQ_VISITORS => Piwik_Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => Piwik_Metrics::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => Piwik_Metrics::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS, ); - static protected $invalidSummedColumnNameToDeleteFromDayArchive = array( - Piwik_Archive::INDEX_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS, ); - + private static $actionColumnAggregationOperations = array( + Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION => 'max', + Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION => 'min' + ); + protected $actionsTablesByType = null; protected $isSiteSearchEnabled = false; - function __construct($idSite) + function __construct($processor) { - $this->isSiteSearchEnabled = Piwik_Site::isSiteSearchEnabledFor($idSite); - } - - /** - * Archives Actions reports for a Period - * @param Piwik_ArchiveProcessing_Period $archiveProcessing - * @return bool - */ - public function archivePeriod(Piwik_ArchiveProcessing_Period $archiveProcessing) - { - Piwik_Actions_ArchivingHelper::reloadConfig(); - $dataTableToSum = array( - 'Actions_actions', - 'Actions_actions_url', - ); - $archiveProcessing->archiveDataTable($dataTableToSum, - self::$invalidSummedColumnNameToRenamedNameFromPeriodArchive, - Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, - Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, - Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation, - self::$actionColumnAggregationOperations); - - $dataTableToSum = array( - 'Actions_downloads', - 'Actions_outlink', - 'Actions_sitesearch', - ); - $nameToCount = $archiveProcessing->archiveDataTable($dataTableToSum, - self::$invalidSummedColumnNameToRenamedNameFromPeriodArchive, - Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, - Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, - Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); - - $archiveProcessing->archiveNumericValuesSum(array( - 'Actions_nb_pageviews', - 'Actions_nb_uniq_pageviews', - 'Actions_nb_downloads', - 'Actions_nb_uniq_downloads', - 'Actions_nb_outlinks', - 'Actions_nb_uniq_outlinks', - 'Actions_nb_searches', - 'Actions_sum_time_generation', - 'Actions_nb_hits_with_time_generation', - )); - - // Unique Keywords can't be summed, instead we take the RowsCount() of the keyword table - $archiveProcessing->insertNumericRecord('Actions_nb_keywords', $nameToCount['Actions_sitesearch']['level0']); - return true; + parent::__construct($processor); + $this->isSiteSearchEnabled = $processor->getSite()->isSiteSearchEnabled(); } /** * Archives Actions reports for a Day * - * @param Piwik_ArchiveProcessing $archiveProcessing + * @param Piwik_ArchiveProcessor $this->getProcessor() * @return bool */ - public function archiveDay(Piwik_ArchiveProcessing $archiveProcessing) + public function archiveDay() { $rankingQueryLimit = self::getRankingQueryLimit(); - + // FIXME: This is a quick fix for #3482. The actual cause of the bug is that // the site search & performance metrics additions to // Piwik_Actions_ArchivingHelper::updateActionsTableWithRowQuery expect every @@ -132,49 +110,86 @@ class Piwik_Actions_Archiving if ($rankingQueryLimit === 0) { $rankingQueryLimit = 100000; } - + Piwik_Actions_ArchivingHelper::reloadConfig(); $this->initActionsTables(); - $this->archiveDayActions($archiveProcessing, $rankingQueryLimit); - $this->archiveDayEntryActions($archiveProcessing, $rankingQueryLimit); - $this->archiveDayExitActions($archiveProcessing, $rankingQueryLimit); - $this->archiveDayActionsTime($archiveProcessing, $rankingQueryLimit); + $this->archiveDayActions($rankingQueryLimit); + $this->archiveDayEntryActions($rankingQueryLimit); + $this->archiveDayExitActions($rankingQueryLimit); + $this->archiveDayActionsTime($rankingQueryLimit); - // Record the final datasets - $this->archiveDayRecordInDatabase($archiveProcessing); + $this->recordDayReports(); return true; } + /** + * Returns the limit to use with RankingQuery for this plugin. + * + * @return int + */ + private static function getRankingQueryLimit() + { + $configGeneral = Piwik_Config::getInstance()->General; + $configLimit = $configGeneral['archiving_ranking_query_row_limit']; + return $configLimit == 0 ? 0 : max( + $configLimit, + $configGeneral['datatable_archiving_maximum_rows_actions'], + $configGeneral['datatable_archiving_maximum_rows_subtable_actions'] + ); + } + /* * Page URLs and Page names, general stats */ - protected function archiveDayActions($archiveProcessing, $rankingQueryLimit) + + /** + * Initializes the DataTables created by the archiveDay function. + */ + private function initActionsTables() + { + $this->actionsTablesByType = array(); + foreach (self::$actionTypes as $type) { + $dataTable = new Piwik_DataTable(); + $dataTable->setMaximumAllowedRows(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero); + + if ($type == Piwik_Tracker_Action::TYPE_ACTION_URL + || $type == Piwik_Tracker_Action::TYPE_ACTION_NAME + ) { + // for page urls and page titles, performance metrics exist and have to be aggregated correctly + $dataTable->setColumnAggregationOperations(self::$actionColumnAggregationOperations); + } + + $this->actionsTablesByType[$type] = $dataTable; + } + } + + protected function archiveDayActions($rankingQueryLimit) { $select = "log_action.name, log_action.type, log_action.idaction, log_action.url_prefix, - count(distinct log_link_visit_action.idvisit) as `" . Piwik_Archive::INDEX_NB_VISITS . "`, - count(distinct log_link_visit_action.idvisitor) as `" . Piwik_Archive::INDEX_NB_UNIQ_VISITORS . "`, - count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`, + count(distinct log_link_visit_action.idvisit) as `" . Piwik_Metrics::INDEX_NB_VISITS . "`, + count(distinct log_link_visit_action.idvisitor) as `" . Piwik_Metrics::INDEX_NB_UNIQ_VISITORS . "`, + count(*) as `" . Piwik_Metrics::INDEX_PAGE_NB_HITS . "`, sum( case when " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . " is null - then 0 + then 0 else " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . " end - ) / 1000 as `" . Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION . "`, + ) / 1000 as `" . Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION . "`, sum( case when " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . " is null then 0 else 1 end - ) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION . "`, + ) as `" . Piwik_Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION . "`, min(" . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . ") / 1000 - as `" . Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION . "`, + as `" . Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION . "`, max(" . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . ") / 1000 - as `" . Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION . "` + as `" . Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION . "` "; $from = array( @@ -191,23 +206,23 @@ class Piwik_Actions_Archiving AND log_link_visit_action.%s IS NOT NULL"; $groupBy = "log_action.idaction"; - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_NB_HITS . "` DESC, name ASC"; + $orderBy = "`" . Piwik_Metrics::INDEX_PAGE_NB_HITS . "` DESC, name ASC"; $rankingQuery = false; if ($rankingQueryLimit > 0) { $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); $rankingQuery->addLabelColumn(array('idaction', 'name')); - $rankingQuery->addColumn(array('url_prefix', Piwik_Archive::INDEX_NB_UNIQ_VISITORS)); - $rankingQuery->addColumn(array(Piwik_Archive::INDEX_PAGE_NB_HITS, Piwik_Archive::INDEX_NB_VISITS), 'sum'); + $rankingQuery->addColumn(array('url_prefix', Piwik_Metrics::INDEX_NB_UNIQ_VISITORS)); + $rankingQuery->addColumn(array(Piwik_Metrics::INDEX_PAGE_NB_HITS, Piwik_Metrics::INDEX_NB_VISITS), 'sum'); if ($this->isSiteSearchEnabled()) { - $rankingQuery->addColumn(Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT, 'min'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS, 'sum'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT, 'min'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS, 'sum'); } - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION, 'sum'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, 'sum'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION, 'min'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION, 'max'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION, 'sum'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, 'sum'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION, 'min'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION, 'max'); $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); } @@ -216,7 +231,7 @@ class Piwik_Actions_Archiving // 2) For each page view, count number of times the referrer page was a Site Search if ($this->isSiteSearchEnabled()) { $selectFlagNoResultKeywords = ", - CASE WHEN (MAX(log_link_visit_action.custom_var_v" . Piwik_Tracker_Action::CVAR_INDEX_SEARCH_COUNT . ") = 0 AND log_link_visit_action.custom_var_k" . Piwik_Tracker_Action::CVAR_INDEX_SEARCH_COUNT . " = '" . Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT . "') THEN 1 ELSE 0 END AS `" . Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT . "`"; + CASE WHEN (MAX(log_link_visit_action.custom_var_v" . Piwik_Tracker_Action::CVAR_INDEX_SEARCH_COUNT . ") = 0 AND log_link_visit_action.custom_var_k" . Piwik_Tracker_Action::CVAR_INDEX_SEARCH_COUNT . " = '" . Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT . "') THEN 1 ELSE 0 END AS `" . Piwik_Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT . "`"; //we need an extra JOIN to know whether the referrer "idaction_name_ref" was a Site Search request $from[] = array( @@ -226,39 +241,62 @@ class Piwik_Actions_Archiving ); $selectSiteSearchFollowingPages = ", - SUM(CASE WHEN log_action_name_ref.type = " . Piwik_Tracker_Action::TYPE_SITE_SEARCH . " THEN 1 ELSE 0 END) AS `" . Piwik_Archive::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS . "`"; + SUM(CASE WHEN log_action_name_ref.type = " . Piwik_Tracker_Action::TYPE_SITE_SEARCH . " THEN 1 ELSE 0 END) AS `" . Piwik_Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS . "`"; $select .= $selectFlagNoResultKeywords . $selectSiteSearchFollowingPages; - // Not working yet -// $selectRefPageIsStartingSiteSearch = ", -// SUM(CASE WHEN log_action_name_ref.type = " . Piwik_Tracker_Action::TYPE_ACTION_NAME . " THEN 1 ELSE 0 END) AS `". Piwik_Archive::INDEX_PAGE_STARTING_SITE_SEARCH_NB_HITS."`"; -// . $selectRefPageIsStartingSiteSearch -// . ", idaction_url_ref, idaction_name_ref" } - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_name", $archiveProcessing, $rankingQuery); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_name", $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_url", $rankingQuery); + } + + protected function isSiteSearchEnabled() + { + return $this->isSiteSearchEnabled; + } + + protected function archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, $sprintfField, $rankingQuery = false) + { + // idaction field needs to be set in select clause before calling getSelectQuery(). + // if a complex segmentation join is needed, the field needs to be propagated + // to the outer select. therefore, $segment needs to know about it. + $select = sprintf($select, $sprintfField); + + + // get query with segmentation + $query = $this->getLogAggregator()->generateQuery($select, $from, $where, $groupBy, $orderBy); + + // replace the rest of the %s + $querySql = str_replace("%s", $sprintfField, $query['sql']); - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_url", $archiveProcessing, $rankingQuery); + // apply ranking query + if ($rankingQuery) { + $querySql = $rankingQuery->generateQuery($querySql); + } + + // get result + $resultSet = $this->getLogAggregator()->getDb()->query($querySql, $query['bind']); + $modified = Piwik_Actions_ArchivingHelper::updateActionsTableWithRowQuery($resultSet, $sprintfField, $this->actionsTablesByType); + return $modified; } /** * Entry actions for Page URLs and Page names */ - protected function archiveDayEntryActions($archiveProcessing, $rankingQueryLimit) + protected function archiveDayEntryActions($rankingQueryLimit) { $rankingQuery = false; if ($rankingQueryLimit > 0) { $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); $rankingQuery->addLabelColumn('idaction'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS); - $rankingQuery->addColumn(array(Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS, - Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH, - Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT), 'sum'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS); + $rankingQuery->addColumn(array(Piwik_Metrics::INDEX_PAGE_ENTRY_NB_VISITS, + Piwik_Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS, + Piwik_Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH, + Piwik_Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT), 'sum'); $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); $extraSelects = 'log_action.type, log_action.name,'; @@ -269,7 +307,7 @@ class Piwik_Actions_Archiving "joinOn" => "log_visit.%s = log_action.idaction" ) ); - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS . "` DESC, log_action.name ASC"; + $orderBy = "`" . Piwik_Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS . "` DESC, log_action.name ASC"; } else { $extraSelects = false; $from = "log_visit"; @@ -277,11 +315,11 @@ class Piwik_Actions_Archiving } $select = "log_visit.%s as idaction, $extraSelects - count(distinct log_visit.idvisitor) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS . "`, - count(*) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS . "`, - sum(log_visit.visit_total_actions) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS . "`, - sum(log_visit.visit_total_time) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH . "`, - sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT . "`"; + count(distinct log_visit.idvisitor) as `" . Piwik_Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS . "`, + count(*) as `" . Piwik_Metrics::INDEX_PAGE_ENTRY_NB_VISITS . "`, + sum(log_visit.visit_total_actions) as `" . Piwik_Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS . "`, + sum(log_visit.visit_total_time) as `" . Piwik_Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH . "`, + sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `" . Piwik_Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT . "`"; $where = "log_visit.visit_last_action_time >= ? AND log_visit.visit_last_action_time <= ? @@ -290,254 +328,145 @@ class Piwik_Actions_Archiving $groupBy = "log_visit.%s, idaction"; - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_entry_idaction_url", $archiveProcessing, $rankingQuery); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_entry_idaction_url", $rankingQuery); - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_entry_idaction_name", $archiveProcessing, $rankingQuery); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_entry_idaction_name", $rankingQuery); } - /** - * Time per action + * Exit actions */ - protected function archiveDayActionsTime($archiveProcessing, $rankingQueryLimit) + public function archiveDayExitActions($rankingQueryLimit) { $rankingQuery = false; if ($rankingQueryLimit > 0) { $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); $rankingQuery->addLabelColumn('idaction'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT, 'sum'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_EXIT_NB_VISITS, 'sum'); $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - $extraSelects = "log_action.type, log_action.name, count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`,"; + $extraSelects = 'log_action.type, log_action.name,'; $from = array( - "log_link_visit_action", + "log_visit", array( "table" => "log_action", - "joinOn" => "log_link_visit_action.%s = log_action.idaction" + "joinOn" => "log_visit.%s = log_action.idaction" ) ); - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_NB_HITS . "` DESC, log_action.name ASC"; + $orderBy = "`" . Piwik_Metrics::INDEX_PAGE_EXIT_NB_VISITS . "` DESC, log_action.name ASC"; } else { $extraSelects = false; - $from = "log_link_visit_action"; + $from = "log_visit"; $orderBy = false; } - $select = "log_link_visit_action.%s as idaction, $extraSelects - sum(log_link_visit_action.time_spent_ref_action) as `" . Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT . "`"; + $select = "log_visit.%s as idaction, $extraSelects + count(distinct log_visit.idvisitor) as `" . Piwik_Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS . "`, + count(*) as `" . Piwik_Metrics::INDEX_PAGE_EXIT_NB_VISITS . "`"; - $where = "log_link_visit_action.server_time >= ? - AND log_link_visit_action.server_time <= ? - AND log_link_visit_action.idsite = ? - AND log_link_visit_action.time_spent_ref_action > 0 - AND log_link_visit_action.%s > 0"; + $where = "log_visit.visit_last_action_time >= ? + AND log_visit.visit_last_action_time <= ? + AND log_visit.idsite = ? + AND log_visit.%s > 0"; - $groupBy = "log_link_visit_action.%s, idaction"; + $groupBy = "log_visit.%s, idaction"; - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_url_ref", $archiveProcessing, $rankingQuery); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_exit_idaction_url", $rankingQuery); - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_name_ref", $archiveProcessing, $rankingQuery); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "visit_exit_idaction_name", $rankingQuery); + return array($rankingQuery, $extraSelects, $from, $orderBy, $select, $where, $groupBy); } /** - * Exit actions + * Time per action */ - public function archiveDayExitActions($archiveProcessing, $rankingQueryLimit) + protected function archiveDayActionsTime($rankingQueryLimit) { $rankingQuery = false; if ($rankingQueryLimit > 0) { $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); $rankingQuery->addLabelColumn('idaction'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS, 'sum'); + $rankingQuery->addColumn(Piwik_Metrics::INDEX_PAGE_SUM_TIME_SPENT, 'sum'); $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - $extraSelects = 'log_action.type, log_action.name,'; + $extraSelects = "log_action.type, log_action.name, count(*) as `" . Piwik_Metrics::INDEX_PAGE_NB_HITS . "`,"; $from = array( - "log_visit", + "log_link_visit_action", array( "table" => "log_action", - "joinOn" => "log_visit.%s = log_action.idaction" + "joinOn" => "log_link_visit_action.%s = log_action.idaction" ) ); - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS . "` DESC, log_action.name ASC"; + $orderBy = "`" . Piwik_Metrics::INDEX_PAGE_NB_HITS . "` DESC, log_action.name ASC"; } else { $extraSelects = false; - $from = "log_visit"; + $from = "log_link_visit_action"; $orderBy = false; } - $select = "log_visit.%s as idaction, $extraSelects - count(distinct log_visit.idvisitor) as `" . Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS . "`, - count(*) as `" . Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS . "`"; + $select = "log_link_visit_action.%s as idaction, $extraSelects + sum(log_link_visit_action.time_spent_ref_action) as `" . Piwik_Metrics::INDEX_PAGE_SUM_TIME_SPENT . "`"; - $where = "log_visit.visit_last_action_time >= ? - AND log_visit.visit_last_action_time <= ? - AND log_visit.idsite = ? - AND log_visit.%s > 0"; + $where = "log_link_visit_action.server_time >= ? + AND log_link_visit_action.server_time <= ? + AND log_link_visit_action.idsite = ? + AND log_link_visit_action.time_spent_ref_action > 0 + AND log_link_visit_action.%s > 0"; - $groupBy = "log_visit.%s, idaction"; + $groupBy = "log_link_visit_action.%s, idaction"; - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_exit_idaction_url", $archiveProcessing, $rankingQuery); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_url_ref", $rankingQuery); - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_exit_idaction_name", $archiveProcessing, $rankingQuery); - return array($rankingQuery, $extraSelects, $from, $orderBy, $select, $where, $groupBy); + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_name_ref", $rankingQuery); } - /** * Records in the DB the archived reports for Page views, Downloads, Outlinks, and Page titles - * - * @param $archiveProcessing */ - protected function archiveDayRecordInDatabase($archiveProcessing) + protected function recordDayReports() { Piwik_Actions_ArchivingHelper::clearActionsCache(); - /** @var Piwik_DataTable $dataTable */ - $dataTable = $this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION_URL]; - self::deleteInvalidSummedColumnsFromDataTable($dataTable); - $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); - $archiveProcessing->insertBlobRecord('Actions_actions_url', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_pageviews', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_uniq_pageviews', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - $archiveProcessing->insertNumericRecord('Actions_sum_time_generation', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION))); - $archiveProcessing->insertNumericRecord('Actions_nb_hits_with_time_generation', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION))); - destroy($dataTable); - - $dataTable = $this->actionsTablesByType[Piwik_Tracker_Action::TYPE_DOWNLOAD]; - self::deleteInvalidSummedColumnsFromDataTable($dataTable); - $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); - $archiveProcessing->insertBlobRecord('Actions_downloads', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_downloads', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_uniq_downloads', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - destroy($dataTable); - - $dataTable = $this->actionsTablesByType[Piwik_Tracker_Action::TYPE_OUTLINK]; - self::deleteInvalidSummedColumnsFromDataTable($dataTable); - $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); - $archiveProcessing->insertBlobRecord('Actions_outlink', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_outlinks', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_uniq_outlinks', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - destroy($dataTable); - - $dataTable = $this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION_NAME]; - self::deleteInvalidSummedColumnsFromDataTable($dataTable); - $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); - $archiveProcessing->insertBlobRecord('Actions_actions', $s); - destroy($dataTable); - - $dataTable = $this->actionsTablesByType[Piwik_Tracker_Action::TYPE_SITE_SEARCH]; - self::deleteInvalidSummedColumnsFromDataTable($dataTable); - $this->deleteUnusedColumnsFromKeywordsDataTable($dataTable); - $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); - $archiveProcessing->insertBlobRecord('Actions_sitesearch', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_searches', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_keywords', $dataTable->getRowsCount()); - destroy($dataTable); - - destroy($this->actionsTablesByType); + $this->recordPageUrlsReports(); + $this->recordDownloadsReports(); + $this->recordOutlinksReports(); + $this->recordPageTitlesReports(); + $this->recordSiteSearchReports(); } - protected function deleteUnusedColumnsFromKeywordsDataTable($dataTable) + protected function recordPageUrlsReports() { - $columnsToDelete = array( - Piwik_Archive::INDEX_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS, - Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS, - Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT, - Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS, + $dataTable = $this->getDataTable(Piwik_Tracker_Action::TYPE_ACTION_URL); + $this->recordDataTable($dataTable, self::PAGE_URLS_RECORD_NAME); + + $records = array( + self::METRIC_PAGEVIEWS_RECORD_NAME => array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_PAGE_NB_HITS)), + self::METRIC_UNIQ_PAGEVIEWS_RECORD_NAME => array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_NB_VISITS)), + self::METRIC_SUM_TIME_RECORD_NAME => array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION)), + self::METRIC_HITS_TIMED_RECORD_NAME => array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION)) ); - $dataTable->deleteColumns($columnsToDelete); - } - - static protected function removeEmptyColumns($dataTable) - { - // Delete all columns that have a value of zero - $dataTable->filter('ColumnDelete', array( - $columnsToRemove = array(Piwik_Archive::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS), - $columnsToKeep = array(), - $deleteIfZeroOnly = true - )); + $this->getProcessor()->insertNumericRecords( $records ); } - /** - * Returns the limit to use with RankingQuery for this plugin. - * - * @return int + * @param $typeId + * @return Piwik_DataTable */ - private static function getRankingQueryLimit() + protected function getDataTable($typeId) { - $configGeneral = Piwik_Config::getInstance()->General; - $configLimit = $configGeneral['archiving_ranking_query_row_limit']; - return $configLimit == 0 ? 0 : max( - $configLimit, - $configGeneral['datatable_archiving_maximum_rows_actions'], - $configGeneral['datatable_archiving_maximum_rows_subtable_actions'] - ); + return $this->actionsTablesByType[$typeId]; } - - /** - * @param $select - * @param $from - * @param $where - * @param $orderBy - * @param $groupBy - * @param $sprintfField - * @param Piwik_ArchiveProcessing $archiveProcessing - * @param Piwik_RankingQuery|false $rankingQuery - * @return int - */ - protected function archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - $sprintfField, $archiveProcessing, $rankingQuery = false) + protected function recordDataTable($dataTable, $recordName) { - // idaction field needs to be set in select clause before calling getSelectQuery(). - // if a complex segmentation join is needed, the field needs to be propagated - // to the outer select. therefore, $segment needs to know about it. - $select = sprintf($select, $sprintfField); - - $bind = array(); - - // get query with segmentation - $query = $archiveProcessing->getSegment()->getSelectQuery( - $select, $from, $where, $bind, $orderBy, $groupBy); - - // extend bindings - $bind = array_merge(array($archiveProcessing->getStartDatetimeUTC(), - $archiveProcessing->getEndDatetimeUTC(), - $archiveProcessing->idsite - ), - $query['bind'] - ); - - // replace the rest of the %s - $querySql = str_replace("%s", $sprintfField, $query['sql']); - - // apply ranking query - if ($rankingQuery) { - $querySql = $rankingQuery->generateQuery($querySql); - } - - // get result - $resultSet = $archiveProcessing->db->query($querySql, $bind); - $modified = Piwik_Actions_ArchivingHelper::updateActionsTableWithRowQuery($resultSet, $sprintfField, $this->actionsTablesByType); - return $modified; + self::deleteInvalidSummedColumnsFromDataTable($dataTable); + $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); + $this->getProcessor()->insertBlobRecord($recordName, $s); } - /** * For rows which have subtables (eg. directories with sub pages), * deletes columns which don't make sense when all values of sub pages are summed. @@ -569,28 +498,107 @@ class Piwik_Actions_Archiving self::removeEmptyColumns($dataTable); } - /** - * Initializes the DataTables created by the archiveDay function. - */ - private function initActionsTables() + static protected function removeEmptyColumns($dataTable) { - $this->actionsTablesByType = array(); - foreach (self::$actionTypes as $type) { - $dataTable = new Piwik_DataTable(); - $dataTable->setMaximumAllowedRows(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero); + // Delete all columns that have a value of zero + $dataTable->filter('ColumnDelete', array( + $columnsToRemove = array(Piwik_Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS), + $columnsToKeep = array(), + $deleteIfZeroOnly = true + )); + } - if ($type == Piwik_Tracker_Action::TYPE_ACTION_URL - || $type == Piwik_Tracker_Action::TYPE_ACTION_NAME) { - // for page urls and page titles, performance metrics exist and have to be aggregated correctly - $dataTable->setColumnAggregationOperations(self::$actionColumnAggregationOperations); - } - - $this->actionsTablesByType[$type] = $dataTable; - } + protected function recordDownloadsReports() + { + $dataTable = $this->getDataTable(Piwik_Tracker_Action::TYPE_DOWNLOAD); + $this->recordDataTable($dataTable, self::DOWNLOADS_RECORD_NAME); + + $this->getProcessor()->insertNumericRecord(self::METRIC_DOWNLOADS_RECORD_NAME, array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_PAGE_NB_HITS))); + $this->getProcessor()->insertNumericRecord(self::METRIC_UNIQ_DOWNLOADS_RECORD_NAME, array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_NB_VISITS))); } - protected function isSiteSearchEnabled() + protected function recordOutlinksReports() { - return $this->isSiteSearchEnabled; + $dataTable = $this->getDataTable(Piwik_Tracker_Action::TYPE_OUTLINK); + $this->recordDataTable($dataTable, self::OUTLINKS_RECORD_NAME); + + $this->getProcessor()->insertNumericRecord(self::METRIC_OUTLINKS_RECORD_NAME, array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_PAGE_NB_HITS))); + $this->getProcessor()->insertNumericRecord(self::METRIC_UNIQ_OUTLINKS_RECORD_NAME, array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_NB_VISITS))); + } + + protected function recordPageTitlesReports() + { + $dataTable = $this->getDataTable(Piwik_Tracker_Action::TYPE_ACTION_NAME); + $this->recordDataTable($dataTable, self::PAGE_TITLES_RECORD_NAME); + } + + protected function recordSiteSearchReports() + { + $dataTable = $this->getDataTable(Piwik_Tracker_Action::TYPE_SITE_SEARCH); + $this->deleteUnusedColumnsFromKeywordsDataTable($dataTable); + $this->recordDataTable($dataTable, self::SITE_SEARCH_RECORD_NAME); + + $this->getProcessor()->insertNumericRecord(self::METRIC_SEARCHES_RECORD_NAME, array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_NB_VISITS))); + $this->getProcessor()->insertNumericRecord(self::METRIC_KEYWORDS_RECORD_NAME, $dataTable->getRowsCount()); + } + + protected function deleteUnusedColumnsFromKeywordsDataTable($dataTable) + { + $columnsToDelete = array( + Piwik_Metrics::INDEX_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS, + Piwik_Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS, + Piwik_Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS, + Piwik_Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH, + Piwik_Metrics::INDEX_PAGE_ENTRY_NB_VISITS, + Piwik_Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT, + Piwik_Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS, + ); + $dataTable->deleteColumns($columnsToDelete); + } + + public function archivePeriod() + { + Piwik_Actions_ArchivingHelper::reloadConfig(); + $dataTableToSum = array( + self::PAGE_TITLES_RECORD_NAME, + self::PAGE_URLS_RECORD_NAME, + ); + $this->getProcessor()->aggregateDataTableReports($dataTableToSum, + Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, + Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, + Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation, + self::$actionColumnAggregationOperations, + self::$invalidSummedColumnNameToRenamedNameFromPeriodArchive + ); + + $dataTableToSum = array( + self::DOWNLOADS_RECORD_NAME, + self::OUTLINKS_RECORD_NAME, + self::SITE_SEARCH_RECORD_NAME, + ); + $aggregation = null; + $nameToCount = $this->getProcessor()->aggregateDataTableReports($dataTableToSum, + Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, + Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, + Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation, + $aggregation, + self::$invalidSummedColumnNameToRenamedNameFromPeriodArchive + ); + + $this->getProcessor()->aggregateNumericMetrics(array( + self::METRIC_PAGEVIEWS_RECORD_NAME, + self::METRIC_UNIQ_PAGEVIEWS_RECORD_NAME, + self::METRIC_DOWNLOADS_RECORD_NAME, + self::METRIC_UNIQ_DOWNLOADS_RECORD_NAME, + self::METRIC_OUTLINKS_RECORD_NAME, + self::METRIC_UNIQ_OUTLINKS_RECORD_NAME, + self::METRIC_SEARCHES_RECORD_NAME, + self::METRIC_SUM_TIME_RECORD_NAME, + self::METRIC_HITS_TIMED_RECORD_NAME, + )); + + // Unique Keywords can't be summed, instead we take the RowsCount() of the keyword table + $this->getProcessor()->insertNumericRecord(self::METRIC_KEYWORDS_RECORD_NAME, $nameToCount[self::SITE_SEARCH_RECORD_NAME]['level0']); } } diff --git a/plugins/Actions/ArchivingHelper.php b/plugins/Actions/ArchivingHelper.php index e5773d96e6..0f7744522c 100644 --- a/plugins/Actions/ArchivingHelper.php +++ b/plugins/Actions/ArchivingHelper.php @@ -22,7 +22,7 @@ class Piwik_Actions_ArchivingHelper const OTHERS_ROW_KEY = ''; /** - * FIXME See FIXME related to this function at Piwik_Actions_Archiving::archiveDay. + * FIXME See FIXME related to this function at Piwik_Actions_Archiver::archiveDay. * * @param Zend_Db_Statement|PDOStatement $query * @param string|bool $fieldQueried @@ -42,7 +42,7 @@ class Piwik_Actions_ArchivingHelper } if ($row['type'] != Piwik_Tracker_Action::TYPE_SITE_SEARCH) { - unset($row[Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT]); + unset($row[Piwik_Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT]); } // This will appear as <url /> in the API, which is actually very important to keep @@ -100,15 +100,15 @@ class Piwik_Actions_ArchivingHelper && !$actionRow->isSummaryRow() ) { if (($existingUrl = $actionRow->getMetadata('url')) !== false) { - if (!empty($row[Piwik_Archive::INDEX_PAGE_NB_HITS]) - && $row[Piwik_Archive::INDEX_PAGE_NB_HITS] > $actionRow->maxVisitsSummed + if (!empty($row[Piwik_Metrics::INDEX_PAGE_NB_HITS]) + && $row[Piwik_Metrics::INDEX_PAGE_NB_HITS] > $actionRow->maxVisitsSummed ) { $actionRow->setMetadata('url', $url); - $actionRow->maxVisitsSummed = $row[Piwik_Archive::INDEX_PAGE_NB_HITS]; + $actionRow->maxVisitsSummed = $row[Piwik_Metrics::INDEX_PAGE_NB_HITS]; } } else { $actionRow->setMetadata('url', $url); - $actionRow->maxVisitsSummed = !empty($row[Piwik_Archive::INDEX_PAGE_NB_HITS]) ? $row[Piwik_Archive::INDEX_PAGE_NB_HITS] : 0; + $actionRow->maxVisitsSummed = !empty($row[Piwik_Metrics::INDEX_PAGE_NB_HITS]) ? $row[Piwik_Metrics::INDEX_PAGE_NB_HITS] : 0; } } @@ -116,17 +116,17 @@ class Piwik_Actions_ArchivingHelper && $row['type'] != Piwik_Tracker_Action::TYPE_ACTION_NAME ) { // only keep performance metrics when they're used (i.e. for URLs and page titles) - if (array_key_exists(Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION, $row)) { - unset($row[Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION]); + if (array_key_exists(Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION, $row)) { + unset($row[Piwik_Metrics::INDEX_PAGE_SUM_TIME_GENERATION]); } - if (array_key_exists(Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, $row)) { - unset($row[Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION]); + if (array_key_exists(Piwik_Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, $row)) { + unset($row[Piwik_Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION]); } - if (array_key_exists(Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION, $row)) { - unset($row[Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION]); + if (array_key_exists(Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION, $row)) { + unset($row[Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION]); } - if (array_key_exists(Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION, $row)) { - unset($row[Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION]); + if (array_key_exists(Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION, $row)) { + unset($row[Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION]); } } @@ -150,7 +150,7 @@ class Piwik_Actions_ArchivingHelper // if the exit_action was not recorded properly in the log_link_visit_action // there would be an error message when getting the nb_hits column // we must fake the record and add the columns - if ($actionRow->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS) === false) { + if ($actionRow->getColumn(Piwik_Metrics::INDEX_PAGE_NB_HITS) === false) { // to test this code: delete the entries in log_link_action_visit for // a given exit_idaction_url foreach (self::getDefaultRow()->getColumns() as $name => $value) { @@ -173,7 +173,7 @@ class Piwik_Actions_ArchivingHelper */ private static function getColumnValuesMerged($columnName, $alreadyValue, $value) { - if ($columnName == Piwik_Archive::INDEX_PAGE_MIN_TIME_GENERATION) { + if ($columnName == Piwik_Metrics::INDEX_PAGE_MIN_TIME_GENERATION) { if (empty($alreadyValue)) { $newValue = $value; } else if (empty($value)) { @@ -183,7 +183,7 @@ class Piwik_Actions_ArchivingHelper } return $newValue; } - if ($columnName == Piwik_Archive::INDEX_PAGE_MAX_TIME_GENERATION) { + if ($columnName == Piwik_Metrics::INDEX_PAGE_MAX_TIME_GENERATION) { $newValue = max($alreadyValue, $value); return $newValue; } @@ -214,7 +214,7 @@ class Piwik_Actions_ArchivingHelper } self::$defaultActionName = Piwik_Config::getInstance()->General['action_default_name']; - self::$columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS; + self::$columnToSortByBeforeTruncation = Piwik_Metrics::INDEX_NB_VISITS; self::$maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_actions']; self::$maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_actions']; @@ -238,9 +238,9 @@ class Piwik_Actions_ArchivingHelper // so we add this fake row information to make sure there is a nb_hits, etc. column for every action $row = new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array( - Piwik_Archive::INDEX_NB_VISITS => 1, - Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 1, - Piwik_Archive::INDEX_PAGE_NB_HITS => 1, + Piwik_Metrics::INDEX_NB_VISITS => 1, + Piwik_Metrics::INDEX_NB_UNIQ_VISITORS => 1, + Piwik_Metrics::INDEX_PAGE_NB_HITS => 1, ))); } return $row; @@ -485,10 +485,10 @@ class Piwik_Actions_ArchivingHelper */ private static function getDefaultRowColumns() { - return array(Piwik_Archive::INDEX_NB_VISITS => 0, - Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 0, - Piwik_Archive::INDEX_PAGE_NB_HITS => 0, - Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT => 0); + return array(Piwik_Metrics::INDEX_NB_VISITS => 0, + Piwik_Metrics::INDEX_NB_UNIQ_VISITORS => 0, + Piwik_Metrics::INDEX_PAGE_NB_HITS => 0, + Piwik_Metrics::INDEX_PAGE_SUM_TIME_SPENT => 0); } /** diff --git a/plugins/Annotations/javascripts/annotations.js b/plugins/Annotations/javascripts/annotations.js index 9f1070b8a2..25718fbbb0 100755 --- a/plugins/Annotations/javascripts/annotations.js +++ b/plugins/Annotations/javascripts/annotations.js @@ -45,7 +45,6 @@ var ajaxRequest = new ajaxHelper(); ajaxRequest.addParams(ajaxParams, 'get'); - ajaxRequest.addParams({token_auth: piwik.token_auth}, 'post'); ajaxRequest.setCallback(callback); ajaxRequest.setFormat('html'); ajaxRequest.send(false); @@ -68,7 +67,6 @@ var ajaxRequest = new ajaxHelper(); ajaxRequest.addParams(ajaxParams, 'get'); - ajaxRequest.addParams({token_auth: piwik.token_auth}, 'post'); ajaxRequest.setCallback(callback); ajaxRequest.setFormat('html'); ajaxRequest.send(false); @@ -88,7 +86,6 @@ var ajaxRequest = new ajaxHelper(); ajaxRequest.addParams(ajaxParams, 'get'); - ajaxRequest.addParams({token_auth: piwik.token_auth}, 'post'); ajaxRequest.setCallback(callback); ajaxRequest.setFormat('html'); ajaxRequest.send(false); @@ -211,7 +208,7 @@ * @return {boolean} */ var isAnnotationStarred = function (annotation) { - return +$('.annotation-star', annotation).attr('data-starred') == 1 ? true : false; + return !!(+$('.annotation-star', annotation).attr('data-starred') == 1); }; /** diff --git a/plugins/CoreAdminHome/API.php b/plugins/CoreAdminHome/API.php index eded6d451f..e17fb28467 100644 --- a/plugins/CoreAdminHome/API.php +++ b/plugins/CoreAdminHome/API.php @@ -92,9 +92,6 @@ class Piwik_CoreAdminHome_API } } - // Lookup archive tables - $tables = Piwik::getTablesInstalled(); - $archiveTables = Piwik::getTablesArchivesInstalled(); // If using the feature "Delete logs older than N days"... $logsAreDeletedBeforeThisDate = Piwik_Config::getInstance()->Deletelogs['delete_logs_schedule_lowest_interval']; @@ -144,10 +141,10 @@ class Piwik_CoreAdminHome_API // In each table, invalidate day/week/month/year containing this date $sqlIdSites = implode(",", $idSites); + $archiveTables = Piwik_DataAccess_ArchiveTableCreator::getTablesArchivesInstalled(); foreach ($archiveTables as $table) { // Extract Y_m from table name - $suffix = str_replace(array('archive_numeric_', 'archive_blob_'), '', Piwik_Common::unprefixTable($table)); - + $suffix = Piwik_DataAccess_ArchiveTableCreator::getDateFromTableName($table); if (!isset($datesByMonth[$suffix])) { continue; } diff --git a/plugins/CoreAdminHome/Controller.php b/plugins/CoreAdminHome/Controller.php index cb914716cf..d9bf99b606 100644 --- a/plugins/CoreAdminHome/Controller.php +++ b/plugins/CoreAdminHome/Controller.php @@ -29,8 +29,8 @@ class Piwik_CoreAdminHome_Controller extends Piwik_Controller_Admin $view = new Piwik_View('@CoreAdminHome/generalSettings'); if (Piwik::isUserIsSuperUser()) { - $enableBrowserTriggerArchiving = Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled(); - $todayArchiveTimeToLive = Piwik_ArchiveProcessing::getTodayArchiveTimeToLive(); + $enableBrowserTriggerArchiving = Piwik_ArchiveProcessor_Rules::isBrowserTriggerEnabled(); + $todayArchiveTimeToLive = Piwik_ArchiveProcessor_Rules::getTodayArchiveTimeToLive(); $showWarningCron = false; if (!$enableBrowserTriggerArchiving && $todayArchiveTimeToLive < 3600 @@ -81,8 +81,8 @@ class Piwik_CoreAdminHome_Controller extends Piwik_Controller_Admin $enableBrowserTriggerArchiving = Piwik_Common::getRequestVar('enableBrowserTriggerArchiving'); $todayArchiveTimeToLive = Piwik_Common::getRequestVar('todayArchiveTimeToLive'); - Piwik_ArchiveProcessing::setBrowserTriggerArchiving((bool)$enableBrowserTriggerArchiving); - Piwik_ArchiveProcessing::setTodayArchiveTimeToLive($todayArchiveTimeToLive); + Piwik_ArchiveProcessor_Rules::setBrowserTriggerArchiving((bool)$enableBrowserTriggerArchiving); + Piwik_ArchiveProcessor_Rules::setTodayArchiveTimeToLive($todayArchiveTimeToLive); // Update email settings $mail = array(); diff --git a/plugins/CoreAdminHome/CoreAdminHome.php b/plugins/CoreAdminHome/CoreAdminHome.php index e6228d52fa..b23df6d5ac 100644 --- a/plugins/CoreAdminHome/CoreAdminHome.php +++ b/plugins/CoreAdminHome/CoreAdminHome.php @@ -111,17 +111,17 @@ class Piwik_CoreAdminHome extends Piwik_Plugin function purgeOutdatedArchives() { - $archiveTables = Piwik::getTablesArchivesInstalled(); + $archiveTables = Piwik_DataAccess_ArchiveTableCreator::getTablesArchivesInstalled(); foreach ($archiveTables as $table) { - if (strpos($table, 'numeric') !== false) { - Piwik_ArchiveProcessing_Period::doPurgeOutdatedArchives($table); - } + $date = Piwik_DataAccess_ArchiveTableCreator::getDateFromTableName($table); + list($month, $year) = explode('_', $date); + Piwik_DataAccess_ArchiveSelector::purgeOutdatedArchives(Piwik_Date::factory("$year-$month-15")); } } function optimizeArchiveTable() { - $archiveTables = Piwik::getTablesArchivesInstalled(); + $archiveTables = Piwik_DataAccess_ArchiveTableCreator::getTablesArchivesInstalled(); Piwik_OptimizeTables($archiveTables); } } diff --git a/plugins/CoreAdminHome/javascripts/generalSettings.js b/plugins/CoreAdminHome/javascripts/generalSettings.js index 6a493626c7..303e0d4e32 100644 --- a/plugins/CoreAdminHome/javascripts/generalSettings.js +++ b/plugins/CoreAdminHome/javascripts/generalSettings.js @@ -123,16 +123,17 @@ $(document).ready(function () { $('#customLogo').change(function () {$("#logoUploadForm").submit()}); // trusted hosts event handling - $('#trustedHostSettings .adminTable').on('click', '.remove-trusted-host', function (e) { + var trustedHostSettings = $('#trustedHostSettings'); + trustedHostSettings.find('.adminTable').on('click', '.remove-trusted-host', function (e) { e.preventDefault(); $(this).parent().parent().remove(); return false; }); - $('#trustedHostSettings .add-trusted-host').click(function (e) { + trustedHostSettings.find('.add-trusted-host').click(function (e) { e.preventDefault(); // append new row to the table - $('#trustedHostSettings tbody').append('<tr>' + $('#trustedHostSettings').find('tbody').append('<tr>' + '<td><input name="trusted_host" type="text" value=""/></td>' + '<td><a href="#" class="remove-trusted-host">x</a></td>' + '</tr>'); diff --git a/plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js b/plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js index d1ecd3705f..32e0aa52ed 100644 --- a/plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js +++ b/plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js @@ -65,7 +65,7 @@ siteUrls = {}, siteCurrencies = {}, allGoals = {}, - noneText = $('#image-tracker-goal>option').text(); + noneText = $('#image-tracker-goal').find('>option').text(); // queries Piwik for needed site info for one site var getSiteData = function (idSite, sectionSelect, callback) { @@ -104,11 +104,6 @@ } ); ajaxRequest.setCallback(function (data) { - for (var i = 0; i != data.length; ++i) { - data[i] = JSON.parse(data[i]); - } - - // set data var currency = data[0][0].currency || ''; siteCurrencies[idSite] = currencySymbols[currency.toUpperCase()]; siteUrls[idSite] = data[1] || []; @@ -142,7 +137,7 @@ // function that generates JS code var generateJsCode = function () { // get data - var idSite = $('#js-tracker-website .custom_select_main_link').attr('siteid'), + var idSite = $('#js-tracker-website').find('.custom_select_main_link').attr('siteid'), groupPageTitlesByDomain = $('#javascript-tracking-group-by-domain').is(':checked'), mergeSubdomains = $('#javascript-tracking-all-subdomains').is(':checked'), mergeAliasUrls = $('#javascript-tracking-all-aliases').is(':checked'), @@ -214,13 +209,13 @@ </script>\n\ <!-- End Piwik Code -->'; - $('#javascript-text textarea').val(result) + $('#javascript-text').find('textarea').val(result) }; // function that generates image tracker link var generateImageTrackerLink = function () { // get data ( (("https:" == document.location.protocol)?"https://' + piwikHost + '":"http://' + piwikHost + '") ) - var idSite = $('#image-tracker-website .custom_select_main_link').attr('siteid'), + var idSite = $('#image-tracker-website').find('.custom_select_main_link').attr('siteid'), path = document.location.pathname, piwikURL = ("https:" == document.location.protocol ? "https://" + piwikHost : "http://" + piwikHost) + path.substring(0, path.lastIndexOf('/')) + '/piwik.php', actionName = $('#image-tracker-action-name').val(), @@ -230,7 +225,7 @@ if ($('#image-tracking-goal-check').is(':checked')) { idGoal = $('#image-tracker-goal').val(); if (idGoal) { - revenue = $('#image-tracker-advanced-options .revenue').val(); + revenue = $('#image-tracker-advanced-options').find('.revenue').val(); } } @@ -256,7 +251,7 @@ <!-- End Piwik -->'; result = result.replace("&", "&", "g"); - $('#image-tracking-link textarea').val(result); + $('#image-tracking-link').find('textarea').val(result); }; // on image link tracker site change, change available goals @@ -328,11 +323,11 @@ }); // initial generation - getSiteData($( - '#js-tracker-website .custom_select_main_link').attr('siteid'), + getSiteData( + $('#js-tracker-website').find('.custom_select_main_link').attr('siteid'), '#js-code-options,#image-tracking-code-options', function () { - var imageTrackerSiteId = $('#image-tracker-website .custom_select_main_link').attr('siteid'); + var imageTrackerSiteId = $('#image-tracker-website').find('.custom_select_main_link').attr('siteid'); resetGoalSelectItems(imageTrackerSiteId, 'image-tracker-goal'); generateJsCode(); diff --git a/plugins/CoreAdminHome/templates/jsTrackingGenerator.twig b/plugins/CoreAdminHome/templates/jsTrackingGenerator.twig index e50bc5c30b..86f07a84d0 100644 --- a/plugins/CoreAdminHome/templates/jsTrackingGenerator.twig +++ b/plugins/CoreAdminHome/templates/jsTrackingGenerator.twig @@ -2,7 +2,7 @@ {% block head %} {{ parent() }} - <link rel="stylesheet" href="plugins/CoreAdminHome/stylesheets/jsTrackingGenerator.css"></link> + <link rel="stylesheet" href="plugins/CoreAdminHome/stylesheets/jsTrackingGenerator.css" /> <script type="text/javascript" src="plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js"></script> {% endblock %} diff --git a/plugins/CoreHome/Controller.php b/plugins/CoreHome/Controller.php index 9e0ace143d..006cb98f79 100644 --- a/plugins/CoreHome/Controller.php +++ b/plugins/CoreHome/Controller.php @@ -219,4 +219,25 @@ class Piwik_CoreHome_Controller extends Piwik_Controller $view->promoVideoUrl = 'http://www.youtube.com/watch?v=OslfF_EH81g'; echo $view->render(); } + + /** + * Redirects the user to a paypal so they can donate to Piwik. + */ + public function redirectToPaypal() + { + $parameters = Piwik_API_Request::getRequestArrayFromString($request = null); + foreach ($paramaters as $name => $param) { + if ($name == 'idSite' + || $name == 'module' + || $name == 'action' + ) { + unset($parameters[$name]); + } + } + + $url = "https://www.paypal.com/cgi-bin/webscr?".Piwik_Url::getQueryStringFromParameters($parameters); + + header("Location: $url"); + exit; + } } diff --git a/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php b/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php index 4c4a964c2e..1e761a0aae 100644 --- a/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php +++ b/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php @@ -74,12 +74,13 @@ class Piwik_CoreHome_DataTableRowAction_MultiRowEvolution /** * Generic method to get an evolution graph or a sparkline for the row evolution popover. * Do as much as possible from outside the controller. + * * @return Piwik_ViewDataTable */ public function getRowEvolutionGraph() { $view = parent::getRowEvolutionGraph(); - $view->setCustomParameter(self::IS_MULTI_EVOLUTION_PARAM, true); + $view->setCustomParameter(self::IS_MULTI_EVOLUTION_PARAM, 1); // set in JS return $view; } } diff --git a/plugins/CoreHome/DataTableRowAction/RowEvolution.php b/plugins/CoreHome/DataTableRowAction/RowEvolution.php index 8561d8734a..ab42ea3452 100644 --- a/plugins/CoreHome/DataTableRowAction/RowEvolution.php +++ b/plugins/CoreHome/DataTableRowAction/RowEvolution.php @@ -74,8 +74,9 @@ class Piwik_CoreHome_DataTableRowAction_RowEvolution $this->apiMethod = Piwik_Common::getRequestVar('apiMethod', '', 'string'); if (empty($this->apiMethod)) throw new Exception("Parameter apiMethod not set."); - $this->label = Piwik_Common::getRequestVar('label', '', 'string'); - $this->label = Piwik_Common::unsanitizeInputValue($this->label); + $this->label = Piwik_API_ResponseBuilder::getLabelFromRequest($_GET); + $this->label = $this->label[0]; + if ($this->label === '') throw new Exception("Parameter label not set."); $this->period = Piwik_Common::getRequestVar('period', '', 'string'); @@ -90,7 +91,7 @@ class Piwik_CoreHome_DataTableRowAction_RowEvolution list($this->date, $lastN) = Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution::getDateRangeAndLastN($this->period, $end); } - $this->segment = Piwik_Common::getRequestVar('segment', '', 'string'); + $this->segment = Piwik_ViewDataTable::getRawSegmentFromRequest(); $this->loadEvolutionReport(); } @@ -132,7 +133,7 @@ class Piwik_CoreHome_DataTableRowAction_RowEvolution $parameters = array( 'method' => 'API.getRowEvolution', - 'label' => urlencode($this->label), + 'label' => $this->label, 'apiModule' => $apiModule, 'apiAction' => $apiAction, 'idSite' => $this->idSite, diff --git a/plugins/CoreHome/javascripts/autocomplete.js b/plugins/CoreHome/javascripts/autocomplete.js index 0e5fa3d1fa..1a9a523c21 100644 --- a/plugins/CoreHome/javascripts/autocomplete.js +++ b/plugins/CoreHome/javascripts/autocomplete.js @@ -14,7 +14,7 @@ function switchSite(id, name, showAjaxLoading, idCanBeAll) { $('.sites_autocomplete input').val(id); $('.custom_select_main_link').text(name); $('.custom_select_main_link').addClass('custom_select_loading'); - broadcast.propagateNewPage('idSite=' + id, showAjaxLoading); + broadcast.propagateNewPage('segment=&idSite=' + id, showAjaxLoading); } return false; } @@ -29,6 +29,15 @@ $(function () { // sets up every un-inited site selector widget piwik.initSiteSelectors = function () { + function getUrlForWebsiteId(idSite) { + var idSiteParam = 'idSite=' + idSite; + var newParameters = 'segment=&' + idSiteParam; + var hash = broadcast.isHashExists() ? broadcast.getHashFromUrl() : "", + linkUrl = piwikHelper.getCurrentQueryStringWithParametersModified(newParameters) + + '#' + piwikHelper.getQueryStringWithParametersModified(hash.substring(1), newParameters); + return linkUrl; + } + $('.sites_autocomplete').each(function () { var selector = $(this); @@ -117,11 +126,8 @@ $(function () { } }).data("ui-autocomplete")._renderItem = function (ul, item) { $(ul).addClass('siteSelect'); - - var idSiteParam = 'idSite=' + item.id, - hash = broadcast.isHashExists() ? broadcast.getHashFromUrl().replace(/idSite=[0-9]+/, idSiteParam) : "", - linkUrl = piwikHelper.getCurrentQueryStringWithParametersModified(idSiteParam) + hash, - link = $("<a></a>").html(item.label).attr('href', linkUrl), + var linkUrl = getUrlForWebsiteId(item.id); + var link = $("<a></a>").html(item.label).attr('href', linkUrl), listItem = $('<li></li>'); listItem.data("item.ui-autocomplete", item) @@ -162,12 +168,10 @@ $(function () { $('.custom_select_block', selector).on('mouseenter', function () { $('.custom_select_ul_list li a', selector).each(function () { - var hash = broadcast.getHashFromUrl(); - hash = hash ? hash.replace(/idSite=[0-9]+/, 'idSite=' + $(this).attr('siteid')) : ""; + var idSite = $(this).attr('siteid'); - var queryString = piwikHelper.getCurrentQueryStringWithParametersModified( - 'idSite=' + $(this).attr('siteid')); - $(this).attr('href', queryString + hash); + var linkUrl = getUrlForWebsiteId(idSite); + $(this).attr('href', linkUrl); }); }); diff --git a/plugins/CoreHome/javascripts/broadcast.js b/plugins/CoreHome/javascripts/broadcast.js index ccfb145033..34352de732 100644 --- a/plugins/CoreHome/javascripts/broadcast.js +++ b/plugins/CoreHome/javascripts/broadcast.js @@ -307,10 +307,13 @@ var broadcast = { if (paramValue == '') { newParamValue = ''; } + var getQuotedRegex = function(str) { + return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + }; + if (valFromUrl != '') { // replacing current param=value to newParamValue; - valFromUrl = valFromUrl.replace(/\$/g, '\\$'); - valFromUrl = valFromUrl.replace(/\./g, '\\.'); + valFromUrl = getQuotedRegex(valFromUrl); var regToBeReplace = new RegExp(paramName + '=' + valFromUrl, 'ig'); if (newParamValue == '') { // if new value is empty remove leading &, aswell @@ -459,7 +462,10 @@ var broadcast = { hashStr = url.substring(url.indexOf("#"), url.length); } else { - hashStr = decodeURIComponent(location.hash); + locationSplit = location.href.split('#'); + if(typeof locationSplit[1] != 'undefined') { + hashStr = '#' + locationSplit[1]; + } } return hashStr; @@ -485,27 +491,36 @@ var broadcast = { }, /** - * Returns all key-value pairs in query string of url. - * - * @param {string} url url to check. if undefined, null or empty, current url is used. - * @return {object} key value pair describing query string parameters + * Extracts from a query strings, the request array + * @param queryString + * @returns {object} */ - getValuesFromUrl: function (url) { - var searchString = this._removeHashFromUrl(url).split('?')[1] || '', - pairs = searchString.split('&'); - + extractKeyValuePairsFromQueryString: function (queryString) { + var pairs = queryString.split('&'); var result = {}; for (var i = 0; i != pairs.length; ++i) { // attn: split with regex has bugs in several browsers such as IE 8 // so we need to split, use the first part as key and rejoin the rest var pair = pairs[i].split('='); - var key = pair.shift(); + var key = pair.shift(); result[key] = pair.join('='); } return result; }, /** + * Returns all key-value pairs in query string of url. + * + * @param {string} url url to check. if undefined, null or empty, current url is used. + * @return {object} key value pair describing query string parameters + */ + getValuesFromUrl: function (url) { + var searchString = this._removeHashFromUrl(url).split('?')[1] || ''; + return this.extractKeyValuePairsFromQueryString(searchString); + }, + + + /** * help to get param value for any given url string with provided param name * if no url is provided, it will get param from current address. * return: @@ -558,9 +573,12 @@ var broadcast = { endStr = url.length; } var value = url.substring(startStr + param.length + 1, endStr); - // sanitize values - value = value.replace(/[^_%\+\-\<\>!@\$\.=,;0-9a-zA-Z]/gi, ''); + // we sanitize values to add a protection layer against XSS + // &segment= value is not sanitized, since segments are designed to accept any user input + if(param != 'segment') { + value = value.replace(/[^_%~\*\+\-\<\>!@\$\.()=,;0-9a-zA-Z]/gi, ''); + } return value; } else { return ''; diff --git a/plugins/CoreHome/javascripts/calendar.js b/plugins/CoreHome/javascripts/calendar.js index e531e375a9..9952ac281b 100644 --- a/plugins/CoreHome/javascripts/calendar.js +++ b/plugins/CoreHome/javascripts/calendar.js @@ -289,8 +289,8 @@ }; var togglePeriodPickers = function (showSingle) { - $('#periodString .period-date').toggle(showSingle); - $('#periodString .period-range').toggle(!showSingle); + $('#periodString').find('.period-date').toggle(showSingle); + $('#periodString').find('.period-range').toggle(!showSingle); $('#calendarRangeApply').toggle(!showSingle); }; @@ -379,13 +379,13 @@ return false; }; - $("#otherPeriods label").on('click', function (e) { + $("#otherPeriods").find("label").on('click', function (e) { var id = $(e.target).attr('for'); changePeriodOnClick($('#' + id)); }); // when non-range period is clicked, change the period & refresh the date picker - $("#otherPeriods input").on('click', function (e) { + $("#otherPeriods").find("input").on('click', function (e) { var request_URL = $(e.target).val(), period = broadcast.getValueFromUrl('period', request_URL), lastPeriod = selectedPeriod; @@ -443,7 +443,7 @@ // reset date/period when opening calendar var firstClick = true; - $('#periodString #date').click(function () { + $('#periodString').find('#date').click(function () { if (!firstClick) { datepickerElem.datepicker('setDate', currentDate); $('#period_id_' + piwik.period).click(); @@ -497,7 +497,7 @@ if (!isValidDate(oDateFrom) || !isValidDate(oDateTo) || oDateFrom > oDateTo) { - $('#alert h2').text(_pk_translate('General_InvalidDateRange_js')); + $('#alert').find('h2').text(_pk_translate('General_InvalidDateRange_js')); piwikHelper.modalConfirm('#alert', {}); return false; } diff --git a/plugins/CoreHome/javascripts/corehome.js b/plugins/CoreHome/javascripts/corehome.js index 7c2ede9c2f..b80b54e57a 100755 --- a/plugins/CoreHome/javascripts/corehome.js +++ b/plugins/CoreHome/javascripts/corehome.js @@ -25,8 +25,7 @@ ajaxRequest.setLoadingElement('#header_message .loadingPiwik'); ajaxRequest.addParams({ module: 'CoreHome', - action: 'checkForUpdates', - token_auth: piwik.token_auth + action: 'checkForUpdates' }, 'get'); ajaxRequest.setCallback(function (response) { headerMessage.fadeOut('slow', function () { diff --git a/plugins/CoreHome/javascripts/datatable.js b/plugins/CoreHome/javascripts/datatable.js index 71c4da7cb8..0e4f5ffc43 100644 --- a/plugins/CoreHome/javascripts/datatable.js +++ b/plugins/CoreHome/javascripts/datatable.js @@ -84,7 +84,8 @@ dataTable.prototype = 'disable_generic_filters', 'columns', 'flat', - 'include_aggregate_rows' + 'include_aggregate_rows', + 'totalRows' ]; for (var key in filters) { @@ -1366,8 +1367,8 @@ dataTable.prototype = // if this url is also the url of a menu item, better to click that menu item instead of // doing AJAX request var menuItem = null; - $("#root>ul.nav a").each(function () { - if ($(this).attr('name') == url) { + $("#root").find(">ul.nav a").each(function () { + if ($(this).attr('href') == url) { menuItem = this; return false } diff --git a/plugins/CoreHome/javascripts/datatable_rowactions.js b/plugins/CoreHome/javascripts/datatable_rowactions.js index 62931dd61e..5aea12e4b1 100644 --- a/plugins/CoreHome/javascripts/datatable_rowactions.js +++ b/plugins/CoreHome/javascripts/datatable_rowactions.js @@ -213,6 +213,7 @@ DataTable_RowAction.prototype.getLabelFromTr = function (tr) { if (!value) { value = label.text(); } + value = value.trim(); return encodeURIComponent(value); }; diff --git a/plugins/CoreHome/javascripts/date.js b/plugins/CoreHome/javascripts/date.js index 1c6c5971bd..776ac417ae 100644 --- a/plugins/CoreHome/javascripts/date.js +++ b/plugins/CoreHome/javascripts/date.js @@ -23,18 +23,12 @@ $(document).ready(function () { } }; - $("#periodString #date") - .hover(function () { - $(this).css({ cursor: "pointer"}); - }, function () { - - }) - .click(function () { - periodWidget.toggle(); - if ($("#periodMore").is(":visible")) { - $("#periodMore .ui-state-highlight").removeClass('ui-state-highlight'); - } - }); + $("#periodString").on('click', "#date,.calendar-icon", function () { + periodWidget.toggle(); + if ($("#periodMore").is(":visible")) { + $("#periodMore").find(".ui-state-highlight").removeClass('ui-state-highlight'); + } + }); //close periodString onClickOutside $('body').on('mouseup', function (e) { diff --git a/plugins/CoreHome/javascripts/menu.js b/plugins/CoreHome/javascripts/menu.js index 3be160b1ca..b2010152be 100644 --- a/plugins/CoreHome/javascripts/menu.js +++ b/plugins/CoreHome/javascripts/menu.js @@ -29,7 +29,7 @@ menu.prototype = onItemClick: function (item) { $('ul.nav').trigger('piwikSwitchPage', item); - broadcast.propagateAjax($(item).attr('name')); + broadcast.propagateAjax( $(item).attr('href').substr(1) ); return false; }, @@ -45,7 +45,7 @@ menu.prototype = // for all sub menu we want to have a unique id based on their module and action // for main menu we want to add just the module as its id. this.menuNode.find('li').each(function () { - var url = $(this).find('a').attr('name'); + var url = $(this).find('a').attr('href').substr(1); var module = broadcast.getValueFromUrl("module", url); var action = broadcast.getValueFromUrl("action", url); var moduleId = broadcast.getValueFromUrl("idGoal", url) || broadcast.getValueFromUrl("idDashboard", url); diff --git a/plugins/CoreHome/templates/donate.twig b/plugins/CoreHome/templates/donate.twig index c33dfffe9e..ad5ad8dee7 100755 --- a/plugins/CoreHome/templates/donate.twig +++ b/plugins/CoreHome/templates/donate.twig @@ -13,7 +13,7 @@ <div class="donate-form-instructions">({{ 'CoreHome_DonateFormInstructions'|translate }})</div> - <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank"> + <form action="index.php?module=CoreHome&action=redirectToPaypal&idSite=1" method="post" target="_blank"> <input type="hidden" name="cmd" value="_s-xclick"/> <input type="hidden" name="hosted_button_id" value="DVKLY73RS7JTE"/> <input type="hidden" name="currency_code" value="USD"/> @@ -36,7 +36,7 @@ <input type="image" src="plugins/Zeitgeist/images/paypal_subscribe.gif" border="0" name="submit" title="{{ 'CoreHome_SubscribeAndBecomePiwikSupporter'|translate }}"/> <a class="donate-spacer">{{ 'CoreHome_MakeOneTimeDonation'|translate }}</a> - <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RPL23NJURMTFA&bb2_screener_=1357583494+83.233.186.82" + <a href="index.php?module=CoreHome&action=redirectToPaypal&idSite=1&cmd=_s-xclick&hosted_button_id=RPL23NJURMTFA&bb2_screener_=1357583494+83.233.186.82" target="_blank" class="donate-one-time">{{ 'CoreHome_MakeOneTimeDonation'|translate }}</a> </div> diff --git a/plugins/CoreHome/templates/html_report_header.twig b/plugins/CoreHome/templates/html_report_header.twig index 2e74bcc038..0e6879a1c6 100644 --- a/plugins/CoreHome/templates/html_report_header.twig +++ b/plugins/CoreHome/templates/html_report_header.twig @@ -15,6 +15,12 @@ {{ description }} - {{ 'General_DateRange'|translate }} {{ prettyDate }} </p> +{% if displaySegment %} +<p style="color: rgb({{ reportTitleTextColor }});"> + {{ 'PDFReports_CustomVisitorSegment'|translate("Piwik") }} {{ segmentName }} +</p> +{% endif %} + {% if reportMetadata|length > 1 %} <h2 style="color: rgb({{ reportTitleTextColor }}); font-size: {{ reportTitleTextSize }}pt;"> {{ 'PDFReports_TableOfContent'|translate }} diff --git a/plugins/CoreHome/templates/menu.twig b/plugins/CoreHome/templates/menu.twig index 9b7ae232c2..2d8cefed98 100644 --- a/plugins/CoreHome/templates/menu.twig +++ b/plugins/CoreHome/templates/menu.twig @@ -6,8 +6,10 @@ <ul> {% for name,urlParameters in level2 %} {% if name|slice(0,1) != '_' %} - <li><a name='{{ urlParameters._url|urlRewriteWithParameters }}' href='#{{ urlParameters._url|urlRewriteWithParameters|slice(1) }}' - onclick='return piwikMenu.onItemClick(this);'>{{ name|translate }}</a></li> + <li> + <a href='#{{ urlParameters._url|urlRewriteWithParameters|slice(1) }}' + onclick='return piwikMenu.onItemClick(this);'>{{ name|translate }}</a> + </li> {% endif %} {% endfor %} </ul> diff --git a/plugins/CoreHome/templates/period_select.twig b/plugins/CoreHome/templates/period_select.twig index bf63a6d752..6b33a9fea3 100644 --- a/plugins/CoreHome/templates/period_select.twig +++ b/plugins/CoreHome/templates/period_select.twig @@ -2,6 +2,7 @@ <div id="periodString"> <div id="date">{{ 'General_DateRange'|translate }} <b>{{ prettyDate }}</b></div> + <div class="calendar-icon"></div> <div id="periodMore"> <div class="period-date"> <h6>{{ 'General_Date'|translate }}</h6> diff --git a/plugins/CoreHome/templates/reports_by_dimension.twig b/plugins/CoreHome/templates/reports_by_dimension.twig index de23fe1053..acea8389b3 100644 --- a/plugins/CoreHome/templates/reports_by_dimension.twig +++ b/plugins/CoreHome/templates/reports_by_dimension.twig @@ -16,7 +16,7 @@ {% endfor %} </div> - <div style="float:left;"> + <div style="float:left;max-width:900px;"> <div class="loadingPiwik" style="display:none"> <img src="plugins/Zeitgeist/images/loading-blue.gif" alt=""/>{{ 'General_LoadingData'|translate }} </div> diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php index ef57443698..387aba3d4b 100644 --- a/plugins/CoreUpdater/Controller.php +++ b/plugins/CoreUpdater/Controller.php @@ -163,7 +163,7 @@ class Piwik_CoreUpdater_Controller extends Piwik_Controller /* * Make sure the execute bit is set for this shell script */ - if (!Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled()) { + if (!Piwik_ArchiveProcessor_Rules::isBrowserTriggerEnabled()) { @chmod($this->pathRootExtractedPiwik . '/misc/cron/archive.sh', 0755); } diff --git a/plugins/CustomVariables/API.php b/plugins/CustomVariables/API.php index 3a47c837c5..338eb6d74a 100644 --- a/plugins/CustomVariables/API.php +++ b/plugins/CustomVariables/API.php @@ -41,9 +41,10 @@ class Piwik_CustomVariables_API */ protected function getDataTable($idSite, $period, $date, $segment, $expanded, $idSubtable) { - $dataTable = Piwik_Archive::getDataTableFromArchive('CustomVariables_valueByName', $idSite, $period, $date, $segment, $expanded, $idSubtable); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); + $dataTable = Piwik_Archive::getDataTableFromArchive(Piwik_CustomVariables_Archiver::CUSTOM_VARIABLE_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $idSubtable); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_ACTIONS, 'desc', $naturalSort = false, $expanded)); $dataTable->queueFilter('ReplaceColumnNames'); + $dataTable->queueFilter('ColumnDelete', 'nb_uniq_visitors'); return $dataTable; } @@ -64,7 +65,7 @@ class Piwik_CustomVariables_API if ($dataTable instanceof Piwik_DataTable && !$_leavePiwikCoreVariables ) { - $mapping = array('_pks', '_pkn', '_pkc', '_pkp', Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT, Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY); + $mapping = self::getReservedCustomVariableKeys(); foreach ($mapping as $name) { $row = $dataTable->getRowFromLabel($name); if ($row) { @@ -76,6 +77,15 @@ class Piwik_CustomVariables_API } /** + * @ignore + * @return array + */ + public static function getReservedCustomVariableKeys() + { + return array('_pks', '_pkn', '_pkc', '_pkp', Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT, Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY); + } + + /** * @param int $idSite * @param string $period * @param Piwik_Date $date @@ -96,7 +106,7 @@ class Piwik_CustomVariables_API $dataTable->renameColumn('price_viewed', 'price'); } $dataTable->queueFilter('ColumnCallbackReplace', array('label', create_function('$label', ' - return $label == Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED + return $label == Piwik_CustomVariables_Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED ? "' . Piwik_Translate('General_NotDefined', Piwik_Translate('CustomVariables_ColumnCustomVariableValue')) . '" : $label;'))); return $dataTable; diff --git a/plugins/CustomVariables/Archiver.php b/plugins/CustomVariables/Archiver.php new file mode 100644 index 0000000000..4b63845215 --- /dev/null +++ b/plugins/CustomVariables/Archiver.php @@ -0,0 +1,198 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_CustomVariables + */ + +class Piwik_CustomVariables_Archiver extends Piwik_PluginsArchiver +{ + const LABEL_CUSTOM_VALUE_NOT_DEFINED = "Value not defined"; + const CUSTOM_VARIABLE_RECORD_NAME = 'CustomVariables_valueByName'; + + /** + * @var Piwik_DataArray + */ + protected $dataArray; + protected $maximumRowsInDataTableLevelZero; + protected $maximumRowsInSubDataTable; + protected $newEmptyRow; + + function __construct($processor) + { + parent::__construct($processor); + $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_custom_variables']; + $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_custom_variables']; + } + + public function archiveDay() + { + $this->dataArray = new Piwik_DataArray(); + + for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) { + $this->aggregateCustomVariable($i); + } + + $this->removeVisitsMetricsFromActionsAggregate(); + $this->dataArray->enrichMetricsWithConversions(); + $table = $this->getProcessor()->getDataTableFromDataArray($this->dataArray); + $blob = $table->getSerialized( + $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, + $columnToSort = Piwik_Metrics::INDEX_NB_VISITS + ); + + $this->getProcessor()->insertBlobRecord(self::CUSTOM_VARIABLE_RECORD_NAME, $blob); + } + + protected function aggregateCustomVariable($slot) + { + $keyField = "custom_var_k" . $slot; + $valueField = "custom_var_v" . $slot; + $where = "%s.$keyField != ''"; + $dimensions = array($keyField, $valueField); + + $query = $this->getLogAggregator()->queryVisitsByDimension($dimensions, $where); + $this->aggregateFromVisits($query, $keyField, $valueField); + + // IF we query Custom Variables scope "page" either: Product SKU, Product Name, + // then we also query the "Product page view" price which was possibly recorded. + $additionalSelects = false; + // FIXMEA + if (in_array($slot, array(3,4,5))) { + $additionalSelects = array( $this->getSelectAveragePrice() ); + } + $query = $this->getLogAggregator()->queryActionsByDimension($dimensions, $where, $additionalSelects); + $this->aggregateFromActions($query, $keyField, $valueField); + + $query = $this->getLogAggregator()->queryConversionsByDimension($dimensions, $where); + $this->aggregateFromConversions($query, $keyField, $valueField); + } + + protected function getSelectAveragePrice() + { + return Piwik_DataAccess_LogAggregator::getSqlRevenue("AVG(log_link_visit_action.custom_var_v2)") + . " as `" . Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED . "`"; + } + + protected function aggregateFromVisits($query, $keyField, $valueField) + { + while ($row = $query->fetch()) { + $key = $row[$keyField]; + $value = $this->cleanCustomVarValue($row[$valueField]); + + $this->dataArray->sumMetricsVisits($key, $row); + $this->dataArray->sumMetricsVisitsPivot($key, $value, $row); + } + } + + protected function cleanCustomVarValue($value) + { + if (strlen($value)) { + return $value; + } + return self::LABEL_CUSTOM_VALUE_NOT_DEFINED; + } + + + protected function aggregateFromActions($query, $keyField, $valueField) + { + while ($row = $query->fetch()) { + $key = $row[$keyField]; + $value = $this->cleanCustomVarValue($row[$valueField]); + + $alreadyAggregated = $this->aggregateEcommerceCategories($key, $value, $row); + if (!$alreadyAggregated) { + $this->aggregateActionByKeyAndValue($key, $value, $row); + $this->dataArray->sumMetricsActions($key, $row); + } + } + } + + /** + * @return bool True if the $row metrics were already added to the ->metrics + */ + protected function aggregateEcommerceCategories($key, $value, $row) + { + $ecommerceCategoriesAggregated = false; + if ($key == '_pkc' + && $value[0] == '[' && $value[1] == '"' + ) { + // In case categories were truncated, try closing the array + if (substr($value, -2) != '"]') { + $value .= '"]'; + } + $decoded = @Piwik_Common::json_decode($value); + if (is_array($decoded)) { + $count = 0; + foreach ($decoded as $category) { + if (empty($category) + || $count >= Piwik_Tracker_GoalManager::MAXIMUM_PRODUCT_CATEGORIES + ) { + continue; + } + $this->aggregateActionByKeyAndValue($key, $category, $row); + $ecommerceCategoriesAggregated = true; + $count++; + } + } + } + return $ecommerceCategoriesAggregated; + } + + protected function aggregateActionByKeyAndValue($key, $value, $row) + { + $this->dataArray->sumMetricsActionsPivot($key, $value, $row); + + if ($this->isReservedKey($key)) { + // Price tracking on Ecommerce product/category pages: + // the average is returned from the SQL query so the price is not "summed" like other metrics + $index = Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED; + if (!empty($row[$index])) { + $this->dataArray->setRowColumnPivot($key, $value, $index, (float)$row[$index]); + } + } + } + + protected static function isReservedKey($key) + { + return in_array($key, Piwik_CustomVariables_API::getReservedCustomVariableKeys()); + } + + + protected function aggregateFromConversions($query, $keyField, $valueField) + { + if ($query === false) { + return; + } + while ($row = $query->fetch()) { + $key = $row[$keyField]; + $value = $this->cleanCustomVarValue($row[$valueField]); + $this->dataArray->sumMetricsGoals($key, $row); + $this->dataArray->sumMetricsGoalsPivot($key, $value, $row); + } + } + + protected function removeVisitsMetricsFromActionsAggregate() + { + $dataArray = &$this->dataArray->getDataArray(); + foreach ($dataArray as $key => &$row) { + if (!self::isReservedKey($key) + && Piwik_DataArray::isRowActions($row) + ) { + unset($row[Piwik_Metrics::INDEX_NB_UNIQ_VISITORS]); + unset($row[Piwik_Metrics::INDEX_NB_VISITS]); + } + } + } + + public function archivePeriod() + { + $nameToCount = $this->getProcessor()->aggregateDataTableReports( + self::CUSTOM_VARIABLE_RECORD_NAME, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, + $columnToSort = Piwik_Metrics::INDEX_NB_VISITS); + } +}
\ No newline at end of file diff --git a/plugins/CustomVariables/Controller.php b/plugins/CustomVariables/Controller.php index ea00259d3e..867988a79b 100644 --- a/plugins/CustomVariables/Controller.php +++ b/plugins/CustomVariables/Controller.php @@ -10,49 +10,52 @@ */ /** - * * @package Piwik_CustomVariables */ class Piwik_CustomVariables_Controller extends Piwik_Controller { - - function index($fetch = false) + public function index($fetch = false) { return Piwik_View::singleReport( Piwik_Translate('CustomVariables_CustomVariables'), $this->getCustomVariables(true), $fetch); } - function getCustomVariables($fetch = false) + public function getCustomVariables($fetch = false) { $view = Piwik_ViewDataTable::factory(); $view->init($this->pluginName, __FUNCTION__, "CustomVariables.getCustomVariables", "getCustomVariablesValuesFromNameId"); $this->setPeriodVariablesView($view); - $view->enableShowGoals(); + $this->setMetricsVariablesView($view); - $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_actions')); + $this->configureView($view); $view->setColumnTranslation('label', Piwik_Translate('CustomVariables_ColumnCustomVariableName')); - $view->setSortedColumn('nb_visits'); - $view->setLimit(10); + $view->setFooterMessage(Piwik_Translate('CustomVariables_TrackingHelp', array('<a target="_blank" href="http://piwik.org/docs/custom-variables/">', '</a>'))); - $this->setMetricsVariablesView($view); + return $this->renderView($view, $fetch); } - function getCustomVariablesValuesFromNameId($fetch = false) + public function getCustomVariablesValuesFromNameId($fetch = false) { $view = Piwik_ViewDataTable::factory(); $view->init($this->pluginName, __FUNCTION__, 'CustomVariables.getCustomVariablesValuesFromNameId'); + $this->configureView($view); $view->disableSearchBox(); - $view->enableShowGoals(); $view->disableExcludeLowPopulation(); - $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_actions')); $view->setColumnTranslation('label', Piwik_Translate('CustomVariables_ColumnCustomVariableValue')); - return $this->renderView($view, $fetch); } + protected function configureView($view) + { + $view->setColumnsToDisplay(array('label', 'nb_actions', 'nb_visits')); + $view->setSortedColumn('nb_actions'); + $view->enableShowGoals(); + } + + } diff --git a/plugins/CustomVariables/CustomVariables.php b/plugins/CustomVariables/CustomVariables.php index d76adab5ea..3f3421fe41 100644 --- a/plugins/CustomVariables/CustomVariables.php +++ b/plugins/CustomVariables/CustomVariables.php @@ -14,11 +14,6 @@ */ class Piwik_CustomVariables extends Piwik_Plugin { - public $archiveProcessing; - protected $columnToSortByBeforeTruncation; - protected $maximumRowsInDataTableLevelZero; - protected $maximumRowsInSubDataTable; - public function getInformation() { $info = array( @@ -146,183 +141,31 @@ class Piwik_CustomVariables extends Piwik_Plugin )); } - function __construct() - { - $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_referers']; - $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers']; - } - - protected $interestByCustomVariables = array(); - protected $interestByCustomVariablesAndValue = array(); - /** * Hooks on daily archive to trigger various log processing * * @param Piwik_Event_Notification $notification notification object - * @return void */ public function archiveDay($notification) { - $this->interestByCustomVariables = $this->interestByCustomVariablesAndValue = array(); - - /** - * @var Piwik_ArchiveProcessing_Day - */ - $this->archiveProcessing = $notification->getNotificationObject(); - - if (!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->archiveDayAggregate($this->archiveProcessing); - $this->archiveDayRecordInDatabase($this->archiveProcessing); - destroy($this->interestByCustomVariables); - destroy($this->interestByCustomVariablesAndValue); - } - - const LABEL_CUSTOM_VALUE_NOT_DEFINED = "Value not defined"; - - /** - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - * @return void - */ - protected function archiveDayAggregate(Piwik_ArchiveProcessing_Day $archiveProcessing) - { - for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) { - $keyField = "custom_var_k" . $i; - $valueField = "custom_var_v" . $i; - $dimensions = array($keyField, $valueField); - $where = "%s.$keyField != ''"; - - // Custom Vars names and values metrics for visits - $query = $archiveProcessing->queryVisitsByDimension($dimensions, $where); - - while ($row = $query->fetch()) { - // Handle case custom var value is empty - $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]); - - // Aggregate - if (!isset($this->interestByCustomVariables[$row[$keyField]])) $this->interestByCustomVariables[$row[$keyField]] = $archiveProcessing->getNewInterestRow(); - if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]] = $archiveProcessing->getNewInterestRow(); - $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariables[$row[$keyField]]); - $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]]); - } - - // Custom Vars names and values metrics for page views - $query = $archiveProcessing->queryActionsByDimension($dimensions, $where); - $onlyMetricsAvailableInActionsTable = true; - while ($row = $query->fetch()) { - // Handle case custom var value is empty - $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]); - - $label = $row[$valueField]; - - // Remove price tracked if it's zero or we if we are not currently tracking an ecommerce var - if (!in_array($row[$keyField], array('_pks', '_pkn', '_pkc'))) { - unset($row[Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED]); - } - - // when custom variable value is a JSON array of categories - // possibly JSON value - $mustInsertCustomVariableValue = true; - if ($row[$keyField] == '_pkc' - && $label[0] == '[' && $label[1] == '"' - ) { - // In case categories were truncated, try closing the array - if (substr($label, -2) != '"]') { - $label .= '"]'; - } - $decoded = @Piwik_Common::json_decode($label); - if (is_array($decoded)) { - $count = 0; - foreach ($decoded as $category) { - if (empty($category) - || $count >= Piwik_Tracker_GoalManager::MAXIMUM_PRODUCT_CATEGORIES - ) { - continue; - } - if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$category])) { - $this->interestByCustomVariablesAndValue[$row[$keyField]][$category] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable); - } - $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$category], $onlyMetricsAvailableInActionsTable); - $mustInsertCustomVariableValue = false; - $count++; - } - } - } // end multi categories hack + $archiveProcessor = $notification->getNotificationObject(); - if ($mustInsertCustomVariableValue) { - if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable); - $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]], $onlyMetricsAvailableInActionsTable); - } - - // Do not report on Price viewed for the Custom Variable names - unset($row[Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED]); - - // When tracking Custom Variables with scope=page we do not add up visits numbers - // as it is incorrect to sum visits this way - // for scope=visit this is allowed, since there is supposed to be one custom var value per custom variable name for a given visit - $doNotSumVisits = true; - - if (!isset($this->interestByCustomVariables[$row[$keyField]])) $this->interestByCustomVariables[$row[$keyField]] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable, $doNotSumVisits); - $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariables[$row[$keyField]], $onlyMetricsAvailableInActionsTable, $doNotSumVisits); - } - - // Custom Vars names and values metrics for Goals - $query = $archiveProcessing->queryConversionsByDimension($dimensions, $where); - - if ($query !== false) { - while ($row = $query->fetch()) { - // Handle case custom var value is empty - $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]); - - if (!isset($this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - - $archiveProcessing->updateGoalStats($row, $this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - $archiveProcessing->updateGoalStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - } - } - } - $archiveProcessing->enrichConversionsByLabelArray($this->interestByCustomVariables); - $archiveProcessing->enrichConversionsByLabelArrayHasTwoLevels($this->interestByCustomVariablesAndValue); - } - - protected function cleanCustomVarValue($value) - { - if (strlen($value)) { - return $value; + $archiving = new Piwik_CustomVariables_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); } - return self::LABEL_CUSTOM_VALUE_NOT_DEFINED; - } - - /** - * @param Piwik_ArchiveProcessing $archiveProcessing - * @return void - */ - protected function archiveDayRecordInDatabase($archiveProcessing) - { - $recordName = 'CustomVariables_valueByName'; - $table = $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestByCustomVariablesAndValue, $this->interestByCustomVariables); - - $blob = $table->getSerialized( - $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, - $columnToSort = Piwik_Archive::INDEX_NB_VISITS); - $archiveProcessing->insertBlobRecord($recordName, $blob); - destroy($table); } /** * @param Piwik_Event_Notification $notification notification object - * @return mixed */ function archivePeriod($notification) { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $dataTableToSum = 'CustomVariables_valueByName'; - $nameToCount = $archiveProcessing->archiveDataTable( - $dataTableToSum, null, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, - $columnToSort = Piwik_Archive::INDEX_NB_VISITS); + $archiveProcessor = $notification->getNotificationObject(); + $archiving = new Piwik_CustomVariables_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); + } } + } diff --git a/plugins/DBStats/API.php b/plugins/DBStats/API.php index fec0e0fe71..693c7f2e1e 100644 --- a/plugins/DBStats/API.php +++ b/plugins/DBStats/API.php @@ -128,9 +128,7 @@ class Piwik_DBStats_API $rowToAddTo['row_count'] += $status['Rows']; } - $result = new Piwik_DataTable(); - $result->addRowsFromArrayWithIndexLabel($rows); - return $result; + return Piwik_DataTable::makeFromIndexedArray($rows); } /** diff --git a/plugins/DBStats/MySQLMetadataProvider.php b/plugins/DBStats/MySQLMetadataProvider.php index 6240a24b19..be487346cb 100755 --- a/plugins/DBStats/MySQLMetadataProvider.php +++ b/plugins/DBStats/MySQLMetadataProvider.php @@ -243,7 +243,6 @@ class Piwik_DBStats_MySQLMetadataProvider array($cols, 'estimated_size', $getEstimatedSize, array($status))); $dataTable->addDataTable($table); - destroy($table); } return $dataTable; } diff --git a/plugins/Dashboard/API.php b/plugins/Dashboard/API.php index 14a058b2aa..59ae8b9ae1 100644 --- a/plugins/Dashboard/API.php +++ b/plugins/Dashboard/API.php @@ -62,7 +62,7 @@ class Piwik_Dashboard_API * * @return array[] */ - public function getDefaultDashboard() + private function getDefaultDashboard() { $defaultLayout = $this->dashboard->getDefaultLayout(); $defaultLayout = $this->dashboard->decodeLayout($defaultLayout); @@ -79,7 +79,7 @@ class Piwik_Dashboard_API * * @return array[] */ - public function getUserDashboards() + private function getUserDashboards() { $userLogin = Piwik::getCurrentUserLogin(); $userDashboards = $this->dashboard->getAllDashboards($userLogin); diff --git a/plugins/Dashboard/Controller.php b/plugins/Dashboard/Controller.php index 204cf8714d..ec63d82e4a 100644 --- a/plugins/Dashboard/Controller.php +++ b/plugins/Dashboard/Controller.php @@ -153,8 +153,11 @@ class Piwik_Dashboard_Controller extends Piwik_Controller public function getAllDashboards() { $this->checkTokenInUrl(); + if (Piwik::isUserIsAnonymous()) { + Piwik_DataTable_Renderer_Json::sendHeaderJSON(); echo '[]'; + return; } diff --git a/plugins/Dashboard/Dashboard.php b/plugins/Dashboard/Dashboard.php index 47a8dfedea..716e055bd7 100644 --- a/plugins/Dashboard/Dashboard.php +++ b/plugins/Dashboard/Dashboard.php @@ -209,7 +209,6 @@ class Piwik_Dashboard extends Piwik_Plugin $pos++; } } - } } diff --git a/plugins/Dashboard/javascripts/dashboard.js b/plugins/Dashboard/javascripts/dashboard.js index 298298e44c..e23ff669a4 100644 --- a/plugins/Dashboard/javascripts/dashboard.js +++ b/plugins/Dashboard/javascripts/dashboard.js @@ -24,7 +24,7 @@ function initDashboard(dashboardId, dashboardLayout) { if (!$('#topBars').length) { $('#dashboardSettings').css({left: 0}); $('#dashboardSettings').after($('#Dashboard')); - $('#Dashboard > ul li a').each(function () {$(this).css({width: this.offestWidth + 30, paddingLeft: 0, paddingRight: 0});}); + $('#Dashboard').find('> ul li a').each(function () {$(this).css({width: this.offestWidth + 30, paddingLeft: 0, paddingRight: 0});}); $('#Dashboard_embeddedIndex_' + dashboardId).addClass('sfHover'); } @@ -36,7 +36,7 @@ function initDashboard(dashboardId, dashboardLayout) { $('#removeDashboardLink').show(); } // fix position - $('#dashboardSettings .widgetpreview-widgetlist').css('paddingTop', $('#dashboardSettings .widgetpreview-categorylist').parent('li').position().top); + $('#dashboardSettings').find('.widgetpreview-widgetlist').css('paddingTop', $('#dashboardSettings').find('.widgetpreview-categorylist').parent('li').position().top); }); $('body').on('mouseup', function (e) { if (!$(e.target).parents('#dashboardSettings').length && !$(e.target).is('#dashboardSettings')) { @@ -56,7 +56,7 @@ function initDashboard(dashboardId, dashboardLayout) { $('#dashboardSettings').widgetPreview({ isWidgetAvailable: function (widgetUniqueId) { - return !$('#dashboardWidgetsArea [widgetId=' + widgetUniqueId + ']').length; + return !$('#dashboardWidgetsArea').find('[widgetId=' + widgetUniqueId + ']').length; }, onSelect: function (widgetUniqueId) { var widget = widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId); @@ -66,7 +66,7 @@ function initDashboard(dashboardId, dashboardLayout) { resetOnSelect: true }); - $('#columnPreview>div').each(function () { + $('#columnPreview').find('>div').each(function () { var width = []; $('div', this).each(function () { width.push(this.className.replace(/width-/, '')); @@ -74,8 +74,8 @@ function initDashboard(dashboardId, dashboardLayout) { $(this).attr('layout', width.join('-')); }); - $('#columnPreview>div').on('click', function () { - $('#columnPreview>div').removeClass('choosen'); + $('#columnPreview').find('>div').on('click', function () { + $('#columnPreview').find('>div').removeClass('choosen'); $(this).addClass('choosen'); }); @@ -122,15 +122,15 @@ function renameDashboard() { } function removeDashboard() { - $('#removeDashboardConfirm h2 span').html($('#dashboardWidgetsArea').dashboard('getDashboardName')); + $('#removeDashboardConfirm').find('h2 span').text($('#dashboardWidgetsArea').dashboard('getDashboardName')); piwikHelper.modalConfirm('#removeDashboardConfirm', {yes: function () { $('#dashboardWidgetsArea').dashboard('removeDashboard'); }}); } function showChangeDashboardLayoutDialog() { - $('#columnPreview>div').removeClass('choosen'); - $('#columnPreview>div[layout=' + $('#dashboardWidgetsArea').dashboard('getColumnLayout') + ']').addClass('choosen'); + $('#columnPreview').find('>div').removeClass('choosen'); + $('#columnPreview').find('>div[layout=' + $('#dashboardWidgetsArea').dashboard('getColumnLayout') + ']').addClass('choosen'); piwikHelper.modalConfirm('#changeDashboardLayout', {yes: function () { - $('#dashboardWidgetsArea').dashboard('setColumnLayout', $('#changeDashboardLayout .choosen').attr('layout')); + $('#dashboardWidgetsArea').dashboard('setColumnLayout', $('#changeDashboardLayout').find('.choosen').attr('layout')); }}); } @@ -159,12 +159,12 @@ function copyDashboardToUser() { function (availableUsers) { $('#copyDashboardUser').empty(); $('#copyDashboardUser').append( - $('<option></option>').val(piwik.userLogin).html(piwik.userLogin) + $('<option></option>').val(piwik.userLogin).text(piwik.userLogin) ); $.each(availableUsers, function (index, user) { if (user.login != 'anonymous' && user.login != piwik.userLogin) { $('#copyDashboardUser').append( - $('<option></option>').val(user.login).html(user.login + ' (' + user.alias + ')') + $('<option></option>').val(user.login).text(user.login + ' (' + user.alias + ')') ); } }); @@ -189,7 +189,7 @@ function copyDashboardToUser() { }, 'post'); ajaxRequest.setCallback( function (id) { - $('#alert h2').text(_pk_translate('Dashboard_DashboardCopied_js')); + $('#alert').find('h2').text(_pk_translate('Dashboard_DashboardCopied_js')); piwikHelper.modalConfirm('#alert', {}); } ); diff --git a/plugins/Dashboard/javascripts/dashboardObject.js b/plugins/Dashboard/javascripts/dashboardObject.js index 8863317c9c..b8f3dbb19d 100644 --- a/plugins/Dashboard/javascripts/dashboardObject.js +++ b/plugins/Dashboard/javascripts/dashboardObject.js @@ -434,7 +434,7 @@ function buildMenu() { var success = function (dashboards) { - var dashboardMenuList = $('#Dashboard > ul'); + var dashboardMenuList = $('#Dashboard').find('> ul'); dashboardMenuList.empty(); if (dashboards.length > 1) { dashboardMenuList.show(); diff --git a/plugins/DevicesDetection/API.php b/plugins/DevicesDetection/API.php new file mode 100644 index 0000000000..e3e7100328 --- /dev/null +++ b/plugins/DevicesDetection/API.php @@ -0,0 +1,161 @@ +<?php + +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_DevicesDetection + */ +class Piwik_DevicesDetection_API +{ + + static private $instance = null; + + /** + * + * @return Piwik_DevicesDetection_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * @param string $name + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + protected function getDataTable($name, $idSite, $period, $date, $segment) + { + Piwik::checkUserHasViewAccess($idSite); + $archive = Piwik_Archive::build($idSite, $period, $date, $segment); + $dataTable = $archive->getDataTable($name); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS)); + $dataTable->queueFilter('ReplaceColumnNames'); + $dataTable->queueFilter('ReplaceSummaryRowLabel'); + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by device type (eg. desktop, smartphone, tablet) + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getType($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_types', $idSite, $period, $date, $segment); + $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getDeviceTypeLabel')); + $dataTable->filter('ColumnCallbackReplace', array('label', 'ucfirst')); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getDeviceTypeLogo')); + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by device manufacturer name + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getBrand($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_brands', $idSite, $period, $date, $segment); + $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getDeviceBrandLabel')); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_GetBrandLogo')); + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by device model + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getModel($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_models', $idSite, $period, $date, $segment); + $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getModelName')); + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by OS family (eg. Windows, Android, Linux) + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getOsFamilies($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_os', $idSite, $period, $date, $segment); + $dataTable->filter('GroupBy', array('label', 'Piwik_getOSFamilyFullNameExtended')); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getOsFamilyLogoExtended')); + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by OS version (eg. Android 4.0, Windows 7) + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getOsVersions($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_osVersions', $idSite, $period, $date, $segment); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getOsLogoExtended')); + $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getOsFullNameExtended')); + + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by Browser family (eg. Firefox, InternetExplorer) + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getBrowserFamilies($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_browsers', $idSite, $period, $date, $segment); + $dataTable->filter('GroupBy', array('label', 'Piwik_getBrowserFamilyFullNameExtended')); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowserFamilyLogoExtended')); + return $dataTable; + } + + /** + * Gets datatable displaying number of visits by Browser version (eg. Firefox 20, Safari 6.0) + * @param int $idSite + * @param string $period + * @param string $date + * @param string $segment + * @return DataTable + */ + public function getBrowserVersions($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getDataTable('DevicesDetection_browserVersions', $idSite, $period, $date, $segment); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowserLogoExtended')); + $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserNameExtended')); + return $dataTable; + } + +}
\ No newline at end of file diff --git a/plugins/DevicesDetection/Archiver.php b/plugins/DevicesDetection/Archiver.php new file mode 100644 index 0000000000..5b8114e412 --- /dev/null +++ b/plugins/DevicesDetection/Archiver.php @@ -0,0 +1,64 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_DevicesDetection + */ + +class Piwik_DevicesDetection_Archiver extends Piwik_PluginsArchiver +{ + const DEVICE_TYPE_RECORD_NAME = 'DevicesDetection_types'; + const DEVICE_BRAND_RECORD_NAME = 'DevicesDetection_brands'; + const DEVICE_MODEL_RECORD_NAME = 'DevicesDetection_models'; + const OS_RECORD_NAME = 'DevicesDetection_os'; + const OS_VERSION_RECORD_NAME = 'DevicesDetection_osVersions'; + const BROWSER_RECORD_NAME = 'DevicesDetection_browsers'; + const BROWSER_VERSION_RECORD_NAME = 'DevicesDetection_browserVersions'; + + const DEVICE_TYPE_FIELD = "config_device_type"; + const DEVICE_BRAND_FIELD = "config_device_brand"; + const DEVICE_MODEL_FIELD = "config_device_model"; + const OS_FIELD = "config_os"; + const OS_VERSION_FIELD = "CONCAT(log_visit.config_os, ';', log_visit.config_os_version)"; + const BROWSER_FIELD = "config_browser_name"; + const BROWSER_VERSION_DIMENSION = "CONCAT(log_visit.config_browser_name, ';', log_visit.config_browser_version)"; + + public function archiveDay() + { + $this->aggregateByLabel( self::DEVICE_TYPE_FIELD, self::DEVICE_TYPE_RECORD_NAME); + $this->aggregateByLabel( self::DEVICE_BRAND_FIELD, self::DEVICE_BRAND_RECORD_NAME); + $this->aggregateByLabel( self::DEVICE_MODEL_FIELD, self::DEVICE_MODEL_RECORD_NAME); + $this->aggregateByLabel( self::OS_FIELD, self::OS_RECORD_NAME); + $this->aggregateByLabel( self::OS_VERSION_FIELD, self::OS_VERSION_RECORD_NAME); + $this->aggregateByLabel( self::BROWSER_FIELD, self::BROWSER_RECORD_NAME); + $this->aggregateByLabel( self::BROWSER_VERSION_DIMENSION, self::BROWSER_VERSION_RECORD_NAME); + } + + private function aggregateByLabel( $labelSQL, $recordName) + { + $metrics = $this->getProcessor()->getMetricsForDimension($labelSQL); + $table = $this->getProcessor()->getDataTableFromDataArray($metrics); + $this->getProcessor()->insertBlobRecord($recordName, $table->getSerialized($this->maximumRows, null, Piwik_Metrics::INDEX_NB_VISITS)); + } + + public function archivePeriod() + { + $dataTablesToSum = array( + self::DEVICE_TYPE_RECORD_NAME, + self::DEVICE_BRAND_RECORD_NAME, + self::DEVICE_MODEL_RECORD_NAME, + self::OS_RECORD_NAME, + self::OS_VERSION_RECORD_NAME, + self::BROWSER_RECORD_NAME, + self::BROWSER_VERSION_RECORD_NAME + ); + foreach ($dataTablesToSum as $dt) { + $this->getProcessor()->aggregateDataTableReports( + $dt, $this->maximumRows, $this->maximumRows, $columnToSort = "nb_visits"); + } + } +}
\ No newline at end of file diff --git a/plugins/DevicesDetection/Controller.php b/plugins/DevicesDetection/Controller.php new file mode 100644 index 0000000000..22681f1a6c --- /dev/null +++ b/plugins/DevicesDetection/Controller.php @@ -0,0 +1,177 @@ +<?php + +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_DevicesDetection + */ +class Piwik_DevicesDetection_Controller extends Piwik_Controller +{ + + /** The set of related reports displayed under the 'Operating Systems' header. */ + private $osRelatedReports = null; + private $browserRelatedReports = null; + + public function __construct() + { + parent::__construct(); + $this->osRelatedReports = array( + 'DevicesDetection.getOsFamilies' => Piwik_Translate('DeviceDetection_OperatingSystemFamilies'), + 'DevicesDetection.getOsVersions' => Piwik_Translate('DeviceDetection_OperatingSystemVersions') + ); + $this->browserRelatedReports = array( + 'DevicesDetection.getBrowserFamilies' => Piwik_Translate('DevicesDetection_BrowsersFamily'), + 'DevicesDetection.getBrowserVersions' => Piwik_Translate('DevicesDetection_BrowserVersions') + ); + } + + public function index($fetch = false) + { + $view = Piwik_View::factory('index'); + $view->deviceTypes = $view->deviceModels = $view->deviceBrands = $view->osReport = $view->browserReport = "blank"; + $view->deviceTypes = $this->getType(true); + $view->deviceBrands = $this->getBrand(true); + $view->deviceModels = $this->getModel(true); + $view->osReport = $this->getOsFamilies(true); + $view->browserReport = $this->getBrowserFamilies(true); + echo $view->render(); + } + + public function getType($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getType' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelTypes")); + return $this->renderView($view, $fetch); + } + + public function getBrand($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getBrand' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelBrands")); + return $this->renderView($view, $fetch); + } + + public function getModel($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getModel' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelModels")); + + return $this->renderView($view, $fetch); + } + + public function getOsFamilies($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getOsFamilies' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelSystemFamily")); + $view->addRelatedReports(Piwik_Translate('DeviceDetection_OperatingSystemFamilies'), $this->osRelatedReports); + return $this->renderView($view, $fetch); + } + + public function getOsVersions($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getOsVersions' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelSystemVersion")); + $view->addRelatedReports(Piwik_Translate('DeviceDetection_OperatingSystemVersions'), $this->osRelatedReports); + return $this->renderView($view, $fetch); + } + + public function getBrowserFamilies($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getBrowserFamilies' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelBrowserFamily")); + $view->addRelatedReports(Piwik_Translate('DevicesDetection_BrowsersFamily'), $this->browserRelatedReports); + return $this->renderView($view, $fetch); + } + + public function getBrowserVersions($fetch = false) + { + $view = $this->getStandardDataTableUserSettings( + __FUNCTION__, 'DevicesDetection.getBrowserVersions' + ); + + $view->setColumnTranslation('label', Piwik_Translate("DevicesDetection_dataTableLabelBrowserVersion")); + $view->addRelatedReports(Piwik_Translate('DevicesDetection_BrowserVersions'), $this->browserRelatedReports); + return $this->renderView($view, $fetch); + } + + protected function getStandardDataTableUserSettings($currentControllerAction, $APItoCall, $defaultDatatableType = null) + { + $view = Piwik_ViewDataTable::factory($defaultDatatableType); + $view->init($this->pluginName, $currentControllerAction, $APItoCall); + $view->disableSearchBox(); + $view->disableExcludeLowPopulation(); + $this->setPeriodVariablesView($view); + $this->setMetricsVariablesView($view); + return $view; + } + + /** + * You may manually call this controller action to force re-processing of past user agents + */ + public function refreshParsedUserAgents() + { + $q = "SELECT idvisit, config_debug_ua FROM " . Piwik_Common::prefixTable("log_visit"); + $res = Piwik_FetchAll($q); + foreach ($res as $rec) { + $UAParser = new UserAgentParserEnhanced($rec['config_debug_ua']); + $UAParser->parse(); + echo "Processing idvisit = " . $rec['idvisit'] . "<br/>"; + echo "UserAgent string: " . $rec['config_debug_ua'] . "<br/> Decoded values:"; + $uaDetails = $this->getArray($UAParser); + var_export($uaDetails); + echo "<hr/>"; + $this->updateVisit($rec['idvisit'], $uaDetails); + unset($UAParser); + } + echo "Please remember to truncate your archives !"; + } + + private function getArray(UserAgentParserEnhanced $UAParser) + { + $UADetails['config_browser_name'] = $UAParser->getBrowser("short_name"); + $UADetails['config_browser_version'] = $UAParser->getBrowser("version"); + $UADetails['config_os'] = $UAParser->getOs("short_name"); + $UADetails['config_os_version'] = $UAParser->getOs("version"); + $UADetails['config_device_type'] = $UAParser->getDevice(); + $UADetails['config_device_model'] = $UAParser->getModel(); + $UADetails['config_device_brand'] = $UAParser->getBrand(); + return $UADetails; + } + + private function updateVisit($idVisit, $uaDetails) + { + $q = "UPDATE " . Piwik_Common::prefixTable("log_visit") . " SET " . + "config_browser_name = '" . $uaDetails['config_browser_name'] . "' ," . + "config_browser_version = '" . $uaDetails['config_browser_version'] . "' ," . + "config_os = '" . $uaDetails['config_os'] . "' ," . + "config_os_version = '" . $uaDetails['config_os_version'] . "' ," . + "config_device_type = " . (isset($uaDetails['config_device_type']) ? "'" . $uaDetails['config_device_type'] . "'" : "NULL") . " ," . + "config_device_model = " . (isset($uaDetails['config_device_model']) ? "'" . $uaDetails['config_device_model'] . "'" : "NULL") . " ," . + "config_device_brand = " . (isset($uaDetails['config_device_brand']) ? "'" . $uaDetails['config_device_brand'] . "'" : "NULL") . " + WHERE idvisit = " . $idVisit; + Piwik_Query($q); + } + +}
\ No newline at end of file diff --git a/plugins/DevicesDetection/DevicesDetection.php b/plugins/DevicesDetection/DevicesDetection.php new file mode 100644 index 0000000000..30c55ffe37 --- /dev/null +++ b/plugins/DevicesDetection/DevicesDetection.php @@ -0,0 +1,264 @@ +<?php + +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_DevicesDetection + */ +require_once PIWIK_INCLUDE_PATH . "/plugins/DevicesDetection/UserAgentParserEnhanced/UserAgentParserEnhanced.php"; +require_once PIWIK_INCLUDE_PATH . '/plugins/DevicesDetection/functions.php'; + +class Piwik_DevicesDetection extends Piwik_Plugin +{ + /** + * Return information about this plugin. + * @return array + */ + public function getInformation() + { + return array( + 'description' => "[Beta Plugin] " . Piwik_Translate("DevicesDetection_description"), + 'author' => 'Piwik and Clearcode.cc', + 'author_homepage' => 'http://clearcode.cc', + 'version' => '1.12-b6', + 'TrackerPlugin' => true, + 'translationAvailable' => true, + ); + } + + /* + * Defines API reports. + * Also used to define Widgets, and Segment(s) + * + * @return array Category, Report Name, API Module, API action, Translated column name, & optional segment info + * + */ + protected function getRawMetadataReports() + { + $report = array( + array( + 'DevicesDetection_DevicesDetection', + 'DevicesDetection_DeviceType', + 'DevicesDetection', + 'getType', + 'DevicesDetection_DeviceType', + + // Segment + 'deviceType', + 'log_visit.config_device_type', + implode(", ", UserAgentParserEnhanced::$deviceTypes), // comma separated examples + create_function('$type', 'return array_search( strtolower(trim(urldecode($type))), UserAgentParserEnhanced::$deviceTypes);') + ), + // device brands report + array( + 'DevicesDetection_DevicesDetection', + 'DevicesDetection_DeviceBrand', + 'DevicesDetection', + 'getBrand', + 'DevicesDetection_DeviceBrand', + ), + // device model report + array( + 'DevicesDetection_DevicesDetection', + 'DevicesDetection_DeviceModel', + 'DevicesDetection', + 'getModel', + 'DevicesDetection_DeviceModel', + ), + // device OS family report + array( + 'DevicesDetection_DevicesDetection', + 'DeviceDetection_OperatingSystemFamilies', + 'DevicesDetection', + 'getOsFamilies', + 'DeviceDetection_OperatingSystemFamilies', + ), + // device OS version report + array( + 'DevicesDetection_DevicesDetection', + 'DeviceDetection_OperatingSystemVersions', + 'DevicesDetection', + 'getOsVersions', + 'DeviceDetection_OperatingSystemVersions', + ), + // Browser family report + array( + 'DevicesDetection_DevicesDetection', + 'DevicesDetection_BrowsersFamily', + 'DevicesDetection', + 'getBrowserFamilies', + 'DevicesDetection_BrowsersFamily', + ), + // Browser versions report + array( + 'DevicesDetection_DevicesDetection', + 'DevicesDetection_BrowserVersions', + 'DevicesDetection', + 'getBrowserVersions', + 'DevicesDetection_BrowserVersions', + ), + ); + return $report; + } + + public function getListHooksRegistered() + { + return array( + 'ArchiveProcessing_Day.compute' => "archiveDay", + 'ArchiveProcessing_Period.compute' => 'archivePeriod', + 'Menu.add' => 'addMenu', + 'Tracker.newVisitorInformation' => 'parseMobileVisitData', + 'WidgetsList.add' => 'addWidgets', + 'API.getReportMetadata' => 'getReportMetadata', + 'API.getSegmentsMetadata' => 'getSegmentsMetadata', + ); + } + + public function addWidgets() + { + foreach ($this->getRawMetadataReports() as $report) { + list($category, $name, $controllerName, $controllerAction) = $report; + if ($category == false) + continue; + Piwik_AddWidget($category, $name, $controllerName, $controllerAction); + } + } + + + /** + * Get segments meta data + * + * @param Piwik_Event_Notification $notification notification object + */ + public function getSegmentsMetadata($notification) + { + // Note: only one field segmented so far: deviceType + $segments =& $notification->getNotificationObject(); + foreach ($this->getRawMetadataReports() as $report) { + @list($category, $name, $apiModule, $apiAction, $columnName, $segment, $sqlSegment, $acceptedValues) = $report; + + if (empty($segment)) continue; + $segments[] = array( + 'type' => 'dimension', + 'category' => Piwik_Translate('General_Visit'), + 'name' => $columnName, + 'segment' => $segment, + 'acceptedValues' => $acceptedValues, + 'sqlSegment' => $sqlSegment, + 'sqlFilter' => isset($sqlFilter) ? $sqlFilter : false, + ); + } + } + + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getReportMetadata($notification) + { + $reports = & $notification->getNotificationObject(); + + $i = 0; + foreach ($this->getRawMetadataReports() as $report) { + list($category, $name, $apiModule, $apiAction, $columnName) = $report; + if ($category == false) + continue; + + $report = array( + 'category' => Piwik_Translate($category), + 'name' => Piwik_Translate($name), + 'module' => $apiModule, + 'action' => $apiAction, + 'dimension' => Piwik_Translate($columnName), + 'order' => $i++ + ); + + $translation = $name . 'Documentation'; + $translated = Piwik_Translate($translation, '<br />'); + if ($translated != $translation) { + $report['documentation'] = $translated; + } + + + $reports[] = $report; + } + } + + public function install() + { +// we catch the exception + try { + $q1 = "ALTER TABLE `" . Piwik_Common::prefixTable("log_visit") . "` + ADD `config_os_version` VARCHAR( 10 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL AFTER `config_os` , + ADD `config_device_type` TINYINT( 10 ) NULL DEFAULT NULL AFTER `config_browser_version` , + ADD `config_device_brand` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL AFTER `config_device_type` , + ADD `config_device_model` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL AFTER `config_device_brand`"; + Piwik_Exec($q1); + // conditionaly add this column + if (@Piwik_Config::getInstance()->Debug['store_user_agent_in_visit']) { + $q2 = "ALTER TABLE `" . Piwik_Common::prefixTable("log_visit") . "` + ADD `config_debug_ua` VARCHAR( 512 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL AFTER `config_device_model`"; + Piwik_Exec($q2); + } + } catch (Exception $e) { + if (!Zend_Registry::get('db')->isErrNo($e, '1060')) { + throw $e; + } + } + } + + public function parseMobileVisitData($notification) + { + $visitorInfo = &$notification->getNotificationObject(); + + $extraInfo = $notification->getNotificationInfo(); + $userAgent = $extraInfo['UserAgent']; + + $UAParser = new UserAgentParserEnhanced($userAgent); + $UAParser->parse(); + $deviceInfo['config_browser_name'] = $UAParser->getBrowser("short_name"); + $deviceInfo['config_browser_version'] = $UAParser->getBrowser("version"); + $deviceInfo['config_os'] = $UAParser->getOs("short_name"); + $deviceInfo['config_os_version'] = $UAParser->getOs("version"); + $deviceInfo['config_device_type'] = $UAParser->getDevice(); + $deviceInfo['config_device_model'] = $UAParser->getModel(); + $deviceInfo['config_device_brand'] = $UAParser->getBrand(); + + if (@Piwik_Config::getInstance()->Debug['store_user_agent_in_visit']) { + $deviceInfo['config_debug_ua'] = $userAgent; + } + + $visitorInfo = array_merge($visitorInfo, $deviceInfo); + printDebug("Device Detection:"); + printDebug($deviceInfo); + } + + public function archiveDay($notification) + { + $archiveProcessor = $notification->getNotificationObject(); + + $archiving = new Piwik_DevicesDetection_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); + } + } + + public function archivePeriod($notification) + { + $archiveProcessor = $notification->getNotificationObject(); + $archiving = new Piwik_DevicesDetection_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); + } + } + + public function addMenu() + { + Piwik_AddMenu('General_Visitors', 'DevicesDetection_submenu', array('module' => 'DevicesDetection', 'action' => 'index')); + } + +}
\ No newline at end of file diff --git a/plugins/DevicesDetection/UserAgentParserEnhanced/UserAgentParserEnhanced.php b/plugins/DevicesDetection/UserAgentParserEnhanced/UserAgentParserEnhanced.php new file mode 100644 index 0000000000..911c751221 --- /dev/null +++ b/plugins/DevicesDetection/UserAgentParserEnhanced/UserAgentParserEnhanced.php @@ -0,0 +1,735 @@ +<?php + +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_DevicesDetection + */ +//yml parser +require_once(PIWIK_INCLUDE_PATH.'/libs/spyc.php'); + +class UserAgentParserEnhanced +{ + public static $deviceTypes = array( 'desktop', 'smartphone', 'tablet', 'feature phone', 'console', 'tv', 'car browser' ); + + public static $deviceBrands = array( + 'AC' => 'Acer', + 'AI' => 'Airness', + 'AL' => 'Alcatel', + 'AO' => 'Amoi', + 'AP' => 'Apple', + 'AU' => 'Asus', + 'AV' => 'Avvio', + 'AX' => 'Audiovox', + 'BE' => 'Becker', + 'BI' => 'Bird', + 'BL' => 'Beetel', + 'BQ' => 'BenQ', + 'BS' => 'BenQ-Siemens', + 'CK' => 'Cricket', + 'CL' => 'Compal', + 'CT' => 'Capitel', + 'DB' => 'Dbtel', + 'DC' => 'DoCoMo', + 'DI' => 'Dicam', + 'DL' => 'Dell', + 'DP' => 'Dopod', + 'EC' => 'Ericsson', + 'EI' => 'Ezio', + 'ER' => 'Ericy', + 'ET' => 'eTouch', + 'EZ' => 'Ezze', + 'FL' => 'Fly', + 'GI' => 'Gionee', + 'GO' => 'Google', + 'GR' => 'Gradiente', + 'GU' => 'Grundig', + 'HA' => 'Haier', + 'HP' => 'HP', + 'HT' => 'HTC', + 'HU' => 'Huawei', + 'IK' => 'iKoMo', + 'IM' => 'i-mate', + 'IN' => 'Innostream', + 'IO' => 'i-mobile', + 'IQ' => 'INQ', + 'KA' => 'Karbonn', + 'KD' => 'KDDI', + 'KN' => 'Kindle', + 'KO' => 'Konka', + 'KY' => 'Kyocera', + 'LA' => 'Lanix', + 'LC' => 'LCT', + 'LE' => 'Lenovo', + 'LG' => 'LG', + 'LU' => 'LGUPlus', + 'MI' => 'MicroMax', + 'MO' => 'Mio', + 'MR' => 'Motorola', + 'MS' => 'Microsoft', + 'MT' => 'Mitsubishi', + 'MY' => 'MyPhone', + 'NE' => 'NEC', + 'NG' => 'NGM', + 'NI' => 'Nintendo', + 'NK' => 'Nokia', + 'NW' => 'Newgen', + 'NX' => 'Nexian', + 'OD' => 'Onda', + 'OP' => 'OPPO', + 'OR' => 'Orange', + 'OT' => 'O2', + 'PA' => 'Panasonic', + 'PH' => 'Philips', + 'PM' => 'Palm', + 'PO' => 'phoneOne', + 'PT' => 'Pantech', + 'QT' => 'Qtek', + 'RM' => 'RIM', + 'RO' => 'Rover', + 'SA' => 'Samsung', + 'SD' => 'Sega', + 'SE' => 'Sony Ericsson', + 'SF' => 'Softbank', + 'SG' => 'Sagem', + 'SH' => 'Sharp', + 'SI' => 'Siemens', + 'SN' => 'Sendo', + 'SO' => 'Sony', + 'SP' => 'Spice', + 'SY' => 'Sanyo', + 'TA' => 'Tesla', + 'TC' => 'TCL', + 'TE' => 'Telit', + 'TH' => 'TiPhone', + 'TI' => 'TIANYU', + 'TM' => 'T-Mobile', + 'TO' => 'Toplux', + 'TS' => 'Toshiba', + 'UT' => 'UTStarcom', + 'VD' => 'Videocon', + 'VE' => 'Vertu', + 'VI' => 'Vitelcom', + 'VK' => 'VK Mobile', + 'VO' => 'Voxtel', + 'WB' => 'Web TV', + 'WE' => 'WellcoM', + 'WO' => 'Wonu', + 'XX' => 'Unknown', + 'ZO' => 'Zonda', + 'ZT' => 'ZTE', + ); + public static $osShorts = array( + 'AIX' => 'AIX', + 'Android' => 'AND', + 'Apple TV' => 'ATV', + 'Arch Linux' => 'ARL', + 'BackTrack' => 'BTR', + 'Bada' => 'SBA', + 'BlackBerry OS' => 'BLB', + 'BlackBerry Tablet OS' => 'QNX', + 'Bot' => 'BOT', + 'Brew' => 'BMP', + 'CentOS' => 'CES', + 'Chrome OS' => 'COS', + 'Debian' => 'DEB', + 'DragonFly' => 'DFB', + 'Fedora' => 'FED', + 'Firefox OS' => 'FOS', + 'FreeBSD' => 'BSD', + 'Gentoo' => 'GNT', + 'Google TV' => 'GTV', + 'HP-UX' => 'HPX', + 'IRIX' => 'IRI', + 'Knoppix' => 'KNO', + 'Kubuntu' => 'KBT', + 'Linux' => 'LIN', + 'Lubuntu' => 'LBT', + 'Mac' => 'MAC', + 'Mandriva' => 'MDR', + 'MeeGo' => 'SMG', + 'Mint' => 'MIN', + 'NetBSD' => 'NBS', + 'Nintendo' => 'WII', + 'Nintendo Mobile' => 'NDS', + 'OS/2' => 'OS2', + 'OSF1' => 'T64', + 'OpenBSD' => 'OBS', + 'PlayStation' => 'PSP', + 'PlayStation 3' => 'PS3', + 'Presto' => 'PRS', + 'Puppy' => 'PPY', + 'Red Hat' => 'RHT', + 'SUSE' => 'SSE', + 'Slackware' => 'SLW', + 'Solaris' => 'SOS', + 'Syllable' => 'SYL', + 'Symbian' => 'SYM', + 'Symbian OS' => 'SYS', + 'Symbian OS Series 40' => 'S40', + 'Symbian OS Series 60' => 'S60', + 'Symbian^3' => 'SY3', + 'Talkatone' => 'TKT', + 'Tizen' => 'TIZ', + 'Ubuntu' => 'UBT', + 'WebTV' => 'WTV', + 'WinWAP' => 'WWP', + 'Windows' => 'WIN', + 'Windows 2000' => 'W2K', + 'Windows 3.1' => 'W31', + 'Windows 7' => 'WI7', + 'Windows 8' => 'WI8', + 'Windows 95' => 'W95', + 'Windows 98' => 'W98', + 'Windows CE' => 'WCE', + 'Windows ME' => 'WME', + 'Windows Mobile' => 'WMO', + 'Windows NT' => 'WNT', + 'Windows Phone' => 'WPH', + 'Windows RT' => 'WRT', + 'Windows Server 2003' => 'WS3', + 'Windows Vista' => 'WVI', + 'Windows XP' => 'WXP', + 'Xbox' => 'XBX', + 'Xubuntu' => 'XBT', + 'YunOs' => 'YNS', + 'iOS' => 'IOS', + 'palmOS' => 'POS', + 'webOS' => 'WOS' + ); + protected static $desktopOsArray = array('IBM', 'Linux', 'Mac', 'Unix', 'Windows'); + public static $osFamilies = array( + 'Android' => array('AND'), + 'Apple TV' => array('ATV'), + 'BlackBerry' => array('BLB'), + 'Bot' => array('BOT'), + 'Brew' => array('BMP'), + 'Chrome OS' => array('COS'), + 'Firefox OS' => array('FOS'), + 'Gaming Console' => array('WII', 'PS3'), + 'Google TV' => array('GTV'), + 'IBM' => array('OS2'), + 'iOS' => array('IOS'), + 'Linux' => array('LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'MDR', 'GNT', 'SLW', 'SSE', 'PPY', 'CES', 'BTR', 'YNS', 'PRS'), + 'Mac' => array('MAC'), + 'Mobile Gaming Console' => array('PSP', 'NDS', 'XBX'), + 'Other Mobile' => array('WOS', 'POS', 'QNX', 'SBA', 'TIZ'), + 'Simulator' => array('TKT', 'WWP'), + 'Symbian' => array('SYM', 'SYS', 'SY3', 'S60', 'S40', 'SMG'), + 'Unix' => array('SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64'), + 'WebTV' => array('WTV'), + 'Windows' => array('WI8', 'WI7', 'WVI', 'WS3', 'WXP', 'W2K', 'WNT', 'WME', 'W98', 'W95', 'WRT', 'W31', 'WIN'), + 'Windows Mobile' => array('WPH', 'WMO', 'WCE') + ); + public static $browserFamilies = array( + 'Android Browser' => array('AN'), + 'BlackBerry Browser' => array('BB'), + 'Chrome' => array('CH', 'CM', 'CI', 'CF', 'CR', 'RM'), + 'Firefox' => array('FF', 'FE', 'SX', 'FB', 'PX', 'MB'), + 'Internet Explorer' => array('IE', 'IM'), + 'Konqueror' => array('KO'), + 'NetFront' => array('NF'), + 'Nokia Browser' => array('NB'), + 'Opera' => array('OP', 'OM', 'OI'), + 'Safari' => array('SF', 'MF') + ); + public static $browsers = array( + 'AB' => 'ABrowse', + 'AM' => 'Amaya', + 'AN' => 'Android Browser', + 'AR' => 'Arora', + 'AV' => 'Amiga Voyager', + 'AW' => 'Amiga Aweb', + 'BB' => 'BlackBerry Browser', + 'BD' => 'Baidu Browser', + 'BE' => 'Beonex', + 'BX' => 'BrowseX', + 'CA' => 'Camino', + 'CF' => 'Chrome Frame', + 'CH' => 'Chrome', + 'CI' => 'Chrome Mobile iOS', + 'CK' => 'Conkeror', + 'CM' => 'Chrome Mobile', + 'CO' => 'CometBird', + 'CR' => 'Chromium', + 'CS' => 'Cheshire', + 'DF' => 'Dolphin', + 'DI' => 'Dillo', + 'EL' => 'Elinks', + 'EP' => 'Epiphany', + 'FB' => 'Firebird', + 'FD' => 'Fluid', + 'FE' => 'Fennec', + 'FF' => 'Firefox', + 'FL' => 'Flock', + 'FN' => 'Fireweb Navigator', + 'GA' => 'Galeon', + 'GE' => 'Google Earth', + 'HJ' => 'HotJava', + 'IB' => 'IBrowse', + 'IC' => 'iCab', + 'IE' => 'Internet Explorer', + 'IM' => 'IE Mobile', + 'IR' => 'Iron', + 'JS' => 'Jasmine', + 'KI' => 'Kindle Browser', + 'KM' => 'K-meleon', + 'KO' => 'Konqueror', + 'KP' => 'Kapiko', + 'KZ' => 'Kazehakase', + 'LG' => 'Lightning', + 'LI' => 'Links', + 'LX' => 'Lynx', + 'MB' => 'MicroB', + 'MC' => 'NCSA Mosaic', + 'MF' => 'Mobile Safari', + 'MI' => 'Midori', + 'MS' => 'Mobile Silk', + 'MX' => 'Maxthon', + 'NB' => 'Nokia Browser', + 'NF' => 'NetFront', + 'NL' => 'NetFront Life', + 'NS' => 'Netscape', + 'OB' => 'Obigo', + 'OI' => 'Opera Mini', + 'OM' => 'Opera Mobile', + 'OP' => 'Opera', + 'OV' => 'Openwave Mobile Browser', + 'OW' => 'OmniWeb', + 'PL' => 'Palm Blazer', + 'PR' => 'Palm Pre', + 'PX' => 'Phoenix', + 'RK' => 'Rekonq', + 'RM' => 'RockMelt', + 'SF' => 'Safari', + 'SM' => 'SeaMonkey', + 'SN' => 'Snowshoe', + 'SX' => 'Swiftfox', + 'TZ' => 'Tizen Browser', + 'UC' => 'UC Browser', + 'WO' => 'wOSBrowser', + 'YA' => 'Yandex Browser' + ); + + const UNKNOWN = "UNK"; + protected static $regexesDir = '/regexes/'; + protected static $osRegexesFile = 'oss.yml'; + protected static $browserRegexesFile = 'browsers.yml'; + protected static $mobileRegexesFile = 'mobiles.yml'; + protected $userAgent; + protected $os; + protected $browser; + protected $device; + protected $brand; + protected $model; + protected $debug = false; + + public function __construct($userAgent) + { + $this->userAgent = $userAgent; + } + + protected function getOsRegexes() + { + return Spyc::YAMLLoad(dirname(__FILE__) . self::$regexesDir . self::$osRegexesFile); + } + + protected function getBrowserRegexes() + { + return Spyc::YAMLLoad(dirname(__FILE__) . self::$regexesDir . self::$browserRegexesFile); + } + + protected function getMobileRegexes() + { + return Spyc::YAMLLoad(dirname(__FILE__) . self::$regexesDir . self::$mobileRegexesFile); + } + + public function parse() + { + $this->parseOs(); + if ($this->isBot() || $this->isSimulator()) + return; + + $this->parseBrowser(); + + if ($this->isMobile()) { + $this->parseMobile(); + } else { + $this->device = array_search('desktop', self::$deviceTypes); + } + if ($this->debug) { + var_export($this->brand, $this->model, $this->device); + } + } + + protected function parseOs() + { + foreach ($this->getOsRegexes() as $osRegex) { + $matches = $this->matchUserAgent($osRegex['regex']); + if ($matches) + break; + } + + if (!$matches) + return; + + if (in_array($osRegex['name'], self::$osShorts)) { + $short = self::$osShorts[$osRegex['name']]; + } else { + $short = 'UNK'; + } + + $this->os = array( + 'name' => $this->buildOsName($osRegex['name'], $matches), + 'short_name' => $short, + 'version' => $this->buildOsVersion($osRegex['version'], $matches) + ); + + if (array_key_exists($this->os['name'], self::$osShorts)) { + $this->os['short_name'] = self::$osShorts[$this->os['name']]; + } + } + + protected function parseBrowser() + { + foreach ($this->getBrowserRegexes() as $browserRegex) { + $matches = $this->matchUserAgent($browserRegex['regex']); + if ($matches) + break; + } + + if (!$matches) + return; + + if (in_array($browserRegex['name'], self::$browsers)) { + $short = array_search($browserRegex['name'], self::$browsers); + } else { + $short = 'XX'; + } + + $this->browser = array( + 'name' => $this->buildBrowserName($browserRegex['name'], $matches), + 'short_name' => $short, + 'version' => $this->buildBrowserVersion($browserRegex['version'], $matches) + ); + } + + protected function parseMobile() + { + $mobileRegexes = $this->getMobileRegexes(); + $this->parseBrand($mobileRegexes); + $this->parseModel($mobileRegexes); + } + + protected function parseBrand($mobileRegexes) + { + foreach ($mobileRegexes as $brand => $mobileRegex) { + $matches = $this->matchUserAgent($mobileRegex['regex']); + if ($matches) + break; + } + + if (!$matches) + return; + $this->brand = array_search($brand, self::$deviceBrands); + $this->fullName = $brand; + + if (isset($mobileRegex['device'])) { + $this->device = array_search($mobileRegex['device'],self::$deviceTypes); + } + + if (isset($mobileRegex['model'])) { + $this->model = $this->buildModel($mobileRegex['model'], $matches); + } + } + + protected function parseModel($mobileRegexes) + { + if (empty($this->brand) || !empty($this->model)) + return; + + foreach ($mobileRegexes[$this->fullName]['models'] as $modelRegex) { + $matches = $this->matchUserAgent($modelRegex['regex']); + if ($matches) + break; + } + + if (!$matches) { + return; + } + + $this->model = $this->buildModel($modelRegex['model'], $matches); + + if (isset($modelRegex['device'])) { + $this->device = array_search($modelRegex['device'], self::$deviceTypes); + } + } + + protected function matchUserAgent($regex) + { + $regex = '/' . str_replace('/', '\/', $regex) . '/i'; + + if (preg_match($regex, $this->userAgent, $matches)) { + return $matches; + } + + return false; + } + + protected function buildOsName($osName, $matches) + { + return $this->buildByMatch($osName, $matches); + } + + protected function buildOsVersion($osVersion, $matches) + { + $osVersion = $this->buildByMatch($osVersion, $matches); + + $osVersion = $this->buildByMatch($osVersion, $matches, '2'); + + $osVersion = str_replace('_', '.', $osVersion); + + return $osVersion; + } + + protected function buildBrowserName($browserName, $matches) + { + return $this->buildByMatch($browserName, $matches); + } + + protected function buildBrowserVersion($browserVersion, $matches) + { + $browserVersion = $this->buildByMatch($browserVersion, $matches); + + $browserVersion = $this->buildByMatch($browserVersion, $matches, '2'); + + $browserVersion = str_replace('_', '.', $browserVersion); + + return $browserVersion; + } + + protected function buildModel($model, $matches) + { + $model = $this->buildByMatch($model, $matches); + + $model = $this->buildByMatch($model, $matches, '2'); + + $model = $this->buildModelExceptions($model); + + $model = str_replace('_', ' ', $model); + + return $model; + } + + protected function buildModelExceptions($model) + { + if ($this->brand == 'O2') { + $model = preg_replace('/([a-z])([A-Z])/', '$1 $2', $model); + $model = ucwords(str_replace('_', ' ', $model)); + } + + return $model; + } + + /** + * This method is used in this class for processing results of pregmatch + * results into string containing recognized information. + * + * General algorithm: + * Parsing UserAgent string consists of trying to match it against list of + * regular expressions for three different information: + * browser + version, + * OS + version, + * device manufacturer + model. + * + * After match has been found iteration stops, and results are processed + * by buildByMatch. + * As $item we get decoded name (name of browser, name of OS, name of manufacturer). + * In array $match we recieve preg_match results containing whole string matched at index 0 + * and following matches in further indexes. Desired action now is to concatenate + * decoded name ($item) with matches found. First step is to append first found match, + * which is located in index=1 (that's why $nb is 1 by default). + * In other cases, where whe know that preg_match may return more than 1 result, + * we call buildByMatch with $nb = 2 or more, depending on what will be returned from + * regular expression. + * + * Example: + * We are parsing UserAgent of Firefox 20.0 browser. + * UserAgentParserEnhanced calls buildBrowserName() and buildBrowserVersion() in order + * to retrieve those information. + * In buildBrowserName() we only have one call of buildByMatch, where passed argument + * is regular expression testing given string for browser name. In this case, we are only + * interrested in first hit, so no $nb parameter will be set to 1. After finding match, and calling + * buildByMatch - we will receive just the name of browser. + * + * Also after decoding browser we will get list of regular expressions for this browser name + * testing UserAgent string for version number. Again we iterate over this list, and after finding first + * occurence - we break loop and proceed to build by match. Since browser regular expressions can + * contain two hits (major version and minor version) in function buildBrowserVersion() we have + * two calls to buildByMatch, one without 3rd parameter, and second with $nb set to 2. + * This way we can retrieve version number, and assign it to object property. + * + * In case of mobiles.yml this schema slightly varies, but general idea is the same. + * + * @param string $item + * @param array $matches + * @param int $nb + * @return type + */ + protected function buildByMatch($item, $matches, $nb = '1') + { + if (strpos($item, '$' . $nb) === false) + return $item; + + $replace = isset($matches[$nb]) ? $matches[$nb] : ''; + return trim(str_replace('$' . $nb, $replace, $item)); + } + + public function isBot() + { + $decodedFamily = ''; + if (in_array($this->getOs('name'), self::$osShorts)) { + $osShort = self::$osShorts[$this->getOs('name')]; + } else { + $osShort = ''; + } + foreach (self::$osFamilies as $family => $familyOs) { + if (in_array($osShort, $familyOs)) { + $decodedFamily = $family; + break; + } + } + + return $decodedFamily == 'Bot'; + } + + public function isSimulator() + { + $decodedFamily = ''; + if (in_array($this->getOs('name'), self::$osShorts)) { + $osShort = self::$osShorts[$this->getOs('name')]; + } else { + $osShort = ''; + } + foreach (self::$osFamilies as $family => $familyOs) { + if (in_array($osShort, $familyOs)) { + $decodedFamily = $family; + break; + } + } + return $decodedFamily == 'Simulator'; + } + + public function isMobile() + { + return !$this->isDesktop(); + } + + public function isDesktop() + { + $osName = $this->getOs('name'); + if (empty($osName) || empty(self::$osShorts[$osName])) { + return false; + } + + $osShort = self::$osShorts[$osName]; + foreach (self::$osFamilies as $family => $familyOs) { + if (in_array($osShort, $familyOs)) { + $decodedFamily = $family; + break; + } + } + return in_array($decodedFamily, self::$desktopOsArray); + } + + public function getOs($attr = '') + { + if ($attr == '') { + return $this->os; + } + + if (!isset($this->os[$attr])) { + return self::UNKNOWN; + } + + if ($attr == 'version') { + $this->os['version'] = $this->os['version']; + } + return $this->os[$attr]; + } + + public function getBrowser($attr = '') + { + if ($attr == '') { + return $this->browser; + } + + if (!isset($this->browser[$attr])) { + return self::UNKNOWN; + } + + return $this->browser[$attr]; + } + + public function getDevice() + { + return $this->device; + } + + public function getBrand() + { + return $this->brand; + } + + public function getModel() + { + return $this->model; + } + + public function getUserAgent() + { + return $this->userAgent; + } + + public static function getOsFamily($osLabel) + { + $osShortName = substr($osLabel, 0, 3); + + foreach (self::$osFamilies as $osFamily => $osShortNames) { + if (in_array($osShortName, $osShortNames)) { + return $osFamily; + } + } + + return 'Other'; + } + + public static function getBrowserFamily($browserLabel) + { + foreach (self::$browserFamilies as $browserFamily => $browserShortNames) { + if (in_array($browserLabel, $browserShortNames)) { + return $browserFamily; + } + } + + return 'Other'; + } + + public static function getOsNameFromId($os, $ver = false) + { + $osFullName = array_search($os, self::$osShorts); + if ($osFullName) { + if (in_array($os, self::$osFamilies['Windows'])) { + return $osFullName; + } else { + return trim($osFullName . " " . $ver); + } + } + return false; + } + +}
\ No newline at end of file diff --git a/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/browsers.yml b/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/browsers.yml new file mode 100644 index 0000000000..0a4d2a6669 --- /dev/null +++ b/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/browsers.yml @@ -0,0 +1,408 @@ +############### +# Piwik - Open source web analytics +# +# @link http://piwik.org +# @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later +# +# @category Piwik_Plugins +# @package Piwik_DevicesDetection +############### + +# SeaMonkey +- regex: '(Iceape|SeaMonkey)/(\d+\.\d+)' + name: $1 + version: '$2' + +# Camino +- regex: 'Camino/(\d+\.\d+)' + name: Camino + version: '$1' + +#Fennec (Firefox for mobile) +- regex: 'Fennec/(\d+\.\d+)' + name: Fennec + version: '$1' + +#MicroB +- regex: 'Firefox.*Tablet browser (\d+\.\d+)' + name: MicroB + version: '$1' + +#Firefox +- regex: 'Firefox/(\d+\.\d+)' + name: Firefox + version: '$1' +- regex: '(BonEcho|GranParadiso|Lorentz|Minefield|Namoroka|Shiretoko)/(\d+\.\d+)' + name: Firefox '$1' + version: '$2' + +#Flock +- regex: 'Flock/(\d+\.\d+)' + name: Flock + version: '$1' + +#RockMelt +- regex: 'RockMelt/(\d+\.\d+)' + name: RockMelt + version: '$1' + +#Netscape +- regex: '(?:Navigator|Netscape6)/(\d+\.\d+)' + name: Netscape + version: '$1' + +#Opera +- regex: '(:?Opera Tablet.*Version|Opera/.+Opera Mobi.+Version|Safari.*OPR)/(\d+\.\d+)' + name: Opera Mobile + version: '$2' +- regex: 'Opera Mini/(:?att/)?(\d+\.\d+)' + name: Opera Mini + version: '$2' +- regex: 'Opera[/ ](?:9.80.*Version/)?(\d+\.\d+)' + name: Opera + version: '$1' + +#wOSBrowser +- regex: '(?:hpw|web)OS/(\d+\.\d+)' + name: wOSBrowser + version: '$1' + +#Swiftfox +- regex: 'Firefox/(\d+\.\d+).*\(Swiftfox\)' + name: Swiftfox + version: '$1' + +#Rekonq +- regex: 'rekonq' + name: Rekonq + version: '' + +#Conkeror +- regex: 'Conkeror/(\d+\.\d+)' + name: Conkeror + version: '$1' + +#Konqueror +- regex: 'Konqueror/(\d+\.\d+)' + name: Konqueror + version: '$1' + +#Baidu Browser +- regex: 'baidubrowser[/ ](\d+)' + name: Baidu Browser + version: '$1' + +#Yandex Browser +- regex: 'YaBrowser/(\d+)' + name: Yandex Browser + version: '$1' + +#Chrome +- regex: 'CrMo/(\d+\.\d+)' + name: Chrome Mobile + version: '$1' +- regex: 'CriOS/(\d+\.\d+)' + name: Chrome Mobile iOS + version: '$1' +- regex: 'Chrome/(\d+\.\d+).*Mobile' + name: Chrome Mobile + version: '$1' +- regex: 'chromeframe/(\d+\.\d+)' + name: Chrome Frame + version: '$1' +- regex: 'Chrome/(\d+\.\d+)' + name: Chrome + version: '$1' +- regex: 'Chromium/(\d+\.\d+)' + name: Chromium + version: '$1' + +#UC Browser +- regex: 'UC[ ]?Browser[ /](\d+\.\d+)' + name: UC Browser + version: '$1' +- regex: '(?:UC Browser|UCBrowser|UCWEB)(\d+\.\d+)' + name: UC Browser + version: '$1' + +#Tizen Browser +- regex: '(?:Tizen|SLP) Browser/(\d+\.\d+)' + name: Tizen Browser + version: '$1' + +#Epiphany +- regex: 'Epiphany/(\d+\.\d+)' + name: Epiphany + version: '$1' + +#Fireweb Navigator +- regex: 'Fireweb Navigator/(\d+\.\d+)' + name: Fireweb Navigator + version: '$1' + +#Jasmine +- regex: 'Jasmine[ /](\d+\.\d+)' + name: Jasmine + version: '$1' + +#Lynx +- regex: 'Lynx/(\d+\.\d+)' + name: Lynx + version: '$1' + +#Midori +- regex: 'Midori/(\d+\.\d+)' + name: Midori + version: '$1' + +#NCSA Mosaic +- regex: 'NCSA_Mosaic/(\d+\.\d+)' + name: NCSA Mosaic + version: '$1' + +#ABrowse +- regex: 'ABrowse (\d+\.\d+)' + name: ABrowse + version: '$1' + +#Amaya +- regex: 'amaya/(\d+\.\d+)' + name: Amaya + version: '$1' + +#Amiga Voyager +- regex: 'AmigaVoyager/(\d+\.\d+)' + name: Amiga Voyager + version: '$1' + +#Amiga Aweb +- regex: 'Amiga-Aweb/(\d+\.\d+)' + name: Amiga Aweb + version: '$1' + +#Arora +- regex: 'Arora/(\d+\.\d+)' + name: Arora + version: '$1' + +#Beonex +- regex: 'Beonex/(\d+\.\d+)' + name: Beonex + version: '$1' + +#BlackBerry Browser +- regex: 'Black[bB]erry|PlayBook|BB10' + name: BlackBerry Browser + version: '' + +#BrowseX +- regex: 'BrowseX \((\d+\.\d+)' + name: BrowseX + version: '$1' + +#Cheshire +- regex: 'Cheshire/(\d+\.\d+)' + name: Cheshire + version: '$1' + +#CometBird +- regex: 'CometBird/(\d+\.\d+)' + name: CometBird + version: '$1' + +#Dillo +- regex: 'Dillo/(\d+\.\d+)' + name: Dillo + version: '$1' + +#Dolphin +- regex: 'Dolfin/(\d+\.\d+)|dolphin' + name: Dolphin + version: '$1' + +#Elinks +- regex: 'Elinks/(\d+\.\d+)' + name: Elinks + version: '$1' + +#Firebird +- regex: 'Firebird/(\d+\.\d+)' + name: Firebird + version: '$1' + +#Fluid +- regex: 'Fluid/(\d+\.\d+)' + name: Fluid + version: '$1' + +#Galeon +- regex: 'Galeon/(\d+\.\d+)' + name: Galeon + version: '$1' + +#Google Earth +- regex: 'Google Earth/(\d+\.\d+)' + name: Google Earth + version: '$1' + +#HotJava +- regex: 'HotJava/(\d+\.\d+)' + name: HotJava + version: '$1' + +#IBrowse +- regex: 'IBrowse[ /](\d+\.\d+)' + name: IBrowse + version: '$1' + +#iCab +- regex: 'iCab[ /](\d+\.\d+)' + name: iCab + version: '$1' + +#Internet Explorer +- regex: 'IEMobile[ /](\d+\.\d+)' + name: IE Mobile + version: '$1' +- regex: 'MSIE (\d+\.\d+).*XBLWP7' + name: IE Mobile + version: '$1' +- regex: 'MSIE (\d+\.\d+)' + name: Internet Explorer + version: '$1' + +#Iron +- regex: 'Iron/(\d+\.\d+)' + name: Iron + version: '$1' + +#Kapiko +- regex: 'Kapiko/(\d+\.\d+)' + name: Kapiko + version: '$1' + +#Kazehakase +- regex: 'Kazehakase/(\d+\.\d+)' + name: Kazehakase + version: '$1' + +#Kindle Browser +- regex: 'Kindle/(\d+\.\d+)' + name: Kindle Browser + version: '$1' + +#K-meleon +- regex: 'K-meleon/(\d+\.\d+)' + name: K-meleon + version: '$1' + +#Lightning +- regex: 'Lightning/(\d+\.\d+)' + name: Lightning + version: '$1' + +#Links +- regex: 'Links \((\d+\.\d+)' + name: Links + version: '$1' + +#Maxthon +- regex: 'Maxthon (\d+\.\d+)' + name: Maxthon + version: '$1' +- regex: '(?:Maxthon|MyIE2|Uzbl|Shiira)' + name: Maxthon + version: '' + +#Openwave Mobile Browser +- regex: 'UP.Browser/(\d+\.\d+)' + name: Openwave Mobile Browser + version: '$1' + +#OmniWeb +- regex: 'OmniWeb/[v]?(\d+\.\d+)' + name: OmniWeb + version: '$1' + +#Phoenix +- regex: 'Phoenix/(\d+\.\d+)' + name: Phoenix + version: '$1' + +#Mobile Silk +- regex: 'Silk/(\d+\.\d+)' + name: Mobile Silk + version: '$1' + +#Nokia Browser +- regex: '(?:NokiaBrowser|BrowserNG)/(\d+\.\d+)' + name: Nokia Browser + version: '$1' +- regex: 'Series60/5\.0' + name: Nokia Browser + version: '7.0' +- regex: 'Series60/(\d+\.\d+)' + name: Nokia OSS Browser + version: '$1' +- regex: 'S40OviBrowser/(\d+\.\d+)' + name: Nokia Ovi Browser + version: '$1' +- regex: '^Nokia|Nokia[EN]?\d+' + name: Nokia Browser + version: '' + +#NetFront +- regex: 'NetFrontLifeBrowser/(\d+\.\d+)' + name: NetFront Life + version: '$1' +- regex: 'NetFront/(\d+\.\d+)' + name: NetFront + version: '$1' +- regex: 'PLAYSTATION|NINTENDO 3|AppleWebKit.+ NX/\d+\.\d+\.\d+' + name: NetFront + version: '' + +#Obigo +- regex: 'Obigo[ ]?(?:InternetBrowser|Browser)?[ /]([A-Za-z0-9]*)' + name: Obigo + version: '$1' +- regex: 'Obigo|Teleca' + name: Obigo + version: '' + +#Palm Blazer +- regex: 'Blazer/(\d+\.\d+)' + name: Palm Blazer + version: '$1' +- regex: 'Pre/(\d+\.\d+)' + name: Palm Pre + version: '$1' + +#Polaris +- regex: '(?:Polaris|Embider)/(\d+\.\d+)' + name: Polaris + version: '$1' + +#Snowshoe +- regex: 'Snowshoe/(\d+\.\d+)' + name: Snowshoe + version: '$1' + +#Safari +- regex: '(?:iPod|iPad|iPhone).+Version/(\d+\.\d+)' + name: Mobile Safari + version: '$1' +- regex: 'Version/(\d+\.\d+).*Mobile.*Safari/' + name: Mobile Safari + version: '$1' +- regex: '(?:iPod|iPhone|iPad)' + name: Mobile Safari + version: '' +- regex: 'Version/(\d+\.\d+).*Safari/|Safari/\d+' + name: Safari + version: '$1' + +#Android Browser +- regex: 'Android' + name: Android Browser + version: ''
\ No newline at end of file diff --git a/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/mobiles.yml b/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/mobiles.yml new file mode 100644 index 0000000000..d9a2a22f88 --- /dev/null +++ b/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/mobiles.yml @@ -0,0 +1,958 @@ +############### +# Piwik - Open source web analytics +# +# @link http://piwik.org +# @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later +# +# @category Piwik_Plugins +# @package Piwik_DevicesDetection +############### + +# HTC +HTC: + regex: 'HTC|Sprint APA|ADR[A-Za-z0-9]+' + device: 'smartphone' + models: + - regex: 'HTC ([A-Za-z0-9]+) Build' + model: '$1' + - regex: 'HTC ([A-Za-z0-9]+(?: [A-Za-z0-9]+)?)' + model: '$1' + - regex: 'USCCHTC(\d+)' + model: '$1' + - regex: 'Sprint APA(9292)' + model: '$1 (Sprint)' + - regex: 'HTC_([A-Za-z0-9_]+)' + model: '$1' + - regex: 'HTC(?:[\-/ ])?([A-Za-z0-9]+)' + model: '$1' + - regex: 'HTC;(?: )?([A-Za-z0-9 ]+)' + model: '$1' + - regex: '(ADR[A-Za-z0-9]+)' + model: '$1' + +# Tesla Model S +Tesla: + regex: 'QtCarBrowser' + device: 'car browser' + model: 'Model S' + +# Kindle +Kindle: + regex: 'KF(?:OT|TT|JWI|JWA) Build|Kindle|Silk/(\d+)\.(\d+)' + device: 'tablet' + models: + - regex: 'KFOT|Kindle Fire|Silk/(\d+)\.(\d+)' + model: 'Fire' + - regex: 'KFTT' + model: 'Fire HD' + - regex: 'KFJWI' + model: 'Fire HD 8.9" WiFi' + - regex: 'KFJWA' + model: 'Fire HD 8.9" 4G' + +# NOKIA +Nokia: + regex: 'Nokia|Lumia|Maemo RX|portalmmm/2\.0 N7|portalmmm/2\.0 NK|nok[0-9]+|Symbian.*\s([a-zA-Z0-9]+)$' + device: 'smartphone' + models: + - regex: 'NokiaInternal|Nokia-WAP-Toolkit|Nokia-MIT-Browser|Nokia Mobile|Nokia Browser|Nokia/Series' + model: '' + - regex: 'Nokia(N[0-9]+)' + model: '$1' + - regex: 'Nokia-([A-Za-z0-9]+)' + model: 'N$1' + - regex: 'NOKIA; ([A-Za-z0-9\- ]+)' + model: '$1' + - regex: 'NOKIA[ ]?([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'NOKIA/([A-Za-z0-9 ]+)' + model: '$1' + - regex: '(Lumia [A-Za-z0-9\-]+)' + model: '$1' + - regex: 'Maemo RX-51 ([A-Za-z0-9]+)' + model: '$1' + - regex: 'Maemo RX-34' + model: 'N800' + - regex: 'portalmmm/2\.0 (N7[37]|NK[A-Za-z0-9]+)' + model: '$1' + - regex: 'nok([0-9]+)' + model: '$1' + - regex: 'Symbian.*\s([a-zA-Z0-9]+)$' + device: 'feature phone' + model: '$1' + +# RIM/BlackBerry +RIM: + regex: 'BB10;|BlackBerry|rim[0-9]+|PlayBook' + device: 'smartphone' + + models: + - regex: 'BB10; ([A-Za-z0-9\- ]+)\)' + model: 'BlackBerry $1' + - regex: 'PlayBook.+RIM Tablet OS' + model: 'BlackBerry Playbook' + device: 'tablet' + - regex: 'BlackBerry(?: )?([A-Za-z0-9]+)' + model: 'BlackBerry $1' + - regex: 'rim([0-9]+)' + model: 'BlackBerry $1' + - regex: 'BlackBerry' + model: 'BlackBerry' + +# PALM +Palm: + regex: '(?:Pre|Pixi)/(\d+)\.(\d+)|Palm|Treo' + device: 'smartphone' + models: + - regex: '((?:Pre|Pixi))/(\d+\.\d+)' + model: '$1 $2' + - regex: 'Palm(?: )?([A-Za-z0-9]+)' + model: '$1' + - regex: 'Treo([A-Za-z0-9]+)' + model: 'Treo $1' + +# HP +HP: + regex: 'Touch[Pp]ad|hp-tablet|HP(?: )?iPAQ|webOS.*(P160U)' + device: 'smartphone' + models: + - regex: 'Touch[Pp]ad/(\d+\.\d+)|hp-tablet' + model: 'TouchPad' + device: 'tablet' + - regex: 'HP(?: )?iPAQ(?: )?([A-Za-z0-9]+)' + model: 'iPAQ $1' + - regex: 'webOS.*(P160U)' + model: 'Veer' + +# TiPhone +TiPhone: + regex: 'TiPhone(?: )?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Apple +Apple: + regex: 'AppleTV|iPad|iPod|iPhone' + models: + - regex: 'AppleTV' + model: 'Apple TV' + device: 'tv' + - regex: 'iPad' + model: 'iPad' + device: 'tablet' + - regex: 'iPod' + model: 'iPod Touch' + device: 'palmtop' + - regex: 'iPhone' + model: 'iPhone' + device: 'smartphone' + +# Acer +Acer: + regex: 'acer[\-_]([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Airness +Airness: + regex: 'AIRNESS-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Alcatel +Alcatel: + regex: 'Alcatel|Alc([A-Za-z0-9]+)' + device: 'smartphone' + models: + - regex: 'Alcatel UP' + model: '' + - regex: 'ALCATEL[ \-]([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'ALCATEL_([A-Za-z0-9_]+)' + model: '$1' + - regex: 'Alc([A-Za-z0-9]+)' + model: '$1' + +# Amoi +Amoi: + regex: 'Amoi' + device: 'smartphone' + models: + - regex: 'Amoi[\- /](A-Za-z0-9]+)' + mobile: '$1' + - regex: 'Amoisonic-([A-Za-z0-9]+)' + model: '$1' + +# Asus +Asus: + regex: 'Asus' + device: 'smartphone' + models: + - regex: 'Asus(?:-|;)?([A-Za-z0-9]+)' + model: '$1' + - regex: 'ASUS (Transformer Pad TF300T)' + device: 'tablet' + model: '$1' + +# Audiovox +Audiovox: + regex: 'Audiovox|CDM|UTS(?:TARCOM)?\-|audio([A-Za-z0-9\-]+)' + device: 'smartphone' + models: + - regex: 'Audiovox[_\-]([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'CDM(?:-)?([A-Za-z0-9]+)' + model: 'CDM-$1' + - regex: 'UTS(?:TARCOM)?-([A-Za-z0-9\-]+)' + model: 'CDM-$1' + - regex: 'audio([A-Za-z0-9\-]+)' + model: 'CDM-$1' + +# Avvio +Avvio: + regex: 'Avvio[ _]([A-Za-z0-9\-]+)' + device: 'smartphone' + model: '$1' + +# Bird +Bird: + regex: 'BIRD[\-. _]([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Becker +Becker: + regex: 'Becker-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Beetel +Beetel: + regex: 'Beetel ([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# BenQ-Siemens +BenQ-Siemens: + regex: 'BENQ-SIEMENS - ([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# BenQ +BenQ: + regex: 'BENQ(?:[ \-])?([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Capitel +Capitel: + regex: 'Capitel-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Compal +Compal: + regex: 'Compal-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Cricket +Cricket: + regex: 'Cricket-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Dell +Dell: + regex: 'Dell ([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Dbtel +Dbtel: + regex: 'DBTEL(?:[\-/])?([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Dicam +Dicam: + regex: 'DICAM-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# DoCoMo +DoCoMo: + regex: 'DoCoMo|\;FOMA|KGT/1\.0' + device: 'feature phone' + models: + - regex: 'DoCoMo/[12]\.0[/ ]([A-Za-z0-9]+)' + model: '$1' + - regex: '([A-Za-z0-9]+)(?:_W)?\;FOMA' + model: '$1' + - regex: 'KGT/1\.0 ([A-Za-z0-9]+)' + model: '$1' + +# Dopod +Dopod: + regex: 'Dopod(?: )?([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Ericy +Ericy: + regex: 'Ericy-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Sony Ericsson +Sony Ericsson: + regex: 'Sony(?: )?Ericsson|portalmmm/2\.0 K' + device: 'smartphone' + models: + - regex: 'SonyEricsson([A-Za-z0-9]+)' + model: '$1' + - regex: 'Sony(?: )?Ericsson ([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'portalmmm/2.0 K([A-Za-z0-9]+)' + model: 'K$1' + +# Ericsson +Ericsson: + regex: 'Ericsson(?:/ )?([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# eTouch +eTouch: + regex: 'eTouch(?: )?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Ezze +Ezze: + regex: 'EZZE-|EZ([A-Za-z0-9]+)' + device: 'feature phone' + models: + - regex: 'EZZE-([A-Za-z0-9]+)' + model: '$1' + - regex: 'EZ([A-Za-z0-9]+)' + model: 'EZ$1' + +# Ezio +Ezio: + regex: 'EZIO-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Gionee +Gionee: + regex: 'GIONEE-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Google +Google: + regex: 'Nexus|GoogleTV' + device: 'smartphone' + models: + - regex: '(Galaxy Nexus)' + model: '$1' + - regex: '(Nexus (:?S|4|One))' + model: '$1' + - regex: '(Nexus (:?7|10))' + device: 'tablet' + model: '$1' + - regex: '(GoogleTV)' + device: 'tv' + model: '$1' + +# Gradiente +Gradiente: + regex: 'GRADIENTE' + device: 'feature phone' + models: + - regex: 'GRADIENTE-([A-Za-z0-9]+)' + model: '$1' + - regex: 'GRADIENTE ([A-Za-z0-9\-]+)' + model: '$1' + +# Grundig +Grundig: + regex: 'GRUNDIG|portalmmm/2\.0 G' + device: 'tv' + models: + - regex: 'GRUNDIG ([A-Za-z0-9]+)' + model: '$1' + - regex: 'portalmmm/2\.0 G([A-Za-z0-9]+)' + model: 'G$1' + +# Haier +Haier: + regex: 'Haier[ -]([A-Za-z0-9\-]+)' + device: 'feature phone' + model: '$1' + +# Huawei +Huawei: + regex: 'Huawei|vodafone([A-Za-z0-9]+)' + device: 'smartphone' + models: + - regex: 'Huawei(?:[\- /_]|/1\.0/)?([A-Za-z0-9]+)' + model: '$1' + - regex: 'vodafone([A-Za-z0-9]+)' + model: 'Vodafone $1' + +# Innostream +Innostream: + regex: 'INNO([A-Za-z0-9]+)' + device: 'feature phone' + model: 'INNO$1' + +# Inq +INQ: + regex: 'INQ/([A-Za-z0-9\-]+)' + device: 'feature phone' + model: '$1' + +# i-mate +i-mate: + regex: 'i-mate ([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# i-mobile +i-mobile: + regex: 'i-mobile(?: )?([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# ikomo +iKoMo: + regex: 'iKoMo ([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# kddi +KDDI: + regex: 'kddi-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# kyocera +Kyocera: + regex: 'Kyocera|KWC-|QC-' + device: 'smartphone' + models: + - regex: 'Kyocera-KZ-([A-Za-z0-9]+)' + model: 'KZ $1' + - regex: 'Kyocera(:?[\-/])?([A-Za-z0-9]+)' + model: '$1' + - regex: '(?:KWC|QC)-([A-Za-z0-9]+)' + model: '$1' + +# lanix +Lanix: + regex: 'LANIX-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# lct +LCT: + regex: 'LCT_([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# lenovo +Lenovo: + regex: 'Lenovo[\-_]([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# lguplus +LGUPlus: + regex: 'LGUPlus' + device: 'smartphone' + model: '' + +# lg +LG: + regex: 'LG|portalmmm/2\.0 (?:KE|KG|KP|L3)|VX[0-9]+' + device: 'smartphone' + models: + - regex: 'LGE_DLNA_SDK' + device: 'tv' + model: 'NetCast' + - regex: 'LGE(?: |-LG| LG-AX|-)([A-Za-z0-9]+)' + model: '$1' + - regex: 'LGE;([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'LG(?:/|-LG| |-)?([A-Za-z0-9]+)' + model: '$1' + - regex: 'LG; ([A-Za-z0-9 ]+)' + model: '$1' + - regex: 'portalmmm/2.0 ((?:KE|KG|KP|L3)[A-Za-z0-9]+)' + model: '$1' + - regex: '(VX[0-9]+)' + model: '$1' + +# microsoft +Microsoft: + regex: 'Xbox|KIN\.(?:One|Two)' + device: 'console' + model: 'Xbox 360' + +# Konka +Konka: + regex: 'KONKA_([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Karbonn +Karbonn: + regex: 'Karbonn_([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Sagem +Sagem: + regex: 'SAGEM|portalmmm/2.0 (?:SG|my)' + device: 'smartphone' + models: + - regex: 'SAGEM ([A-Za-z0-9]+)' + model: '$1' + - regex: 'SAGEM-([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'portalmmm/2.0 ((?:SG|my)[A-Za-z0-9]+)' + model: '$1' + +# micromax +MicroMax: + regex: 'MicroMax(?:[ \-])?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# mio +Mio: + regex: 'MIO(?:/)?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# mitsubishi +Mitsubishi: + regex: 'MITSU|portalmmm/[12]\.0 M' + device: 'feature phone' + models: + - regex: 'MITSU/[A-Za-z0-9.]+ \(([A-Za-z0-9]+)\)' + model: '$1' + - regex: 'MITSU[ \-]?([A-Za-z0-9]+)' + model: '$1' + - regex: 'portalmmm/[12]\.0 (M[A-Za-z0-9]+)' + model: '$1' + +# motorola +Motorola: + regex: 'MOT|(?<!AN)DROID (?:Build|([A-Za-z0-9]+))|portalmmm/2.0 (?:E378i|L6|L7|v3)' + device: 'smartphone' + models: + - regex: 'Motorola[ \-]([A-Za-z0-9]+)' + model: '$1' + - regex: 'MOTORAZR[ \-]([A-Za-z0-9]+)' + model: 'RAZR $1' + - regex: 'MOTORIZR[ \-]([A-Za-z0-9]+)' + model: 'RIZR $1' + - regex: 'MOT[O]?[\-]?([A-Za-z0-9.]+)' + model: '$1' + - regex: '(?<!AN)DROID (?:Build|([A-Za-z0-9]+))' + model: 'DROID $1' + - regex: 'portalmmm/2.0 ((?:E378i|L6|L7|V3)[A-Za-z0-9]+)' + model: '$1' + +# myphone +MyPhone: + regex: 'MyPhone([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# nec +NEC: + regex: 'NEC|KGT/2\.0|portalmmm/1\.0 (?:DB|N)|(?:portalmmm|o2imode)/2.0[ ,]N' + device: 'smartphone' + models: + - regex: '(?:NEC-|KGT/2\.0 )([A-Za-z0-9]+)' + model: '$1' + - regex: 'portalmmm/1\.0 ((?:DB|N)[A-Za-z0-9]+)' + model: '$1' + - regex: '(?:portalmmm|o2imode)/2\.0[ ,](N[A-Za-z0-9]+)' + model: '$1' + +# newgen +Newgen: + regex: 'NEWGEN\-([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# nintendo +Nintendo: + regex: 'Nintendo (([3]?DS[i]?)|Wii[U]?)' + device: 'console' + model: '$1' + +# ngm +NGM: + regex: 'NGM_([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# nexian +Nexian: + regex: 'Nexian' + device: 'smartphone' + models: + - regex: 'Nexian[ ]?([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'Nexian-([A-Za-z0-9]+)' + model: '$1' + +# o2 +O2: + regex: 'Xda|O2[ \-]|COCOON' + device: 'smartphone' + models: + - regex: '(Xda[ _][A-Za-z0-9_]+)' + models: '$1' + - regex: '(COCOON)' + models: '$1' + - regex: 'O2 ([A-Za-z0-9 ]+)' + models: '$1' + - regex: 'O2-([A-Za-z0-9]+)' + models: '$1' + +# onda +Onda: + regex: 'Onda' + device: 'smartphone' + models: + regex: '([A-Za-z0-9]+)[ _]Onda' + model: '$1' + regex: 'Onda ([A-Za-z0-9]+)' + model: '$1' + +# oppo +OPPO: + regex: 'OPPO[ ]?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# orange +Orange: + regex: 'SPV[ \-]?([A-Za-z0-9]+)' + device: 'smartphone' + model: 'SPV $1' + +# panasonic +Panasonic: + regex: 'Panasonic' + device: 'smartphone' + models: + - regex: 'Panasonic MIL DLNA' + device: 'tv' + model: 'Viera Cast' + - regex: 'Panasonic[ \-]?([A-Za-z0-9]+)' + model: '$1' + - regex: 'portalmmm/2.0 (P[A-Za-z0-9]+)' + model: '$1' + +# philips +Philips: + regex: 'Philips' + device: 'smartphone' + models: + - regex: 'Philips-FISIO ([A-Za-z0-9]+)' + model: 'Fisio $1' + - regex: 'Philips[ ]?([A-Za-z0-9]+)' + model: '$1' + - regex: 'Philips-([A-Za-z0-9\-@]+)' + model: '$1' + +# phoneOne +phoneOne: + regex: 'phoneOne[ \-]?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Rover +Rover: + regex: 'Rover ([0-9]+)' + device: 'feature phone' + model: '$1' + +# Siemens +Siemens: + regex: 'SIEMENS|SIE-|portalmmm/2\.0 SI|S55|SL45i' + device: 'smartphone' + models: + - regex: 'SIEMENS[ \-]([A-Za-z0-9]+)' + model: '$1' + - regex: 'SIE(?:MENS )?[\-]?([A-Za-z0-9]+)' + model: '$1' + - regex: '(S55|SL45i)' + model: '$1' + - regex: 'portalmmm/2.0 (SI[A-Za-z0-9]+)' + model: '$1' + +# Samsung +Samsung: + regex: 'SAMSUNG|S(?:CH|GH|PH|EC|AM)-|SMART-TV|GT-|Galaxy|(?:portalmmm|o2imode)/2\.0 [SZ]|sam[rua]' + device: 'smartphone' + models: + - regex: 'SAMSUNG[\-;][ ]?([A-Za-z0-9]+[\-_][A-Za-z0-9]+)' + model: '$1' + - regex: 'SAMSUNG[ _/]?([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'SAMSUNG;[ ]?([A-Za-z0-9 ]+)' + model: '$1' + - regex: '((?:SCH|SGH|SPH|GT)-[A-Za-z0-9]+)' + model: '$1' + - regex: 'SEC-([A-Za-z0-9]+)' + model: '$1' + - regex: 'SAM-([A-Za-z0-9]+)' + model: 'SCH-$1' + - regex: 'SMART-TV' + device: 'tv' + model: 'Smart TV' + - regex: '(Galaxy [A-Za-z0-9]+)' + model: '$1' + - regex: '(?:portalmmm|o2imode)/2\.0 ([SZ][A-Za-z0-9]+)' + model: '$1' + - regex: 'sam([rua][0-9]+)' + model: 'SCH-$1' + +# pantech +Pantech: + regex: 'Pantech|P[GTN]-|TX[T]?[0-9]+' + device: 'smartphone' + models: + - regex: 'Pantech[ \-]?([A-Za-z0-9]+)' + model: '$1' + - regex: 'Pantech_([A-Za-z0-9\-]+)' + model: '$1' + - regex: '(P[GTN]-[A-Za-z0-9]+)' + model: '$1' + - regex: '(TX[T]?[0-9]+)' + model: '$1' + +# Sanyo +Sanyo: + regex: 'Sanyo|MobilePhone ' + device: 'smartphone' + models: + - regex: 'SANYO[ \-_]([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'MobilePhone ([A-Za-z0-9\-]+)' + model: '$1' + +# Sega +Sega: + regex: 'Dreamcast' + device: 'console' + model: 'Dreamcast' + +# Sendo +Sendo: + regex: 'Sendo([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Spice +Spice: + regex: 'Spice' + device: 'smartphone' + models: + - regex: 'Spice ([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'Spice-([A-Za-z0-9]+)' + model: '$1' + +# Sharp +Sharp: + regex: 'SHARP|SBM' + device: 'smartphone' + models: + - regex: 'SHARP-AQUOS' + device: 'tv' + model: 'Aquos Net Plus' + - regex: 'SHARP[ \-]([A-Za-z0-9\-]+)' + model: '$1' + - regex: '(?:SHARP|SBM)([A-Za-z0-9]+)' + model: '$1' + +# Softbank +Softbank: + regex: 'Softbank|J-PHONE' + device: 'smartphone' + models: + - regex: 'Softbank/[12]\.0/([A-Za-z0-9]+)' + model: '$1' + - regex: '([A-Za-z0-9]+);Softbank;' + model: '$1' + - regex: 'J-PHONE/[0-9]\.[0-9]/([A-Za-z0-9\-]+)' + model: '$1' + +# Sony +Sony: + regex: 'Sony|PlayStation' + device: 'smartphone' + models: + - regex: 'Sony[ ]?([A-Za-z0-9\-]+)' + model: '$1' + - regex: '(PlayStation (?:3|Portable|Vita))' + device: 'console' + model: '$1' + +# Qtek +Qtek: + regex: 'Qtek[ _]?([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# T-Mobile +T-Mobile: + regex: 'T-Mobile[ _]([A-Za-z0-9 ]+)' + device: 'smartphone' + model: '$1' + +# Tcl +TCL: + regex: 'TCL-([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Telit +Telit: + regex: 'Telit' + device: 'feature phone' + models: + - regex: 'Telit_Mobile_Terminals-([A-Za-z0-9]+)' + model: '$1' + - regex: 'Telit[\-_]?([A-Za-z0-9]+)' + model: '$1' + +# Tianyu +TIANYU: + regex: 'TIANYU' + device: 'feature phone' + models: + - regex: 'TIANYU ([A-Za-z0-9]+)' + model: '$1' + - regex: 'TIANYU-KTOUCH/([A-Za-z0-9]+)' + model: '$1' + +# Toplux +Toplux: + regex: 'Toplux ([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# UTStarcom +UTStarcom: + regex: 'utstar([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Vitelcom +Vitelcom: + regex: 'Vitelcom|portalmmm/[12].0 TSM' + device: 'feature phone' + models: + - regex: 'TSM-([A-Za-z0-9]+)' + model: '$1' + - regex: 'TSM([A-Za-z0-9\-]+)' + model: '$1' + - regex: 'portalmmm/[12].0 (TSM[A-Za-z0-9 ]+)' + model: '$1' + +# VK Mobile +VK Mobile: + regex: 'VK[\-]?([A-Za-z0-9 ]+)' + device: 'feature phone' + model: '$1' + +# Vertu +Vertu: + regex: 'Vertu[ ]?([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Videocon +Videocon: + regex: 'Videocon_([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Voxtel +Voxtel: + regex: 'Voxtel_([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Wellcom +WellcoM: + regex: 'WELLCOM[ _\-/]([A-Za-z0-9]+)' + device: 'smartphone' + model: '$1' + +# Wonu +Wonu: + regex: 'Wonu ([A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Zonda +Zonda: + regex: '(ZM(?:CK|EM|TFTV|TN)[A-Za-z0-9]+)' + device: 'feature phone' + model: '$1' + +# Toshiba +Toshiba: + regex: 'Toshiba|portalmmm/[12].0 TS' + device: 'smartphone' + models: + - regex: 'Toshiba[ /_\-]?([A-Za-z0-9 ]+)' + model: '$1' + - regex: 'portalmmm/[12].0 (TS[A-Za-z0-9 ]+)' + model: '$1' + +# Fly +Fly: + regex: 'Fly|MERIDIAN-' + device: 'smartphone' + models: + - regex: 'Fly[ _\-]?([A-Za-z0-9]+)' + model: '$1' + - regex: 'MERIDIAN-([A-Za-z0-9]+)' + model: '$1' + +# WebTV +WebTV: + regex: 'WebTV/(\d+\.\d+)' + device: 'tv' + model: '$1' + +# ZTE +ZTE: + regex: 'ZTE|Z331' + device: 'smartphone' + models: + - regex: '(Z331)' + model: '$1' + - regex: 'ZTE-(?:G |G-)?([A-Za-z0-9 _]+)' + model: '$1' + - regex: 'ZTE ([A-Za-z0-9]+)' + model: '$1' + +# Symbian to Nokia ?? +# Change name from Nokia to other to not change above Nokia element +#Nokia: +# regex: 'Symbian' +# device: 'feature phone'
\ No newline at end of file diff --git a/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/oss.yml b/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/oss.yml new file mode 100644 index 0000000000..71b85f987f --- /dev/null +++ b/plugins/DevicesDetection/UserAgentParserEnhanced/regexes/oss.yml @@ -0,0 +1,427 @@ +############### +# Piwik - Open source web analytics +# +# @link http://piwik.org +# @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later +# +# @category Piwik_Plugins +# @package Piwik_DevicesDetection +############### + +########## +# Tizen +########## +- regex: 'Tizen' + name: 'Tizen' + version: '' + + + +########## +# Android +########## +- regex: 'Android[ /](\d+\.\d+)' + name: 'Android' + version: '$1' + + +- regex: 'Android|Silk-Accelerated=[a-z]{4,5}' + name: 'Android' + version: '' + + + +########## +# Linux +########## +- regex: 'Linux; .*((?:Arch Linux|Debian|Knoppix|Mint|Ubuntu|Kubuntu|Xubuntu|Lubuntu|Fedora|Red Hat|Mandriva|Gentoo|Slackware|SUSE|Puppy|CentOS|BackTrack|YunOs|Presto))[ /](\d+\.\d+)' + name: '$1' + version: '$2' + + +- regex: '((?:Arch Linux|Debian|Knoppix|Mint|Ubuntu|Kubuntu|Xubuntu|Lubuntu|Fedora|Red Hat|Mandriva|Gentoo|Slackware|SUSE|Puppy|CentOS|BackTrack|YunOs|Presto));.*Linux' + name: '$1' + version: '' + + +- regex: 'Linux; |Linux (?:x86_64|zbov|i686)' + name: 'Linux' + version: '' + + + +########## +# Windows Mobile +########## +- regex: 'Windows Phone (?:OS)?[ ]?(\d+\.\d+)' + name: 'Windows Phone' + version: '$1' + + +- regex: 'XBLWP7|Windows Phone' + name: 'Windows Phone' + version: '' + +- regex: 'Windows CE' + name: 'Windows CE' + version: '' + + +- regex: '(?:IEMobile|Windows Mobile)(?: (\d+\.\d+))?' + name: 'Windows Mobile' + version: '$1' + + +- regex: 'Windows NT 6.2; ARM;' + name: 'Windows RT' + version: '' + + + +########## +# Windows +########## +- regex: 'CYGWIN_NT-6.2|Windows NT 6.2|Windows 8' + name: 'Windows 8' + version: '8' + + +- regex: 'CYGWIN_NT-6.1|Windows NT 6.1|Windows 7' + name: 'Windows 7' + version: '7' + + +- regex: 'CYGWIN_NT-6.0|Windows NT 6.0|Windows Vista' + name: 'Windows Vista' + version: 'Vista' + + +- regex: 'CYGWIN_NT-5.2|Windows NT 5.2|Windows Server 2003 / XP x64' + name: 'Windows Server 2003' + version: 'Server 2003' + + +- regex: 'CYGWIN_NT-5.1|Windows NT 5.1|Windows XP' + name: 'Windows XP' + version: 'XP' + + +- regex: 'CYGWIN_NT-5.0|Windows NT 5.0|Windows 2000' + name: 'Windows 2000' + version: '2000' + + +- regex: 'CYGWIN_NT-4.0|Windows NT 4.0|WinNT|Windows NT' + name: 'Windows NT' + version: 'NT' + + +- regex: 'CYGWIN_ME-4.90|Win 9x 4.90|Windows ME' + name: 'Windows ME' + version: 'ME' + + +- regex: 'CYGWIN_98-4.10|Win98|Windows 98' + name: 'Windows 98' + version: '98' + + +- regex: 'CYGWIN_95-4.0|Win32|Win95|Windows 95|Windows_95' + name: 'Windows 95' + version: '95' + + +- regex: 'Windows 3.1' + name: 'Windows 3.1' + version: '3.1' + + +- regex: 'Windows' + name: 'Windows' + version: '' + + + +########## +# Mac +########## +- regex: 'Mac OS X (\d+[_.]\d+)' + name: 'Mac' + + version: '$1' + +- regex: 'Darwin|Macintosh|Mac_PowerPC|PPC|Mac PowerPC' + name: 'Mac' + version: '' + + + +########## +# iOS +########## +- regex: '(?:CPU OS|iPhone OS) (\d+_\d+)' + name: 'iOS' + + version: '$1' + +- regex: '(?:iPhone|iPad|iPod)(?:.*Mac OS X.*Version/(\d+\.\d+)|; Opera)' + name: 'iOS' + version: '$1' + + + +########## +# webOS +########## +- regex: '(?:webOS|Palm webOS)(?:/(\d+\.\d+))?' + name: 'webOS' + version: '$1' + + +- regex: '(?:PalmOS|Palm OS)(?:/(\d+\.\d+))?' + name: 'PalmOS' + version: '' + + + +########## +# ChromeOS +########## +- regex: 'CrOS [a-z0-9_]+ (\d+\.\d+)' + name: 'Chrome OS' + version: '$1' + + + +########## +# BlackBerry +########## +- regex: '(?:BB10;.+Version|Black[Bb]erry[0-9a-z]+|Black[Bb]erry.+Version)/(\d+\.\d+)' + name: 'BlackBerry OS' + version: '$1' + + +- regex: 'RIM Tablet OS (\d+\.\d+)' + name: 'BlackBerry Tablet OS' + version: '$1' + + +- regex: 'RIM Tablet OS|QNX|Play[Bb]ook' + name: 'BlackBerry Tablet OS' + version: '' + + +- regex: 'Black[Bb]erry' + name: 'BlackBerry OS' + version: '' + + + +########## +# Symbian +########## +- regex: 'Symbian[Oo][Ss]/(\d+\.\d+)' + name: 'Symbian OS' + version: '$1' + + +- regex: 'Symbian/3.+NokiaBrowser/7\.3' + name: 'Symbian' + version: '^3 Anna' + + +- regex: 'Symbian/3.+NokiaBrowser/7\.4' + name: 'Symbian' + version: '^3 Belle' + + +- regex: 'Symbian[/]?3' + name: 'Symbian^3' + version: '^3' + + +- regex: '(?:Series 60|SymbOS|S60)(?:[ /]?(\d+\.\d+|V\d+))?' + name: 'Symbian OS Series 60' + version: '$1' + + +- regex: 'Series40' + name: 'Symbian OS Series 40' + version: '' + + +- regex: 'MeeGo|WeTab' + name: 'MeeGo' + version: '' + + +- regex: 'Symbian [Oo][Ss]|SymbOS' + name: 'Symbian OS' + version: '' + + +- regex: 'Nokia' + name: 'Symbian' + version: '' + + + +########## +# Firefox OS +########## +- regex: '(?:Mobile|Tablet);.+Firefox/\d+\.\d+' + name: 'Firefox OS' + version: '' + + + +########## +# Bada +########## +- regex: 'bada' + name: 'Bada' + version: '' + + + +########## +# Brew +########## +- regex: '(?:Brew MP|BREW|BMP)(?:[ /](\d+\.\d+))?' + name: 'Brew' + version: '$1' + + + +########## +# Web TV +########## +- regex: 'GoogleTV[ /](\d+\.\d+)|GoogleTV' + name: 'Google TV' + version: '$1' + + +- regex: 'AppleTV/(\d+\.\d+)' + name: 'Apple TV' + version: '$1' + + +- regex: 'WebTV/(\d+\.\d+)' + name: 'WebTV' + version: '$1' + + + +########## +# Unix +########## +- regex: 'SunOS' + name: 'Solaris' + version: '' + + +- regex: 'AIX' + name: 'AIX' + version: '' + + +- regex: 'HP-UX' + name: 'HP-UX' + version: '' + + +- regex: 'FreeBSD' + name: 'FreeBSD' + version: '' + + +- regex: 'NetBSD' + name: 'NetBSD' + version: '' + + +- regex: 'OpenBSD' + name: 'OpenBSD' + version: '' + + +- regex: 'DragonFly' + name: 'DragonFly' + version: '' + + +- regex: 'Syllable' + name: 'Syllable' + version: '' + + +- regex: 'IRIX' + name: 'IRIX' + version: '' + + +- regex: 'OSF1' + name: 'OSF1' + version: '' + + + +########## +# Gaming Console +########## +- regex: 'Nintendo Wii' + name: 'Nintendo' + version: 'Wii' + + +- regex: 'PlayStation 3|PlayStation3' + name: 'PlayStation' + version: '3' + + +- regex: 'Xbox|KIN\.(?:One|Two)' + name: 'Xbox' + version: '360' + + + +########## +# Mobile Gaming Console +########## +- regex: 'Nitro|Nintendo ([3]?DS[i]?)' + name: 'Nintendo Mobile' + version: '$1' + + +- regex: 'PlayStation ((?:Portable|Vita))' + name: 'PlayStation' + version: '$1' + + + +########## +# IBM +########## +- regex: 'OS/2' + name: 'OS/2' + version: '' + + + +########## +# Simulators +########## +- regex: '(Talkatone|WinWAP)' + name: '$1' + version: '' + + + +########## +# Bot +########## +- regex: '(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves/Teoma|ia_archiver|ScoutJet|Gulper Web Bot|EmailWolf|grub-client|Download Demon|OmniWeb|SearchExpress|Microsoft URL Control|bot|borg|yahoo|slurp|msnbot|msrbot|openbot|archiver|netresearch|transcoder|crawler|lycos|scooter|altavista|teoma|gigabot|baiduspider|blitzbot|oegp|charlotte|furlbot|http%20client|polybot|htdig|ichiro|mogimogi|larbin|pompos|scrubby|searchsight|seekbot|semanticdiscovery|silk|snappy|speedy|spider|voila|vortex|voyager|zao|zeal|fast-webcrawler|converacrawler|dataparksearch|findlinksYottaaMonitor|BrowserMob|HttpMonitor|YandexBot|Slurp|BingPreview|PagePeeker|ThumbShotsBot|WebThumb|URL2PNG|ZooShot|GomezA|Catchpoint bot|Willow Internet Crawler|Google SketchUp|Read%20Later|Minimo|Pingdom.com|facebookexternalhit|Twitterbot|RackspaceBot)' + name: 'Bot ' + version: '' + +
\ No newline at end of file diff --git a/plugins/DevicesDetection/functions.php b/plugins/DevicesDetection/functions.php new file mode 100644 index 0000000000..4819cd6974 --- /dev/null +++ b/plugins/DevicesDetection/functions.php @@ -0,0 +1,149 @@ +<?php + +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_DevicesDetection + */ +function Piwik_GetBrandLogo($label) +{ + $path = dirname(__FILE__) . '/images/brand/' . $label . '.ico'; + if (file_exists($path)) { + return 'plugins/DevicesDetection/images/brand/' . $label . '.ico'; + } else { + return 'plugins/DevicesDetection/images/brand/unknown.ico'; + } +} + +function Piwik_getBrowserFamilyFullNameExtended($label) +{ + foreach (UserAgentParserEnhanced::$browserFamilies as $name => $family) { + if (in_array($label, $family)) { + return $name; + } + } + return Piwik_Translate('General_Unknown'); +} + +function Piwik_getBrowserFamilyLogoExtended($label) +{ + if (array_key_exists($label, UserAgentParserEnhanced::$browserFamilies)) { + $path = 'plugins/UserSettings/images/browsers/' . UserAgentParserEnhanced::$browserFamilies[$label][0] . '.gif'; + } else { + $path = 'plugins/UserSettings/images/browsers/UNK.gif'; + } + return $path; +} + +function Piwik_getBrowserNameExtended($label) +{ + $short = substr($label, 0, 2); + $ver = substr($label, 3, 10); + if (array_key_exists($short, UserAgentParserEnhanced::$browsers)) { + return trim(ucfirst(UserAgentParserEnhanced::$browsers[$short]) . ' ' . $ver); + } else { + return Piwik_Translate('General_Unknown'); + } +} + +function Piwik_getBrowserLogoExtended($label) +{ + $short = substr($label, 0, 2); + + $familyName = Piwik_getBrowserFamilyFullNameExtended($short); + $path = Piwik_getBrowserFamilyLogoExtended($familyName); + + return $path; +} + +function Piwik_getDeviceBrandLabel($label) +{ + if (array_key_exists($label, UserAgentParserEnhanced::$deviceBrands)) { + return ucfirst(UserAgentParserEnhanced::$deviceBrands[$label]); + } else { + return Piwik_Translate('General_Unknown'); + } +} + +function Piwik_getDeviceTypeLabel($label) +{ + if (isset(UserAgentParserEnhanced::$deviceTypes[$label])) { + return UserAgentParserEnhanced::$deviceTypes[$label]; + } else { + return Piwik_Translate('General_Unknown'); + } +} + +function Piwik_getDeviceTypeLogo($label) +{ + $deviceTypeLogos = Array( + "Desktop" => "normal.gif", + "Smartphone" => "smartphone.png", + "Tablet" => "tablet.png", + "Tv" => "tv.png", + "Feature phone" => "mobile.gif", + "Console" => "console.gif"); + + if (!array_key_exists($label, $deviceTypeLogos) || $label == "Unknown") { + $label = 'unknown.gif'; + } else { + $label = $deviceTypeLogos[$label]; + } + $path = 'plugins/DevicesDetection/images/screens/' . $label; + return $path; +} + +function Piwik_getModelName($label) +{ + if (!$label) { + return Piwik_Translate('General_Unknown'); + } + return $label; +} + +function Piwik_getOSFamilyFullNameExtended($label) +{ + foreach (UserAgentParserEnhanced::$osFamilies as $name => $family) { + if (in_array($label, $family)) { + return $name; + } + } + return Piwik_Translate('General_Unknown'); +} + +function Piwik_getOsFamilyLogoExtended($label) +{ + if (array_key_exists($label, UserAgentParserEnhanced::$osFamilies)) { + $path = 'plugins/UserSettings/images/os/' . UserAgentParserEnhanced::$osFamilies[$label][0] . ".gif"; + } else { + $path = 'plugins/UserSettings/images/os/UNK.gif'; + } + return $path; +} + +function Piwik_getOsFullNameExtended($label) +{ + if (!empty($label) && $label != ";") { + $os = substr($label, 0, 3); + $ver = substr($label, 4, 15); + $name = UserAgentParserEnhanced::getOsNameFromId($os, $ver); + if(!empty($name)) { + return $name; + } + } + return Piwik_Translate('General_Unknown'); +} + + + +function Piwik_getOsLogoExtended($label) +{ + $short = substr($label, 0, 3); + $familyName = Piwik_getOsFamilyFullNameExtended($short); + $path = Piwik_getOsFamilyLogoExtended($familyName); + return $path; +}
\ No newline at end of file diff --git a/plugins/DevicesDetection/images/brand/Acer.ico b/plugins/DevicesDetection/images/brand/Acer.ico Binary files differnew file mode 100644 index 0000000000..6c5b9f6a9e --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Acer.ico diff --git a/plugins/DevicesDetection/images/brand/Alcatel.ico b/plugins/DevicesDetection/images/brand/Alcatel.ico Binary files differnew file mode 100644 index 0000000000..9d8de98eab --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Alcatel.ico diff --git a/plugins/DevicesDetection/images/brand/Apple.ico b/plugins/DevicesDetection/images/brand/Apple.ico Binary files differnew file mode 100644 index 0000000000..8f562cc422 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Apple.ico diff --git a/plugins/DevicesDetection/images/brand/Asus.ico b/plugins/DevicesDetection/images/brand/Asus.ico Binary files differnew file mode 100644 index 0000000000..6ca527164a --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Asus.ico diff --git a/plugins/DevicesDetection/images/brand/Audiovox.ico b/plugins/DevicesDetection/images/brand/Audiovox.ico Binary files differnew file mode 100644 index 0000000000..1745af087e --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Audiovox.ico diff --git a/plugins/DevicesDetection/images/brand/Avvio.ico b/plugins/DevicesDetection/images/brand/Avvio.ico Binary files differnew file mode 100644 index 0000000000..e96d6e7d9c --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Avvio.ico diff --git a/plugins/DevicesDetection/images/brand/Becker.ico b/plugins/DevicesDetection/images/brand/Becker.ico Binary files differnew file mode 100644 index 0000000000..414fab5a71 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Becker.ico diff --git a/plugins/DevicesDetection/images/brand/Beetel.ico b/plugins/DevicesDetection/images/brand/Beetel.ico Binary files differnew file mode 100644 index 0000000000..c9bef20575 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Beetel.ico diff --git a/plugins/DevicesDetection/images/brand/BenQ.ico b/plugins/DevicesDetection/images/brand/BenQ.ico Binary files differnew file mode 100644 index 0000000000..dcfdd0908d --- /dev/null +++ b/plugins/DevicesDetection/images/brand/BenQ.ico diff --git a/plugins/DevicesDetection/images/brand/Compal.ico b/plugins/DevicesDetection/images/brand/Compal.ico Binary files differnew file mode 100644 index 0000000000..20c1340e86 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Compal.ico diff --git a/plugins/DevicesDetection/images/brand/Cricket.ico b/plugins/DevicesDetection/images/brand/Cricket.ico Binary files differnew file mode 100644 index 0000000000..cdaeffaa01 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Cricket.ico diff --git a/plugins/DevicesDetection/images/brand/Dell.ico b/plugins/DevicesDetection/images/brand/Dell.ico Binary files differnew file mode 100644 index 0000000000..1d7e4ac457 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Dell.ico diff --git a/plugins/DevicesDetection/images/brand/DoCoMo.ico b/plugins/DevicesDetection/images/brand/DoCoMo.ico Binary files differnew file mode 100644 index 0000000000..7874325557 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/DoCoMo.ico diff --git a/plugins/DevicesDetection/images/brand/Ericsson.ico b/plugins/DevicesDetection/images/brand/Ericsson.ico Binary files differnew file mode 100644 index 0000000000..2b91cfd581 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Ericsson.ico diff --git a/plugins/DevicesDetection/images/brand/Google.ico b/plugins/DevicesDetection/images/brand/Google.ico Binary files differnew file mode 100644 index 0000000000..cae29b26fd --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Google.ico diff --git a/plugins/DevicesDetection/images/brand/Gradiente.ico b/plugins/DevicesDetection/images/brand/Gradiente.ico Binary files differnew file mode 100644 index 0000000000..de530228b9 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Gradiente.ico diff --git a/plugins/DevicesDetection/images/brand/Grundig.ico b/plugins/DevicesDetection/images/brand/Grundig.ico Binary files differnew file mode 100644 index 0000000000..3dd1731e0a --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Grundig.ico diff --git a/plugins/DevicesDetection/images/brand/HP.ico b/plugins/DevicesDetection/images/brand/HP.ico Binary files differnew file mode 100644 index 0000000000..6870d2a134 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/HP.ico diff --git a/plugins/DevicesDetection/images/brand/HTC.ico b/plugins/DevicesDetection/images/brand/HTC.ico Binary files differnew file mode 100644 index 0000000000..a4b722423f --- /dev/null +++ b/plugins/DevicesDetection/images/brand/HTC.ico diff --git a/plugins/DevicesDetection/images/brand/Haier.ico b/plugins/DevicesDetection/images/brand/Haier.ico Binary files differnew file mode 100644 index 0000000000..761eba5924 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Haier.ico diff --git a/plugins/DevicesDetection/images/brand/Huawei.ico b/plugins/DevicesDetection/images/brand/Huawei.ico Binary files differnew file mode 100644 index 0000000000..d2d72cc39c --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Huawei.ico diff --git a/plugins/DevicesDetection/images/brand/INQ.ico b/plugins/DevicesDetection/images/brand/INQ.ico Binary files differnew file mode 100644 index 0000000000..8317daeac5 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/INQ.ico diff --git a/plugins/DevicesDetection/images/brand/KDDI.ico b/plugins/DevicesDetection/images/brand/KDDI.ico Binary files differnew file mode 100644 index 0000000000..8ba0c412b4 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/KDDI.ico diff --git a/plugins/DevicesDetection/images/brand/Karbonn.ico b/plugins/DevicesDetection/images/brand/Karbonn.ico Binary files differnew file mode 100644 index 0000000000..370ac7081c --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Karbonn.ico diff --git a/plugins/DevicesDetection/images/brand/Kindle.ico b/plugins/DevicesDetection/images/brand/Kindle.ico Binary files differnew file mode 100644 index 0000000000..f1c96b70a4 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Kindle.ico diff --git a/plugins/DevicesDetection/images/brand/Kyocera.ico b/plugins/DevicesDetection/images/brand/Kyocera.ico Binary files differnew file mode 100644 index 0000000000..5415b00407 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Kyocera.ico diff --git a/plugins/DevicesDetection/images/brand/LG.ico b/plugins/DevicesDetection/images/brand/LG.ico Binary files differnew file mode 100644 index 0000000000..ca03ac84c8 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/LG.ico diff --git a/plugins/DevicesDetection/images/brand/LGUPlus.ico b/plugins/DevicesDetection/images/brand/LGUPlus.ico Binary files differnew file mode 100644 index 0000000000..cf8b998ce9 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/LGUPlus.ico diff --git a/plugins/DevicesDetection/images/brand/Lanix.ico b/plugins/DevicesDetection/images/brand/Lanix.ico Binary files differnew file mode 100644 index 0000000000..9736d9ef90 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Lanix.ico diff --git a/plugins/DevicesDetection/images/brand/Lenovo.ico b/plugins/DevicesDetection/images/brand/Lenovo.ico Binary files differnew file mode 100644 index 0000000000..beb7c247f9 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Lenovo.ico diff --git a/plugins/DevicesDetection/images/brand/MicroMax.ico b/plugins/DevicesDetection/images/brand/MicroMax.ico Binary files differnew file mode 100644 index 0000000000..d31758326d --- /dev/null +++ b/plugins/DevicesDetection/images/brand/MicroMax.ico diff --git a/plugins/DevicesDetection/images/brand/Microsoft.ico b/plugins/DevicesDetection/images/brand/Microsoft.ico Binary files differnew file mode 100644 index 0000000000..94d1a2b3ab --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Microsoft.ico diff --git a/plugins/DevicesDetection/images/brand/Mio.ico b/plugins/DevicesDetection/images/brand/Mio.ico Binary files differnew file mode 100644 index 0000000000..16f8ef2e59 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Mio.ico diff --git a/plugins/DevicesDetection/images/brand/Mitsubishi.ico b/plugins/DevicesDetection/images/brand/Mitsubishi.ico Binary files differnew file mode 100644 index 0000000000..abd920e561 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Mitsubishi.ico diff --git a/plugins/DevicesDetection/images/brand/Motorola.ico b/plugins/DevicesDetection/images/brand/Motorola.ico Binary files differnew file mode 100644 index 0000000000..f388f50c9a --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Motorola.ico diff --git a/plugins/DevicesDetection/images/brand/MyPhone.ico b/plugins/DevicesDetection/images/brand/MyPhone.ico Binary files differnew file mode 100644 index 0000000000..4fccc0c0c2 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/MyPhone.ico diff --git a/plugins/DevicesDetection/images/brand/NGM.ico b/plugins/DevicesDetection/images/brand/NGM.ico Binary files differnew file mode 100644 index 0000000000..5a0fcfc1ba --- /dev/null +++ b/plugins/DevicesDetection/images/brand/NGM.ico diff --git a/plugins/DevicesDetection/images/brand/Nintendo.ico b/plugins/DevicesDetection/images/brand/Nintendo.ico Binary files differnew file mode 100644 index 0000000000..4c949d50c5 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Nintendo.ico diff --git a/plugins/DevicesDetection/images/brand/Nokia.ico b/plugins/DevicesDetection/images/brand/Nokia.ico Binary files differnew file mode 100644 index 0000000000..fe54973014 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Nokia.ico diff --git a/plugins/DevicesDetection/images/brand/O2.ico b/plugins/DevicesDetection/images/brand/O2.ico Binary files differnew file mode 100644 index 0000000000..15ef4a7a24 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/O2.ico diff --git a/plugins/DevicesDetection/images/brand/OPPO.ico b/plugins/DevicesDetection/images/brand/OPPO.ico Binary files differnew file mode 100644 index 0000000000..10af023560 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/OPPO.ico diff --git a/plugins/DevicesDetection/images/brand/Onda.ico b/plugins/DevicesDetection/images/brand/Onda.ico Binary files differnew file mode 100644 index 0000000000..3bc55223ee --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Onda.ico diff --git a/plugins/DevicesDetection/images/brand/Orange.ico b/plugins/DevicesDetection/images/brand/Orange.ico Binary files differnew file mode 100644 index 0000000000..55437dda38 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Orange.ico diff --git a/plugins/DevicesDetection/images/brand/Palm.ico b/plugins/DevicesDetection/images/brand/Palm.ico Binary files differnew file mode 100644 index 0000000000..901c44fa3f --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Palm.ico diff --git a/plugins/DevicesDetection/images/brand/Panasonic.ico b/plugins/DevicesDetection/images/brand/Panasonic.ico Binary files differnew file mode 100644 index 0000000000..00a3e9cb24 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Panasonic.ico diff --git a/plugins/DevicesDetection/images/brand/Pantech.ico b/plugins/DevicesDetection/images/brand/Pantech.ico Binary files differnew file mode 100644 index 0000000000..6fc39d30cb --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Pantech.ico diff --git a/plugins/DevicesDetection/images/brand/Philips.ico b/plugins/DevicesDetection/images/brand/Philips.ico Binary files differnew file mode 100644 index 0000000000..8eb97f5ecd --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Philips.ico diff --git a/plugins/DevicesDetection/images/brand/RIM.ico b/plugins/DevicesDetection/images/brand/RIM.ico Binary files differnew file mode 100644 index 0000000000..c7d1bdacc2 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/RIM.ico diff --git a/plugins/DevicesDetection/images/brand/Samsung.ico b/plugins/DevicesDetection/images/brand/Samsung.ico Binary files differnew file mode 100644 index 0000000000..b7a852d6aa --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Samsung.ico diff --git a/plugins/DevicesDetection/images/brand/Sanyo.ico b/plugins/DevicesDetection/images/brand/Sanyo.ico Binary files differnew file mode 100644 index 0000000000..5415b00407 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Sanyo.ico diff --git a/plugins/DevicesDetection/images/brand/Sega.ico b/plugins/DevicesDetection/images/brand/Sega.ico Binary files differnew file mode 100644 index 0000000000..aa5ba9bdfd --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Sega.ico diff --git a/plugins/DevicesDetection/images/brand/Softbank.ico b/plugins/DevicesDetection/images/brand/Softbank.ico Binary files differnew file mode 100644 index 0000000000..da354bfb46 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Softbank.ico diff --git a/plugins/DevicesDetection/images/brand/Sony Ericsson.ico b/plugins/DevicesDetection/images/brand/Sony Ericsson.ico Binary files differnew file mode 100644 index 0000000000..776a08d5af --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Sony Ericsson.ico diff --git a/plugins/DevicesDetection/images/brand/Sony.ico b/plugins/DevicesDetection/images/brand/Sony.ico Binary files differnew file mode 100644 index 0000000000..776a08d5af --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Sony.ico diff --git a/plugins/DevicesDetection/images/brand/T-Mobile.ico b/plugins/DevicesDetection/images/brand/T-Mobile.ico Binary files differnew file mode 100644 index 0000000000..0496780292 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/T-Mobile.ico diff --git a/plugins/DevicesDetection/images/brand/Telit.ico b/plugins/DevicesDetection/images/brand/Telit.ico Binary files differnew file mode 100644 index 0000000000..bca28a1684 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Telit.ico diff --git a/plugins/DevicesDetection/images/brand/TiPhone.ico b/plugins/DevicesDetection/images/brand/TiPhone.ico Binary files differnew file mode 100644 index 0000000000..87a7ccd4da --- /dev/null +++ b/plugins/DevicesDetection/images/brand/TiPhone.ico diff --git a/plugins/DevicesDetection/images/brand/Vertu.ico b/plugins/DevicesDetection/images/brand/Vertu.ico Binary files differnew file mode 100644 index 0000000000..48ff28926b --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Vertu.ico diff --git a/plugins/DevicesDetection/images/brand/Videocon.ico b/plugins/DevicesDetection/images/brand/Videocon.ico Binary files differnew file mode 100644 index 0000000000..dc1bda5cc6 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Videocon.ico diff --git a/plugins/DevicesDetection/images/brand/Zonda.ico b/plugins/DevicesDetection/images/brand/Zonda.ico Binary files differnew file mode 100644 index 0000000000..eede4f8baa --- /dev/null +++ b/plugins/DevicesDetection/images/brand/Zonda.ico diff --git a/plugins/DevicesDetection/images/brand/eTouch.ico b/plugins/DevicesDetection/images/brand/eTouch.ico Binary files differnew file mode 100644 index 0000000000..d4487284b2 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/eTouch.ico diff --git a/plugins/DevicesDetection/images/brand/i-mobile.ico b/plugins/DevicesDetection/images/brand/i-mobile.ico Binary files differnew file mode 100644 index 0000000000..c5a9b064c2 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/i-mobile.ico diff --git a/plugins/DevicesDetection/images/brand/unknown.ico b/plugins/DevicesDetection/images/brand/unknown.ico Binary files differnew file mode 100644 index 0000000000..62a489e293 --- /dev/null +++ b/plugins/DevicesDetection/images/brand/unknown.ico diff --git a/plugins/DevicesDetection/images/screens/computer.png b/plugins/DevicesDetection/images/screens/computer.png Binary files differnew file mode 100644 index 0000000000..9763689e6f --- /dev/null +++ b/plugins/DevicesDetection/images/screens/computer.png diff --git a/plugins/DevicesDetection/images/screens/console.gif b/plugins/DevicesDetection/images/screens/console.gif Binary files differnew file mode 100644 index 0000000000..7957a9106a --- /dev/null +++ b/plugins/DevicesDetection/images/screens/console.gif diff --git a/plugins/DevicesDetection/images/screens/dual.gif b/plugins/DevicesDetection/images/screens/dual.gif Binary files differnew file mode 100644 index 0000000000..a8cb8b2963 --- /dev/null +++ b/plugins/DevicesDetection/images/screens/dual.gif diff --git a/plugins/DevicesDetection/images/screens/mobile.gif b/plugins/DevicesDetection/images/screens/mobile.gif Binary files differnew file mode 100644 index 0000000000..814642933f --- /dev/null +++ b/plugins/DevicesDetection/images/screens/mobile.gif diff --git a/plugins/DevicesDetection/images/screens/normal.gif b/plugins/DevicesDetection/images/screens/normal.gif Binary files differnew file mode 100644 index 0000000000..afe97e9d9f --- /dev/null +++ b/plugins/DevicesDetection/images/screens/normal.gif diff --git a/plugins/DevicesDetection/images/screens/smartphone.png b/plugins/DevicesDetection/images/screens/smartphone.png Binary files differnew file mode 100644 index 0000000000..c5bfb31ca4 --- /dev/null +++ b/plugins/DevicesDetection/images/screens/smartphone.png diff --git a/plugins/DevicesDetection/images/screens/tablet.png b/plugins/DevicesDetection/images/screens/tablet.png Binary files differnew file mode 100644 index 0000000000..e6ac30bdd8 --- /dev/null +++ b/plugins/DevicesDetection/images/screens/tablet.png diff --git a/plugins/DevicesDetection/images/screens/tv.png b/plugins/DevicesDetection/images/screens/tv.png Binary files differnew file mode 100644 index 0000000000..fb6db07cf4 --- /dev/null +++ b/plugins/DevicesDetection/images/screens/tv.png diff --git a/plugins/DevicesDetection/images/screens/unknown.gif b/plugins/DevicesDetection/images/screens/unknown.gif Binary files differnew file mode 100644 index 0000000000..2c44083422 --- /dev/null +++ b/plugins/DevicesDetection/images/screens/unknown.gif diff --git a/plugins/DevicesDetection/images/screens/wide.gif b/plugins/DevicesDetection/images/screens/wide.gif Binary files differnew file mode 100644 index 0000000000..1b09fc529b --- /dev/null +++ b/plugins/DevicesDetection/images/screens/wide.gif diff --git a/plugins/DevicesDetection/lang/en.php b/plugins/DevicesDetection/lang/en.php new file mode 100644 index 0000000000..93d4504957 --- /dev/null +++ b/plugins/DevicesDetection/lang/en.php @@ -0,0 +1,29 @@ +<?php + +$translations = array( + "DevicesDetection_description" => "This plugin provides extended information about mobile devices, such as Brand (manufacturer), Model (device version), better Device type detection (tv, consoles, smart phones, desktop, etc) and more. This plugin adds a new report in 'Visitors > Devices'.", + "DevicesDetection_submenu" => "Devices", + 'DevicesDetection_DevicesDetection' => "Visitor Devices", + // DataTable label translations for reports + "DevicesDetection_dataTableLabelBrands" => "Brand", + "DevicesDetection_dataTableLabelTypes" => "Type", + "DevicesDetection_dataTableLabelModels" => "Model", + "DevicesDetection_dataTableLabelSystemFamily" => "Operating System family", + "DevicesDetection_dataTableLabelSystemVersion" => "Operating System version", + "DevicesDetection_dataTableLabelBrowserFamily" => "Browser family", + "DevicesDetection_dataTableLabelBrowserVersion" => "Browser version", + // Title translations for reports + "DevicesDetection_DeviceType" => "Device types report", + "DevicesDetection_DeviceBrand" => "Device manufacturers report", + "DevicesDetection_DeviceModel" => "Device model report", + 'DeviceDetection_OperatingSystemVersions' => "Operating System versions", + 'DeviceDetection_OperatingSystemFamilies' => "Operating System families", + 'DevicesDetection_BrowsersFamily' => 'Browsers families', + 'DevicesDetection_BrowserVersions' => 'Browser versions', + // Evolution graph title translations + "DevicesDetection_DeviceType" => "Device type", + 'DevicesDetection_DeviceBrand' => 'Device brand', + 'DevicesDetection_DeviceModel' => 'Device model', +// 'DevicesDetection_OS' => 'Device operating system', +// 'DevicesDetection_Browser' => 'Device browser', +); diff --git a/plugins/DevicesDetection/templates/index.tpl b/plugins/DevicesDetection/templates/index.tpl new file mode 100644 index 0000000000..74e6c0b116 --- /dev/null +++ b/plugins/DevicesDetection/templates/index.tpl @@ -0,0 +1,15 @@ +<div id='leftcolumn'> + <h2>{"DevicesDetection_DeviceType"|Piwik_Translate}</h2> + {$deviceTypes} + <h2>{"DevicesDetection_DeviceBrand"|Piwik_Translate}</h2> + {$deviceBrands} + <h2>{"DevicesDetection_DeviceModel"|Piwik_Translate}</h2> + {$deviceModels} +</div> + +<div id='rightcolumn'> + <h2>{"DeviceDetection_OperatingSystemFamilies"|Piwik_Translate}</h2> + {$osReport} + <h2>{"DevicesDetection_BrowsersFamily"|Piwik_Translate}</h2> + {$browserReport} +</div> diff --git a/plugins/ExampleUI/API.php b/plugins/ExampleUI/API.php index 91e29806be..697844a589 100644 --- a/plugins/ExampleUI/API.php +++ b/plugins/ExampleUI/API.php @@ -50,11 +50,7 @@ class Piwik_ExampleUI_API $value = array('server1' => $server1, 'server2' => $server2); $temperatures[$subPeriod->getLocalizedShortString()] = $value; } - - // convert this array to a DataTable object - $dataTable = new Piwik_DataTable(); - $dataTable->addRowsFromArrayWithIndexLabel($temperatures); - return $dataTable; + return Piwik_DataTable::makeFromIndexedArray($temperatures); } // we generate an array of random server temperatures @@ -71,10 +67,7 @@ class Piwik_ExampleUI_API $temperatures[$xAxisLabel] = $temperatureValues[$i]; } - // convert this array to a DataTable object - $dataTable = new Piwik_DataTable(); - $dataTable->addRowsFromArrayWithIndexLabel($temperatures); - return $dataTable; + return Piwik_DataTable::makeFromIndexedArray($temperatures); } public function getPlanetRatios() @@ -90,9 +83,7 @@ class Piwik_ExampleUI_API 'Neptune' => 3.883, ); // convert this array to a DataTable object - $dataTable = new Piwik_DataTable(); - $dataTable->addRowsFromArrayWithIndexLabel($planetRatios); - return $dataTable; + return Piwik_DataTable::makeFromIndexedArray($planetRatios); } public function getPlanetRatiosWithLogos() diff --git a/plugins/Feedback/Feedback.php b/plugins/Feedback/Feedback.php index bb43437905..d514245194 100644 --- a/plugins/Feedback/Feedback.php +++ b/plugins/Feedback/Feedback.php @@ -38,7 +38,7 @@ class Piwik_Feedback extends Piwik_Plugin { Piwik_AddTopMenu( 'General_GiveUsYourFeedback', - array('module' => 'Feedback', 'action' => 'index'), + array('module' => 'Feedback', 'action' => 'index', 'segment' => false), true, $order = 20, $isHTML = false, diff --git a/plugins/Feedback/javascripts/feedback.js b/plugins/Feedback/javascripts/feedback.js index 5f7112cf8f..f99464c5bd 100644 --- a/plugins/Feedback/javascripts/feedback.js +++ b/plugins/Feedback/javascripts/feedback.js @@ -10,7 +10,7 @@ $(function () { if (feedback.size()) { var fbDiv = $('<div id="feedback-dialog"></div>').appendTo('body'); - $('a#topmenu-feedback').click(function () { + feedback.click(function () { if (fbDiv.html() == '') { fbDiv.html('<div id="feedback-loading"><img alt="" src="plugins/Zeitgeist/images/loading-blue.gif"> ' + _pk_translate('General_Loading_js') + '</div>'); } diff --git a/plugins/Feedback/templates/index.twig b/plugins/Feedback/templates/index.twig index 7bd23e21b5..ab26930b98 100644 --- a/plugins/Feedback/templates/index.twig +++ b/plugins/Feedback/templates/index.twig @@ -13,7 +13,7 @@ }); $('#feedback-form-submit').click(function () { - var feedback = $('#feedback-form form'); + var feedback = $('#feedback-form').find('form'); $('#feedback-form').hide(); $.post(feedback.attr('action'), feedback.serialize(), function (data) { $('#feedback-sent').show().html(data); diff --git a/plugins/Goals/API.php b/plugins/Goals/API.php index aea16ad70e..35563a7ed5 100644 --- a/plugins/Goals/API.php +++ b/plugins/Goals/API.php @@ -206,18 +206,19 @@ class Piwik_Goals_API Piwik::checkUserHasViewAccess($idSite); $recordNameFinal = $recordName; if ($abandonedCarts) { - $recordNameFinal = Piwik_Goals::getItemRecordNameAbandonedCart($recordName); + $recordNameFinal = Piwik_Goals_Archiver::getItemRecordNameAbandonedCart($recordName); } $archive = Piwik_Archive::build($idSite, $period, $date); $dataTable = $archive->getDataTable($recordNameFinal); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE)); + + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_ECOMMERCE_ITEM_REVENUE)); $dataTable->queueFilter('ReplaceColumnNames'); $dataTable->queueFilter('ReplaceSummaryRowLabel'); $ordersColumn = 'orders'; if ($abandonedCarts) { $ordersColumn = 'abandoned_carts'; - $dataTable->renameColumn(Piwik_Archive::INDEX_ECOMMERCE_ORDERS, $ordersColumn); + $dataTable->renameColumn(Piwik_Metrics::INDEX_ECOMMERCE_ORDERS, $ordersColumn); } // Average price = sum product revenue / quantity @@ -285,7 +286,7 @@ class Piwik_Goals_API } return; } - $rowNotDefined = $dataTable->getRowFromLabel(Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED); + $rowNotDefined = $dataTable->getRowFromLabel(Piwik_CustomVariables_Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED); if ($rowNotDefined) { $rowNotDefined->setColumn('label', $notDefinedStringPretty); } @@ -303,15 +304,15 @@ class Piwik_Goals_API // If there is not already a 'sum price' for this product $rowFound = $dataTable->getRowFromLabel($rowView->getColumn('label')); $price = $rowFound - ? $rowFound->getColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE) + ? $rowFound->getColumn(Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE) : false; if (empty($price)) { // If a price was tracked on the product page - if ($rowView->getColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED)) { - $rowView->renameColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, 'avg_price'); + if ($rowView->getColumn(Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED)) { + $rowView->renameColumn(Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, 'avg_price'); } } - $rowView->deleteColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED); + $rowView->deleteColumn(Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED); } $dataTable->addDataTable($ecommerceViews); @@ -337,17 +338,17 @@ class Piwik_Goals_API * their integer equivalents. * * Checks for the following values: - * Piwik_Archive::LABEL_ECOMMERCE_ORDER - * Piwik_Archive::LABEL_ECOMMERCE_CART + * Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER + * Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART * * @param string|int $idGoal The goal id as an integer or a special string. * @return int The numeric goal id. */ protected static function convertSpecialGoalIds($idGoal) { - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { return Piwik_Tracker_GoalManager::IDGOAL_ORDER; - } else if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_CART) { + } else if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { return Piwik_Tracker_GoalManager::IDGOAL_CART; } else { return $idGoal; @@ -376,12 +377,12 @@ class Piwik_Goals_API if (empty($columns)) { $columns = Piwik_Goals::getGoalColumns($idGoal); - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { $columns[] = 'avg_order_revenue'; } } if (in_array('avg_order_revenue', $columns) - && $idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER + && $idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER ) { $columns[] = 'nb_conversions'; $columns[] = 'revenue'; @@ -389,7 +390,7 @@ class Piwik_Goals_API } $columnsToSelect = array(); foreach ($columns as &$columnName) { - $columnsToSelect[] = Piwik_Goals::getRecordName($columnName, $idGoal); + $columnsToSelect[] = Piwik_Goals_Archiver::getRecordName($columnName, $idGoal); } $dataTable = $archive->getDataTableFromNumeric($columnsToSelect); @@ -397,7 +398,7 @@ class Piwik_Goals_API foreach ($columnsToSelect as $id => $oldName) { $dataTable->renameColumn($oldName, $columns[$id]); } - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { if ($dataTable instanceof Piwik_DataTable_Array) { foreach ($dataTable->getArray() as $row) { $this->enrichTable($row); @@ -428,7 +429,7 @@ class Piwik_Goals_API { Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - $dataTable = $archive->getNumeric($toFetch); + $dataTable = $archive->getDataTableFromNumeric($toFetch); return $dataTable; } @@ -437,7 +438,7 @@ class Piwik_Goals_API */ public function getConversions($idSite, $period, $date, $segment = false, $idGoal = false) { - return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('nb_conversions', $idGoal)); + return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals_Archiver::getRecordName('nb_conversions', $idGoal)); } /** @@ -445,7 +446,7 @@ class Piwik_Goals_API */ public function getNbVisitsConverted($idSite, $period, $date, $segment = false, $idGoal = false) { - return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('nb_visits_converted', $idGoal)); + return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals_Archiver::getRecordName('nb_visits_converted', $idGoal)); } /** @@ -453,7 +454,7 @@ class Piwik_Goals_API */ public function getConversionRate($idSite, $period, $date, $segment = false, $idGoal = false) { - return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('conversion_rate', $idGoal)); + return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals_Archiver::getRecordName('conversion_rate', $idGoal)); } /** @@ -461,7 +462,7 @@ class Piwik_Goals_API */ public function getRevenue($idSite, $period, $date, $segment = false, $idGoal = false) { - return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('revenue', $idGoal)); + return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals_Archiver::getRecordName('revenue', $idGoal)); } /** @@ -487,7 +488,7 @@ class Piwik_Goals_API $realGoalId = $idGoal != true ? false : self::convertSpecialGoalIds($idGoal); // get the data table - $dataTable = $archive->getDataTable(Piwik_Goals::getRecordName($recordName, $realGoalId), $idSubtable = null); + $dataTable = $archive->getDataTable(Piwik_Goals_Archiver::getRecordName($recordName, $realGoalId), $idSubtable = null); $dataTable->queueFilter('ReplaceColumnNames'); return $dataTable; @@ -507,7 +508,7 @@ class Piwik_Goals_API public function getDaysToConversion($idSite, $period, $date, $segment = false, $idGoal = false) { $dataTable = $this->getGoalSpecificDataTable( - Piwik_Goals::DAYS_UNTIL_CONV_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal); + Piwik_Goals_Archiver::DAYS_UNTIL_CONV_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal); $dataTable->queueFilter('Sort', array('label', 'asc', true)); $dataTable->queueFilter( @@ -530,7 +531,7 @@ class Piwik_Goals_API public function getVisitsUntilConversion($idSite, $period, $date, $segment = false, $idGoal = false) { $dataTable = $this->getGoalSpecificDataTable( - Piwik_Goals::VISITS_UNTIL_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal); + Piwik_Goals_Archiver::VISITS_UNTIL_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal); $dataTable->queueFilter('Sort', array('label', 'asc', true)); $dataTable->queueFilter( diff --git a/plugins/Goals/Archiver.php b/plugins/Goals/Archiver.php new file mode 100644 index 0000000000..425a7050b9 --- /dev/null +++ b/plugins/Goals/Archiver.php @@ -0,0 +1,424 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_Goals + */ + +class Piwik_Goals_Archiver extends Piwik_PluginsArchiver +{ + const VISITS_UNTIL_RECORD_NAME = 'visits_until_conv'; + const DAYS_UNTIL_CONV_RECORD_NAME = 'days_until_conv'; + const ITEMS_SKU_RECORD_NAME = 'Goals_ItemsSku'; + const ITEMS_NAME_RECORD_NAME = 'Goals_ItemsName'; + const ITEMS_CATEGORY_RECORD_NAME = 'Goals_ItemsCategory'; + const SKU_FIELD = 'idaction_sku'; + const NAME_FIELD = 'idaction_name'; + const CATEGORY_FIELD = 'idaction_category'; + const CATEGORY2_FIELD = 'idaction_category2'; + const CATEGORY3_FIELD = 'idaction_category3'; + const CATEGORY4_FIELD = 'idaction_category4'; + const CATEGORY5_FIELD = 'idaction_category5'; + const NO_LABEL = ':'; + const LOG_CONVERSION_TABLE = 'log_conversion'; + const VISITS_COUNT_FIELD = 'visitor_count_visits'; + const DAYS_SINCE_FIRST_VISIT_FIELD = 'visitor_days_since_first'; + /** + * This array stores the ranges to use when displaying the 'visits to conversion' report + */ + public static $visitCountRanges = array( + array(1, 1), + array(2, 2), + array(3, 3), + array(4, 4), + array(5, 5), + array(6, 6), + array(7, 7), + array(8, 8), + array(9, 14), + array(15, 25), + array(26, 50), + array(51, 100), + array(100) + ); + /** + * This array stores the ranges to use when displaying the 'days to conversion' report + */ + public static $daysToConvRanges = array( + array(0, 0), + array(1, 1), + array(2, 2), + array(3, 3), + array(4, 4), + array(5, 5), + array(6, 6), + array(7, 7), + array(8, 14), + array(15, 30), + array(31, 60), + array(61, 120), + array(121, 364), + array(364) + ); + protected $dimensionRecord = array( + self::SKU_FIELD => self::ITEMS_SKU_RECORD_NAME, + self::NAME_FIELD => self::ITEMS_NAME_RECORD_NAME, + self::CATEGORY_FIELD => self::ITEMS_CATEGORY_RECORD_NAME + ); + + /** + * Array containing one DataArray for each Ecommerce items dimension (name/sku/category abandoned carts and orders) + * @var array + */ + protected $itemReports = array(); + + public function archiveDay() + { + $this->archiveGeneralGoalMetrics(); + $this->archiveEcommerceItems(); + } + + protected function archiveGeneralGoalMetrics() + { + $prefixes = array( + self::VISITS_UNTIL_RECORD_NAME => 'vcv', + self::DAYS_UNTIL_CONV_RECORD_NAME => 'vdsf', + ); + $aggregatesMetadata = array( + array(self::VISITS_COUNT_FIELD, self::$visitCountRanges, self::LOG_CONVERSION_TABLE, $prefixes[self::VISITS_UNTIL_RECORD_NAME]), + array(self::DAYS_SINCE_FIRST_VISIT_FIELD, self::$daysToConvRanges, self::LOG_CONVERSION_TABLE, $prefixes[self::DAYS_UNTIL_CONV_RECORD_NAME]), + ); + $selects = array(); + foreach ($aggregatesMetadata as $aggregateMetadata) { + $selects = array_merge($selects, Piwik_DataAccess_LogAggregator::getSelectsFromRangedColumn($aggregateMetadata)); + } + + $query = $this->getLogAggregator()->queryConversionsByDimension(array(), false, $selects); + if ($query === false) { + return; + } + + $totalConversions = $totalRevenue = 0; + $goals = new Piwik_DataArray(); + $visitsToConversions = $daysToConversions = array(); + + $conversionMetrics = $this->getLogAggregator()->getConversionsMetricFields(); + while ($row = $query->fetch()) { + $idGoal = $row['idgoal']; + unset($row['idgoal']); + unset($row['label']); + + $values = array(); + foreach($conversionMetrics as $field => $statement) { + $values[$field] = $row[$field]; + } + $goals->sumMetrics($idGoal, $values); + + if (empty($visitsToConversions[$idGoal])) { + $visitsToConversions[$idGoal] = new Piwik_DataTable(); + } + $array = Piwik_DataAccess_LogAggregator::makeArrayOneColumn($row, Piwik_Metrics::INDEX_NB_CONVERSIONS, $prefixes[self::VISITS_UNTIL_RECORD_NAME]); + $visitsToConversions[$idGoal]->addDataTable(Piwik_DataTable::makeFromIndexedArray($array)); + + if (empty($daysToConversions[$idGoal])) { + $daysToConversions[$idGoal] = new Piwik_DataTable(); + } + $array = Piwik_DataAccess_LogAggregator::makeArrayOneColumn($row, Piwik_Metrics::INDEX_NB_CONVERSIONS, $prefixes[self::DAYS_UNTIL_CONV_RECORD_NAME]); + $daysToConversions[$idGoal]->addDataTable(Piwik_DataTable::makeFromIndexedArray($array)); + + // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits + // since it is a "negative conversion" + if ($idGoal != Piwik_Tracker_GoalManager::IDGOAL_CART) { + $totalConversions += $row[Piwik_Metrics::INDEX_GOAL_NB_CONVERSIONS]; + $totalRevenue += $row[Piwik_Metrics::INDEX_GOAL_REVENUE]; + } + } + + // Stats by goal, for all visitors + $numericRecords = $this->getConversionsNumericMetrics($goals); + $this->getProcessor()->insertNumericRecords($numericRecords); + + $this->insertReports(self::VISITS_UNTIL_RECORD_NAME, $visitsToConversions); + $this->insertReports(self::DAYS_UNTIL_CONV_RECORD_NAME, $daysToConversions); + + // Stats for all goals + $nbConvertedVisits = $this->getProcessor()->getNumberOfVisitsConverted(); + $metrics = array( + self::getRecordName('conversion_rate') => $this->getConversionRate($nbConvertedVisits), + self::getRecordName('nb_conversions') => $totalConversions, + self::getRecordName('nb_visits_converted') => $nbConvertedVisits, + self::getRecordName('revenue') => $totalRevenue, + ); + $this->getProcessor()->insertNumericRecords($metrics); + } + + protected function getConversionsNumericMetrics(Piwik_DataArray $goals) + { + $numericRecords = array(); + $goals = $goals->getDataArray(); + foreach ($goals as $idGoal => $array) { + foreach ($array as $metricId => $value) { + $metricName = Piwik_Metrics::$mappingFromIdToNameGoal[$metricId]; + $recordName = self::getRecordName($metricName, $idGoal); + $numericRecords[$recordName] = $value; + } + if(!empty($array[Piwik_Metrics::INDEX_GOAL_NB_VISITS_CONVERTED])) { + $conversion_rate = $this->getConversionRate($array[Piwik_Metrics::INDEX_GOAL_NB_VISITS_CONVERTED]); + $recordName = self::getRecordName('conversion_rate', $idGoal); + $numericRecords[$recordName] = $conversion_rate; + } + } + return $numericRecords; + } + + /** + * @param string $recordName 'nb_conversions' + * @param int|bool $idGoal idGoal to return the metrics for, or false to return overall + * @return string Archive record name + */ + static public function getRecordName($recordName, $idGoal = false) + { + $idGoalStr = ''; + if ($idGoal !== false) { + $idGoalStr = $idGoal . "_"; + } + return 'Goal_' . $idGoalStr . $recordName; + } + + protected function getConversionRate($count) + { + $visits = $this->getProcessor()->getNumberOfVisits(); + return round(100 * $count / $visits, Piwik_Tracker_GoalManager::REVENUE_PRECISION); + } + + protected function insertReports($recordName, $visitsToConversions) + { + foreach ($visitsToConversions as $idGoal => $table) { + $record = self::getRecordName($recordName, $idGoal); + $this->getProcessor()->insertBlobRecord($record, $table->getSerialized()); + } + $overviewTable = $this->getOverviewFromGoalTables($visitsToConversions); + $this->getProcessor()->insertBlobRecord(self::getRecordName($recordName), $overviewTable->getSerialized()); + } + + protected function getOverviewFromGoalTables($tableByGoal) + { + $overview = new Piwik_DataTable(); + foreach ($tableByGoal as $idGoal => $table) { + if ($this->isStandardGoal($idGoal)) { + $overview->addDataTable($table); + } + } + return $overview; + } + + protected function isStandardGoal($idGoal) + { + return !in_array($idGoal, $this->getEcommerceIdGoals()); + } + + protected function archiveEcommerceItems() + { + if (!$this->shouldArchiveEcommerceItems()) { + return false; + } + $this->initItemReports(); + foreach ($this->getItemsDimensions() as $dimension) { + $query = $this->getLogAggregator()->queryEcommerceItems($dimension); + if ($query == false) { + continue; + } + $this->aggregateFromEcommerceItems($query, $dimension); + } + $this->recordItemReports(); + } + + protected function initItemReports() + { + foreach ($this->getEcommerceIdGoals() as $ecommerceType) { + foreach ($this->dimensionRecord as $dimension => $record) { + $this->itemReports[$dimension][$ecommerceType] = new Piwik_DataArray(); + } + } + } + + protected function recordItemReports() + { + /** @var Piwik_DataArray $array */ + foreach ($this->itemReports as $dimension => $itemAggregatesByType) { + foreach ($itemAggregatesByType as $ecommerceType => $itemAggregate) { + $recordName = $this->dimensionRecord[$dimension]; + if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) { + $recordName = self::getItemRecordNameAbandonedCart($recordName); + } + $table = $this->getProcessor()->getDataTableFromDataArray($itemAggregate); + $this->getProcessor()->insertBlobRecord($recordName, $table->getSerialized()); + } + } + } + + protected function shouldArchiveEcommerceItems() + { + // Per item doesn't support segment + // Also, when querying Goal metrics for visitorType==returning, we wouldnt want to trigger an extra request + // event if it did support segment + if (!$this->getProcessor()->getSegment()->isEmpty()) { + return false; + } + return true; + } + + protected function getItemsDimensions() + { + $dimensions = array_keys($this->dimensionRecord); + foreach ($this->getItemExtraCategories() as $category) { + $dimensions[] = $category; + } + return $dimensions; + } + + protected function getItemExtraCategories() + { + return array(self::CATEGORY2_FIELD, self::CATEGORY3_FIELD, self::CATEGORY4_FIELD, self::CATEGORY5_FIELD); + } + + protected function isItemExtraCategory($field) + { + return in_array($field, $this->getItemExtraCategories()); + } + + protected function aggregateFromEcommerceItems($query, $dimension) + { + while ($row = $query->fetch()) { + $ecommerceType = $row['ecommerceType']; + + $label = $this->cleanupRowGetLabel($row, $dimension); + if ($label === false) { + continue; + } + + // Aggregate extra categories in the Item categories array + if ($this->isItemExtraCategory($dimension)) { + $array = $this->itemReports[self::CATEGORY_FIELD][$ecommerceType]; + } else { + $array = $this->itemReports[$dimension][$ecommerceType]; + } + + $this->roundColumnValues($row); + $array->sumMetrics($label, $row); + } + } + + protected function cleanupRowGetLabel(&$row, $currentField) + { + $label = $row['label']; + if (empty($label)) { + // An empty additional category -> skip this iteration + if ($this->isItemExtraCategory($currentField)) { + return false; + } + $label = "Value not defined"; + // Product Name/Category not defined" + if (class_exists('Piwik_CustomVariables')) { + $label = Piwik_CustomVariables_Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED; + } + } + + if ($row['ecommerceType'] == Piwik_Tracker_GoalManager::IDGOAL_CART) { + // abandoned carts are the numner of visits with an abandoned cart + $row[Piwik_Metrics::INDEX_ECOMMERCE_ORDERS] = $row[Piwik_Metrics::INDEX_NB_VISITS]; + } + + unset($row[Piwik_Metrics::INDEX_NB_VISITS]); + unset($row['label']); + unset($row['ecommerceType']); + + return $label; + } + + protected function roundColumnValues(&$row) + { + $columnsToRound = array( + Piwik_Metrics::INDEX_ECOMMERCE_ITEM_REVENUE, + Piwik_Metrics::INDEX_ECOMMERCE_ITEM_QUANTITY, + Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE, + Piwik_Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, + ); + foreach ($columnsToRound as $column) { + if (isset($row[$column]) + && $row[$column] == round($row[$column]) + ) { + $row[$column] = round($row[$column]); + } + } + } + + protected function getEcommerceIdGoals() + { + return array(Piwik_Tracker_GoalManager::IDGOAL_CART, Piwik_Tracker_GoalManager::IDGOAL_ORDER); + } + + static public function getItemRecordNameAbandonedCart($recordName) + { + return $recordName . '_Cart'; + } + + /** + * @param $this->getProcessor() + */ + public function archivePeriod() + { + /* + * Archive Ecommerce Items + */ + if ($this->shouldArchiveEcommerceItems()) { + $dataTableToSum = $this->dimensionRecord; + foreach ($this->dimensionRecord as $recordName) { + $dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName); + } + $this->getProcessor()->aggregateDataTableReports($dataTableToSum); + } + + /* + * Archive General Goal metrics + */ + $goalIdsToSum = Piwik_Tracker_GoalManager::getGoalIds($this->getProcessor()->getSite()->getId()); + + //Ecommerce + $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_ORDER; + $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_CART; //bug here if idgoal=1 + // Overall goal metrics + $goalIdsToSum[] = false; + + $fieldsToSum = array(); + foreach ($goalIdsToSum as $goalId) { + $metricsToSum = Piwik_Goals::getGoalColumns($goalId); + unset($metricsToSum[array_search('conversion_rate', $metricsToSum)]); + foreach ($metricsToSum as $metricName) { + $fieldsToSum[] = self::getRecordName($metricName, $goalId); + } + } + $records = $this->getProcessor()->aggregateNumericMetrics($fieldsToSum); + + // also recording conversion_rate for each goal + foreach ($goalIdsToSum as $goalId) { + $nb_conversions = $records[self::getRecordName('nb_visits_converted', $goalId)]; + $conversion_rate = $this->getConversionRate($nb_conversions); + $this->getProcessor()->insertNumericRecord(self::getRecordName('conversion_rate', $goalId), $conversion_rate); + + // sum up the visits to conversion data table & the days to conversion data table + $this->getProcessor()->aggregateDataTableReports(array( + self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId), + self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId))); + } + + // sum up goal overview reports + $this->getProcessor()->aggregateDataTableReports(array( + self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), + self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME))); + } +}
\ No newline at end of file diff --git a/plugins/Goals/Controller.php b/plugins/Goals/Controller.php index 47752a7a4d..62e76be000 100644 --- a/plugins/Goals/Controller.php +++ b/plugins/Goals/Controller.php @@ -33,6 +33,14 @@ class Piwik_Goals_Controller extends Piwik_Controller private function formatConversionRate($conversionRate) { + if ($conversionRate instanceof Piwik_DataTable) { + if ($conversionRate->getRowsCount() == 0) { + $conversionRate = 0; + } else { + $columns = $conversionRate->getFirstRow()->getColumns(); + $conversionRate = (float)reset($columns); + } + } return sprintf('%.' . self::CONVERSION_RATE_PRECISION . 'f%%', $conversionRate); } @@ -69,7 +77,7 @@ class Piwik_Goals_Controller extends Piwik_Controller throw new Exception("Ecommerce Tracking requires that the plugin Custom Variables is enabled. Please enable the plugin CustomVariables (or ask your admin)."); } - $view = $this->getGoalReportView($idGoal = Piwik_Archive::LABEL_ECOMMERCE_ORDER); + $view = $this->getGoalReportView($idGoal = Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER); $view->displayFullReport = true; echo $view->render(); } @@ -157,7 +165,7 @@ class Piwik_Goals_Controller extends Piwik_Controller protected function getGoalReportView($idGoal = false) { $view = new Piwik_View('@Goals/single_goal'); - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { $goalDefinition['name'] = Piwik_Translate('Goals_Ecommerce'); $goalDefinition['allow_multiple'] = true; $ecommerce = $view->ecommerce = true; @@ -172,8 +180,8 @@ class Piwik_Goals_Controller extends Piwik_Controller foreach ($goal as $name => $value) { $view->$name = $value; } - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { - $goal = $this->getMetricsForGoal(Piwik_Archive::LABEL_ECOMMERCE_CART); + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { + $goal = $this->getMetricsForGoal(Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART); foreach ($goal as $name => $value) { $name = 'cart_' . $name; $view->$name = $value; @@ -201,7 +209,18 @@ class Piwik_Goals_Controller extends Piwik_Controller public function index() { $view = $this->getOverviewView(); - $view->goalsJSON = Piwik_Common::json_encode($this->goals); + + // unsanitize goal names and other text data (not done in API so as not to break + // any other code/cause security issues) + $goals = $this->goals; + foreach ($goals as &$goal) { + $goal['name'] = Piwik_Common::unsanitizeInputValue($goal['name']); + if (isset($goal['pattern'])) { + $goal['pattern'] = Piwik_Common::unsanitizeInputValue($goal['pattern']); + } + } + $view->goalsJSON = Piwik_Common::json_encode($goals); + $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite); $view->ecommerceEnabled = $this->site->isEcommerceEnabled(); $view->displayFullReport = true; @@ -294,9 +313,9 @@ class Piwik_Goals_Controller extends Piwik_Controller $view->setParametersToModify(array('idGoal' => $idGoal)); $nameToLabel = $this->goalColumnNameToLabel; - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { $nameToLabel['nb_conversions'] = 'General_EcommerceOrders'; - } elseif ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_CART) { + } elseif ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { $nameToLabel['nb_conversions'] = Piwik_Translate('General_VisitsWith', Piwik_Translate('Goals_AbandonedCart')); $nameToLabel['conversion_rate'] = $nameToLabel['nb_conversions']; $nameToLabel['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_ColumnRevenue')); @@ -350,7 +369,7 @@ class Piwik_Goals_Controller extends Piwik_Controller $keywordNotDefinedString = ''; if (Piwik_PluginsManager::getInstance()->isPluginActivated('Referers')) { - $keywordNotDefinedString = Piwik_Referers::getKeywordNotDefinedString(); + $keywordNotDefinedString = Piwik_Referers_API::getKeywordNotDefinedString(); $topDimensionsToLoad += array( 'keyword' => 'Referers.getKeywords', 'website' => 'Referers.getWebsites', @@ -414,7 +433,7 @@ class Piwik_Goals_Controller extends Piwik_Controller 'urlSparklineConversionRate' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => $idGoal)), 'urlSparklineRevenue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => $idGoal)), ); - if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) { + if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { $items = $dataRow->getColumn('items'); $aov = $dataRow->getColumn('avg_order_revenue'); $return = array_merge($return, array( @@ -447,7 +466,7 @@ class Piwik_Goals_Controller extends Piwik_Controller $view->setSortedColumn('label', 'asc'); $view->setColumnTranslation('label', Piwik_Translate('Goals_VisitsUntilConv')); $view->setColumnTranslation('nb_conversions', Piwik_Translate('Goals_ColumnConversions')); - $view->setLimit(count(Piwik_Goals::$visitCountRanges)); + $view->setLimit(count(Piwik_Goals_Archiver::$visitCountRanges)); $view->disableOffsetInformationAndPaginationControls(); $view->disableShowAllViewsIcons(); return $this->renderView($view, $fetch); @@ -469,7 +488,7 @@ class Piwik_Goals_Controller extends Piwik_Controller $view->setColumnTranslation('label', Piwik_Translate('Goals_DaysToConv')); $view->setColumnTranslation('nb_conversions', Piwik_Translate('Goals_ColumnConversions')); $view->disableShowAllViewsIcons(); - $view->setLimit(count(Piwik_Goals::$daysToConvRanges)); + $view->setLimit(count(Piwik_Goals_Archiver::$daysToConvRanges)); $view->disableOffsetInformationAndPaginationControls(); return $this->renderView($view, $fetch); } @@ -542,7 +561,7 @@ class Piwik_ViewDataTable_HtmlTable_EcommerceOrder extends Piwik_ViewDataTable_H { protected function getViewDataTableId() { - return Piwik_Archive::LABEL_ECOMMERCE_ORDER; + return Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER; } } @@ -550,6 +569,6 @@ class Piwik_ViewDataTable_HtmlTable_EcommerceAbandonedCart extends Piwik_ViewDat { protected function getViewDataTableId() { - return Piwik_Archive::LABEL_ECOMMERCE_CART; + return Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART; } } diff --git a/plugins/Goals/Goals.php b/plugins/Goals/Goals.php index dd8a138bc4..2ae78f60cc 100644 --- a/plugins/Goals/Goals.php +++ b/plugins/Goals/Goals.php @@ -15,49 +15,51 @@ */ class Piwik_Goals extends Piwik_Plugin { - const VISITS_UNTIL_RECORD_NAME = 'visits_until_conv'; - const DAYS_UNTIL_CONV_RECORD_NAME = 'days_until_conv'; - - /** - * This array stores the ranges to use when displaying the 'visits to conversion' - * report. - */ - public static $visitCountRanges = array( - array(1, 1), - array(2, 2), - array(3, 3), - array(4, 4), - array(5, 5), - array(6, 6), - array(7, 7), - array(8, 8), - array(9, 14), - array(15, 25), - array(26, 50), - array(51, 100), - array(100) + protected $ecommerceReports = array( + array('Goals_ProductSKU', 'Goals', 'getItemsSku'), + array('Goals_ProductName', 'Goals', 'getItemsName'), + array('Goals_ProductCategory', 'Goals', 'getItemsCategory') ); - /** - * This array stores the ranges to use when displaying the 'days to conversion' - * report. - */ - public static $daysToConvRanges = array( - array(0, 0), - array(1, 1), - array(2, 2), - array(3, 3), - array(4, 4), - array(5, 5), - array(6, 6), - array(7, 7), - array(8, 14), - array(15, 30), - array(31, 60), - array(61, 120), - array(121, 364), - array(364) - ); + static public function getReportsWithGoalMetrics() + { + $dimensions = array(); + Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $dimensions); + $dimensionsByGroup = array(); + foreach ($dimensions as $dimension) { + $group = $dimension['category']; + unset($dimension['category']); + $dimensionsByGroup[$group][] = $dimension; + } + return $dimensionsByGroup; + } + + static public function getGoalColumns($idGoal) + { + $columns = array( + 'nb_conversions', + 'nb_visits_converted', + 'conversion_rate', + 'revenue', + ); + if ($idGoal === false) { + return $columns; + } + // Orders + if ($idGoal === Piwik_Tracker_GoalManager::IDGOAL_ORDER) { + $columns = array_merge($columns, array( + 'revenue_subtotal', + 'revenue_tax', + 'revenue_shipping', + 'revenue_discount', + )); + } + // Abandoned carts & orders + if ($idGoal <= Piwik_Tracker_GoalManager::IDGOAL_ORDER) { + $columns[] = 'items'; + } + return $columns; + } public function getInformation() { @@ -237,7 +239,7 @@ class Piwik_Goals extends Piwik_Plugin 'name' => Piwik_Translate('General_EcommerceOrders'), 'module' => 'Goals', 'action' => 'get', - 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), + 'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), 'metrics' => $ecommerceMetrics, 'processedMetrics' => false, 'order' => 10 @@ -250,7 +252,7 @@ class Piwik_Goals extends Piwik_Plugin 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'), 'constantRowsCount' => true, 'metrics' => $conversionReportMetrics, - 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), + 'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), 'order' => 11 ); $reports[] = array( @@ -261,7 +263,7 @@ class Piwik_Goals extends Piwik_Plugin 'dimension' => Piwik_Translate('Goals_DaysToConv'), 'constantRowsCount' => true, 'metrics' => $conversionReportMetrics, - 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), + 'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), 'order' => 12 ); @@ -278,7 +280,7 @@ class Piwik_Goals extends Piwik_Plugin 'name' => Piwik_Translate('General_AbandonedCarts'), 'module' => 'Goals', 'action' => 'get', - 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART), + 'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART), 'metrics' => $abandonedCartMetrics, 'processedMetrics' => false, 'order' => 15 @@ -292,7 +294,7 @@ class Piwik_Goals extends Piwik_Plugin 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'), 'constantRowsCount' => true, 'metrics' => $conversionReportMetrics, - 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART), + 'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART), 'order' => 20 ); $reports[] = array( @@ -303,7 +305,7 @@ class Piwik_Goals extends Piwik_Plugin 'dimension' => Piwik_Translate('Goals_DaysToConv'), 'constantRowsCount' => true, 'metrics' => $conversionReportMetrics, - 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART), + 'parameters' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART), 'order' => 25 ); @@ -348,28 +350,6 @@ class Piwik_Goals extends Piwik_Plugin } - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getSegmentsMetadata($notification) - { - $segments =& $notification->getNotificationObject(); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Visit', - 'name' => 'General_VisitConvertedGoalId', - 'segment' => 'visitConvertedGoalId', - 'sqlSegment' => 'log_conversion.idgoal', - 'acceptedValues' => '1, 2, 3, etc.', - ); - } - - protected $ecommerceReports = array( - array('Goals_ProductSKU', 'Goals', 'getItemsSku'), - array('Goals_ProductName', 'Goals', 'getItemsName'), - array('Goals_ProductCategory', 'Goals', 'getItemsCategory') - ); - static public function getProductReportColumns() { return array( @@ -383,17 +363,44 @@ class Piwik_Goals extends Piwik_Plugin ); } - static public function getReportsWithGoalMetrics() + /** + * This function executes when the 'Goals.getReportsWithGoalMetrics' event fires. It + * adds the 'visits to conversion' report metadata to the list of goal reports so + * this report will be displayed. + * + * @param Piwik_Event_Notification $notification notification object + */ + function getActualReportsWithGoalMetrics($notification) { - $dimensions = array(); - Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $dimensions); - $dimensionsByGroup = array(); - foreach ($dimensions as $dimension) { - $group = $dimension['category']; - unset($dimension['category']); - $dimensionsByGroup[$group][] = $dimension; - } - return $dimensionsByGroup; + $dimensions =& $notification->getNotificationObject(); + $dimensions = array_merge($dimensions, array( + array('category' => Piwik_Translate('General_Visit'), + 'name' => Piwik_Translate('Goals_VisitsUntilConv'), + 'module' => 'Goals', + 'action' => 'getVisitsUntilConversion' + ), + array('category' => Piwik_Translate('General_Visit'), + 'name' => Piwik_Translate('Goals_DaysToConv'), + 'module' => 'Goals', + 'action' => 'getDaysToConversion' + ) + )); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getSegmentsMetadata($notification) + { + $segments =& $notification->getNotificationObject(); + $segments[] = array( + 'type' => 'dimension', + 'category' => Piwik_Translate('General_Visit'), + 'name' => 'General_VisitConvertedGoalId', + 'segment' => 'visitConvertedGoalId', + 'sqlSegment' => 'log_conversion.idgoal', + 'acceptedValues' => '1, 2, 3, etc.', + ); } /** @@ -433,7 +440,7 @@ class Piwik_Goals extends Piwik_Plugin // Ecommerce widgets $site = new Piwik_Site($idSite); if ($site->isEcommerceEnabled()) { - Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceOverview', 'Goals', 'widgetGoalReport', array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER)); + Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceOverview', 'Goals', 'widgetGoalReport', array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER)); Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceLog', 'Goals', 'getEcommerceLog'); foreach ($this->ecommerceReports as $widget) { Piwik_AddWidget('Goals_Ecommerce', $widget[0], $widget[1], $widget[2]); @@ -450,13 +457,6 @@ class Piwik_Goals extends Piwik_Plugin } } - protected function getGoalCategoryName($idSite) - { - $site = new Piwik_Site($idSite); - return $site->isEcommerceEnabled() ? 'Goals_EcommerceAndGoalsMenu' : 'Goals_Goals'; - } - - function addMenus() { $idSite = Piwik_Common::getRequestVar('idSite', null, 'int'); @@ -467,23 +467,23 @@ class Piwik_Goals extends Piwik_Plugin Piwik_AddMenu($mainGoalMenu, '', array( 'module' => 'Goals', 'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'addNewGoal'), - 'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)), + 'idGoal' => ($site->isEcommerceEnabled() ? Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER : null)), true, 25); if ($site->isEcommerceEnabled()) { - Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1); + Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), true, 1); } Piwik_AddMenu($mainGoalMenu, 'Goals_AddNewGoal', array('module' => 'Goals', 'action' => 'addNewGoal')); } else { Piwik_AddMenu($mainGoalMenu, '', array( 'module' => 'Goals', 'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'index'), - 'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)), + 'idGoal' => ($site->isEcommerceEnabled() ? Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER : null)), true, 25); if ($site->isEcommerceEnabled()) { - Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1); + Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER), true, 1); } Piwik_AddMenu($mainGoalMenu, 'Goals_GoalsOverview', array('module' => 'Goals', 'action' => 'index'), true, 2); foreach ($goals as $goal) { @@ -492,111 +492,10 @@ class Piwik_Goals extends Piwik_Plugin } } - /** - * @param string $recordName 'nb_conversions' - * @param int|bool $idGoal idGoal to return the metrics for, or false to return overall - * @return string Archive record name - */ - static public function getRecordName($recordName, $idGoal = false) - { - $idGoalStr = ''; - if ($idGoal !== false) { - $idGoalStr = $idGoal . "_"; - } - return 'Goal_' . $idGoalStr . $recordName; - } - - /** - * Hooks on Period archiving. - * Sums up Goal conversions stats, and processes overall conversion rate - * - * @param Piwik_Event_Notification $notification - * @return void - */ - function archivePeriod($notification) - { - /** - * @var Piwik_ArchiveProcessing - */ - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - /* - * Archive Ecommerce Items - */ - if ($this->shouldArchiveEcommerceItems($archiveProcessing)) { - $dataTableToSum = $this->dimensions; - foreach ($this->dimensions as $recordName) { - $dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName); - } - $archiveProcessing->archiveDataTable($dataTableToSum); - } - - /* - * Archive General Goal metrics - */ - $goalIdsToSum = Piwik_Tracker_GoalManager::getGoalIds($archiveProcessing->idsite); - - //Ecommerce - $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_ORDER; - $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_CART; //bug here if idgoal=1 - // Overall goal metrics - $goalIdsToSum[] = false; - - $fieldsToSum = array(); - foreach ($goalIdsToSum as $goalId) { - $metricsToSum = Piwik_Goals::getGoalColumns($goalId); - unset($metricsToSum[array_search('conversion_rate', $metricsToSum)]); - foreach ($metricsToSum as $metricName) { - $fieldsToSum[] = self::getRecordName($metricName, $goalId); - } - } - $records = $archiveProcessing->archiveNumericValuesSum($fieldsToSum); - - // also recording conversion_rate for each goal - foreach ($goalIdsToSum as $goalId) { - $nb_conversions = $records[self::getRecordName('nb_visits_converted', $goalId)]; - $conversion_rate = $this->getConversionRate($nb_conversions, $archiveProcessing); - $archiveProcessing->insertNumericRecord(self::getRecordName('conversion_rate', $goalId), $conversion_rate); - - // sum up the visits to conversion data table & the days to conversion data table - $archiveProcessing->archiveDataTable(array( - self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId), - self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId))); - } - - // sum up goal overview reports - $archiveProcessing->archiveDataTable(array( - self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), - self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME))); - } - - static public function getGoalColumns($idGoal) + protected function getGoalCategoryName($idSite) { - $columns = array( - 'nb_conversions', - 'nb_visits_converted', - 'conversion_rate', - 'revenue', - ); - if ($idGoal === false) { - return $columns; - } - // Orders - if ($idGoal === Piwik_Tracker_GoalManager::IDGOAL_ORDER) { - $columns = array_merge($columns, array( - 'revenue_subtotal', - 'revenue_tax', - 'revenue_shipping', - 'revenue_discount', - )); - } - // Abandoned carts & orders - if ($idGoal <= Piwik_Tracker_GoalManager::IDGOAL_ORDER) { - $columns[] = 'items'; - } - return $columns; + $site = new Piwik_Site($idSite); + return $site->isEcommerceEnabled() ? 'Goals_EcommerceAndGoalsMenu' : 'Goals_Goals'; } /** @@ -610,260 +509,30 @@ class Piwik_Goals extends Piwik_Plugin function archiveDay($notification) { /** - * @var Piwik_ArchiveProcessing_Day + * @var Piwik_ArchiveProcessor_Day */ - $archiveProcessing = $notification->getNotificationObject(); + $archiveProcessor = $notification->getNotificationObject(); - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->archiveGeneralGoalMetrics($archiveProcessing); - $this->archiveEcommerceItems($archiveProcessing); - } - - /** - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - */ - function archiveGeneralGoalMetrics($archiveProcessing) - { - // extra aggregate selects for the visits to conversion report - $visitToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect( - 'visitor_count_visits', self::$visitCountRanges, 'log_conversion', 'vcv'); - - // extra aggregate selects for the days to conversion report - $daysToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect( - 'visitor_days_since_first', self::$daysToConvRanges, 'log_conversion', 'vdsf'); - - $query = $archiveProcessing->queryConversionsByDimension( - array(), '', array_merge($visitToConvExtraCols, $daysToConvExtraCols)); - - if ($query === false) { - return; + $archiving = new Piwik_Goals_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); } - - $goals = array(); - $visitsToConvReport = array(); - $daysToConvReport = array(); - - // Get a standard empty goal row - $overall = $archiveProcessing->getNewGoalRow($idGoal = 1); - while ($row = $query->fetch()) { - $idgoal = $row['idgoal']; - - if (!isset($goals[$idgoal])) { - $goals[$idgoal] = $archiveProcessing->getNewGoalRow($idgoal); - - $visitsToConvReport[$idgoal] = new Piwik_DataTable(); - $daysToConvReport[$idgoal] = new Piwik_DataTable(); - } - $archiveProcessing->updateGoalStats($row, $goals[$idgoal]); - - // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits - // since it is a "negative conversion" - if ($idgoal != Piwik_Tracker_GoalManager::IDGOAL_CART) { - $archiveProcessing->updateGoalStats($row, $overall); - } - - // map the goal + visit number of a visitor with the # of conversions that happened on that visit - $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vcv'); - $visitsToConvReport[$idgoal]->addDataTable($table); - - // map the goal + day number of a visit with the # of conversion that happened on that day - $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vdsf'); - $daysToConvReport[$idgoal]->addDataTable($table); - } - - // these data tables hold reports for every goal of a site - $visitsToConvOverview = new Piwik_DataTable(); - $daysToConvOverview = new Piwik_DataTable(); - - // Stats by goal, for all visitors - foreach ($goals as $idgoal => $values) { - foreach ($values as $metricId => $value) { - $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId]; - $recordName = self::getRecordName($metricName, $idgoal); - $archiveProcessing->insertNumericRecord($recordName, $value); - } - $conversion_rate = $this->getConversionRate($values[Piwik_Archive::INDEX_GOAL_NB_VISITS_CONVERTED], $archiveProcessing); - $recordName = self::getRecordName('conversion_rate', $idgoal); - $archiveProcessing->insertNumericRecord($recordName, $conversion_rate); - - // if the goal is not a special goal (like ecommerce) add it to the overview report - if ($idgoal !== Piwik_Tracker_GoalManager::IDGOAL_CART && - $idgoal !== Piwik_Tracker_GoalManager::IDGOAL_ORDER - ) { - $visitsToConvOverview->addDataTable($visitsToConvReport[$idgoal]); - $daysToConvOverview->addDataTable($daysToConvReport[$idgoal]); - } - - // visit count until conversion stats - $archiveProcessing->insertBlobRecord( - self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $idgoal), - $visitsToConvReport[$idgoal]->getSerialized()); - - // day count until conversion stats - $archiveProcessing->insertBlobRecord( - self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $idgoal), - $daysToConvReport[$idgoal]->getSerialized()); - } - - // archive overview reports - $archiveProcessing->insertBlobRecord( - self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), $visitsToConvOverview->getSerialized()); - $archiveProcessing->insertBlobRecord( - self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME), $daysToConvOverview->getSerialized()); - - // Stats for all goals - $totalAllGoals = array( - self::getRecordName('conversion_rate') => $this->getConversionRate($archiveProcessing->getNumberOfVisitsConverted(), $archiveProcessing), - self::getRecordName('nb_conversions') => $overall[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS], - self::getRecordName('nb_visits_converted') => $archiveProcessing->getNumberOfVisitsConverted(), - self::getRecordName('revenue') => $overall[Piwik_Archive::INDEX_GOAL_REVENUE], - ); - foreach ($totalAllGoals as $recordName => $value) { - $archiveProcessing->insertNumericRecord($recordName, $value); - } - } - - protected $dimensions = array( - 'idaction_sku' => 'Goals_ItemsSku', - 'idaction_name' => 'Goals_ItemsName', - 'idaction_category' => 'Goals_ItemsCategory' - ); - - protected function shouldArchiveEcommerceItems($archiveProcessing) - { - // Per item doesn't support segment - // Also, when querying Goal metrics for visitorType==returning, we wouldnt want to trigger an extra request - // event if it did support segment - // (if this is implented, we should have shouldProcessReportsForPlugin() support partial archiving based on which metric is requested) - if (!$archiveProcessing->getSegment()->isEmpty()) { - return false; - } - return true; } /** - * @param Piwik_ArchiveProcessing_Day $archiveProcessing + * Hooks on Period archiving. + * Sums up Goal conversions stats, and processes overall conversion rate + * + * @param Piwik_Event_Notification $notification + * @return void */ - function archiveEcommerceItems($archiveProcessing) + function archivePeriod($notification) { - if (!$this->shouldArchiveEcommerceItems($archiveProcessing)) { - return false; - } - $items = array(); - - $dimensionsToQuery = $this->dimensions; - $dimensionsToQuery['idaction_category2'] = 'AdditionalCategory'; - $dimensionsToQuery['idaction_category3'] = 'AdditionalCategory'; - $dimensionsToQuery['idaction_category4'] = 'AdditionalCategory'; - $dimensionsToQuery['idaction_category5'] = 'AdditionalCategory'; - - foreach ($dimensionsToQuery as $dimension => $recordName) { - $query = $archiveProcessing->queryEcommerceItems($dimension); - if ($query == false) { - continue; - } + $archiveProcessor = $notification->getNotificationObject(); - while ($row = $query->fetch()) { - $label = $row['label']; - $ecommerceType = $row['ecommerceType']; - - if (empty($label)) { - // idaction==0 case: - // If we are querying any optional category, we do not include idaction=0 - // Otherwise we over-report in the Product Categories report - if ($recordName == 'AdditionalCategory') { - continue; - } - // Product Name/Category not defined" - if (class_exists('Piwik_CustomVariables')) { - $label = Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED; - } else { - $label = "Value not defined"; - } - } - // For carts, idorder = 0. To count abandoned carts, we must count visits with an abandoned cart - if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) { - $row[Piwik_Archive::INDEX_ECOMMERCE_ORDERS] = $row[Piwik_Archive::INDEX_NB_VISITS]; - } - unset($row[Piwik_Archive::INDEX_NB_VISITS]); - unset($row['label']); - unset($row['ecommerceType']); - - $columnsToRound = array( - Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE, - Piwik_Archive::INDEX_ECOMMERCE_ITEM_QUANTITY, - Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE, - Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, - ); - foreach ($columnsToRound as $column) { - if (isset($row[$column]) - && $row[$column] == round($row[$column]) - ) { - $row[$column] = round($row[$column]); - } - } - $items[$dimension][$ecommerceType][$label] = $row; - } - } - - foreach ($this->dimensions as $dimension => $recordName) { - foreach (array(Piwik_Tracker_GoalManager::IDGOAL_CART, Piwik_Tracker_GoalManager::IDGOAL_ORDER) as $ecommerceType) { - if (!isset($items[$dimension][$ecommerceType])) { - continue; - } - $recordNameInsert = $recordName; - if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) { - $recordNameInsert = self::getItemRecordNameAbandonedCart($recordName); - } - $table = $archiveProcessing->getDataTableFromArray($items[$dimension][$ecommerceType]); - - // For "category" report, we aggregate all 5 category queries into one datatable - if ($dimension == 'idaction_category') { - foreach (array('idaction_category2', 'idaction_category3', 'idaction_category4', 'idaction_category5') as $categoryToSum) { - if (!empty($items[$categoryToSum][$ecommerceType])) { - $tableToSum = $archiveProcessing->getDataTableFromArray($items[$categoryToSum][$ecommerceType]); - $table->addDataTable($tableToSum); - } - } - } - $archiveProcessing->insertBlobRecord($recordNameInsert, $table->getSerialized()); - } + $archiving = new Piwik_Goals_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); } } - - static public function getItemRecordNameAbandonedCart($recordName) - { - return $recordName . '_Cart'; - } - - function getConversionRate($count, $archiveProcessing) - { - $visits = $archiveProcessing->getNumberOfVisits(); - return round(100 * $count / $visits, Piwik_Tracker_GoalManager::REVENUE_PRECISION); - } - - /** - * This function executes when the 'Goals.getReportsWithGoalMetrics' event fires. It - * adds the 'visits to conversion' report metadata to the list of goal reports so - * this report will be displayed. - * - * @param Piwik_Event_Notification $notification notification object - */ - function getActualReportsWithGoalMetrics($notification) - { - $dimensions =& $notification->getNotificationObject(); - $dimensions = array_merge($dimensions, array( - array('category' => Piwik_Translate('General_Visit'), - 'name' => Piwik_Translate('Goals_VisitsUntilConv'), - 'module' => 'Goals', - 'action' => 'getVisitsUntilConversion' - ), - array('category' => Piwik_Translate('General_Visit'), - 'name' => Piwik_Translate('Goals_DaysToConv'), - 'module' => 'Goals', - 'action' => 'getDaysToConversion' - ) - )); - } } diff --git a/plugins/Goals/javascripts/goalsForm.js b/plugins/Goals/javascripts/goalsForm.js index fa1d0beceb..354e061978 100644 --- a/plugins/Goals/javascripts/goalsForm.js +++ b/plugins/Goals/javascripts/goalsForm.js @@ -155,7 +155,7 @@ function bindListGoalEdit() { var goalId = $(this).attr('id'); var goal = piwik.goals[goalId]; - $('#confirm h2').text(sprintf(_pk_translate('Goals_DeleteGoalConfirm_js'), '"' + goal.name + '"')); + $('#confirm').find('h2').text(sprintf(_pk_translate('Goals_DeleteGoalConfirm_js'), '"' + goal.name + '"')); piwikHelper.modalConfirm('#confirm', {yes: function () { ajaxDeleteGoal(goalId); }}); diff --git a/plugins/Goals/templates/overview.twig b/plugins/Goals/templates/overview.twig index 54f3219721..991663194c 100644 --- a/plugins/Goals/templates/overview.twig +++ b/plugins/Goals/templates/overview.twig @@ -33,6 +33,7 @@ {% endfor %} {% if displayFullReport %} + {% if sum_nb_conversions != 0 %} <h2 id='titleGoalsByDimension'> {% if idGoal is defined %} {{ 'Goals_GoalConversionsBy'|translate(goalName) }} @@ -41,6 +42,7 @@ {% endif %} </h2> {{ goalReportsByDimension }} + {% endif %} {% if userCanEditGoals %} {% include "@Goals/add_edit_goal.twig" %} diff --git a/plugins/ImageGraph/API.php b/plugins/ImageGraph/API.php index 916589e683..ddc31e56b3 100644 --- a/plugins/ImageGraph/API.php +++ b/plugins/ImageGraph/API.php @@ -129,7 +129,8 @@ class Piwik_ImageGraph_API $backgroundColor = Piwik_ImageGraph_API::DEFAULT_BACKGROUND_COLOR, $gridColor = Piwik_ImageGraph_API::DEFAULT_GRID_COLOR, $idSubtable = false, - $legendAppendMetric = true + $legendAppendMetric = true, + $segment = false ) { Piwik::checkUserHasViewAccess($idSite); @@ -168,7 +169,7 @@ class Piwik_ImageGraph_API $reportHasDimension = !empty($metadata['dimension']); $constantRowsCount = !empty($metadata['constantRowsCount']); - $isMultiplePeriod = Piwik_Archive::isMultiplePeriod($date, $period); + $isMultiplePeriod = Piwik_Period::isMultiplePeriod($date, $period); if (!$reportHasDimension && !$isMultiplePeriod) { throw new Exception('The graph cannot be drawn for this combination of \'date\' and \'period\' parameters.'); } @@ -296,7 +297,7 @@ class Piwik_ImageGraph_API $apiModule, $apiAction, $labels, - $segment = false, + $segment, $plottedMetric, $languageLoaded, $idGoal, @@ -352,7 +353,7 @@ class Piwik_ImageGraph_API $date, $apiModule, $apiAction, - $segment = false, + $segment, $apiParameters = false, $idGoal, $languageLoaded, diff --git a/plugins/ImageGraph/ImageGraph.php b/plugins/ImageGraph/ImageGraph.php index f149150d23..06e973f462 100644 --- a/plugins/ImageGraph/ImageGraph.php +++ b/plugins/ImageGraph/ImageGraph.php @@ -15,6 +15,11 @@ class Piwik_ImageGraph extends Piwik_Plugin 'Referers_getRefererType', ); + // row evolution support not yet implemented for these APIs + static private $REPORTS_DISABLED_EVOLUTION_GRAPH = array( + 'Referers_getAll', + ); + public function getInformation() { return array( @@ -64,7 +69,7 @@ class Piwik_ImageGraph extends Piwik_Plugin } // need two sets of period & date, one for single period graphs, one for multiple periods graphs - if (Piwik_Archive::isMultiplePeriod($info['date'], $info['period'])) { + if (Piwik_Period::isMultiplePeriod($info['date'], $info['period'])) { $periodForMultiplePeriodGraph = $info['period']; $dateForMultiplePeriodGraph = $info['date']; @@ -130,7 +135,12 @@ class Piwik_ImageGraph extends Piwik_Plugin // thanks to API.getRowEvolution, reports with dimensions can now be plotted using an evolution graph // however, most reports with a fixed set of dimension values are excluded // this is done so Piwik Mobile and Scheduled Reports do not display them - if (empty($report['constantRowsCount']) || in_array($reportUniqueId, self::$CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS)) { + $reportWithDimensionsSupportsEvolution = empty($report['constantRowsCount']) || in_array($reportUniqueId, self::$CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS); + + $reportSupportsEvolution = !in_array($reportUniqueId, self::$REPORTS_DISABLED_EVOLUTION_GRAPH); + + if ( $reportSupportsEvolution + && $reportWithDimensionsSupportsEvolution) { $parameters['period'] = $periodForMultiplePeriodGraph; $parameters['date'] = $dateForMultiplePeriodGraph; $report['imageGraphEvolutionUrl'] = $urlPrefix . Piwik_Url::getQueryStringFromParameters($parameters); diff --git a/plugins/ImageGraph/StaticGraph/HorizontalBar.php b/plugins/ImageGraph/StaticGraph/HorizontalBar.php index 8e6d69147d..fe4cf5b3b4 100644 --- a/plugins/ImageGraph/StaticGraph/HorizontalBar.php +++ b/plugins/ImageGraph/StaticGraph/HorizontalBar.php @@ -173,11 +173,13 @@ class Piwik_ImageGraph_StaticGraph_HorizontalBar extends Piwik_ImageGraph_Static - $logoHeight / 2 + 1; - $this->pImage->$drawingFunction( - $gridLeftMarginBeforePadding, - $logoYPosition, - $logoPath - ); + if(method_exists($this->pImage, $drawingFunction)) { + $this->pImage->$drawingFunction( + $gridLeftMarginBeforePadding, + $logoYPosition, + $logoPath + ); + } } } } diff --git a/plugins/ImageGraph/StaticGraph/PieGraph.php b/plugins/ImageGraph/StaticGraph/PieGraph.php index d2b8df51c5..a44bf79418 100644 --- a/plugins/ImageGraph/StaticGraph/PieGraph.php +++ b/plugins/ImageGraph/StaticGraph/PieGraph.php @@ -114,7 +114,7 @@ abstract class Piwik_ImageGraph_StaticGraph_PieGraph extends Piwik_ImageGraph_St $smallValuesSum += $this->ordinateSeries[$metricColumn][$ordinateValuesCount - 1]; if (($smallValuesSum / $ordinateValuesSum) > 0.01) { $truncatedOrdinateSeries[$metricColumn][] = $smallValuesSum; - $truncatedAbscissaSeries[] = Piwik_Translate('General_Others'); + $truncatedAbscissaSeries[] = end($this->abscissaSeries); } $this->ordinateSeries = $truncatedOrdinateSeries; diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php index cc45c10b18..e55e75b3f1 100644 --- a/plugins/Installation/Controller.php +++ b/plugins/Installation/Controller.php @@ -251,19 +251,20 @@ class Piwik_Installation_Controller extends Piwik_Controller_Admin } $tablesInstalled = Piwik::getTablesInstalled(); - $tablesToInstall = Piwik::getTablesNames(); $view->tablesInstalled = ''; if (count($tablesInstalled) > 0) { // we have existing tables $view->tablesInstalled = implode(', ', $tablesInstalled); $view->someTablesInstalled = true; + // remove monthly archive tables + $archiveTables = Piwik_DataAccess_ArchiveTableCreator::getTablesArchivesInstalled(); + $baseTablesInstalled = count($tablesInstalled) - count($archiveTables); $minimumCountPiwikTables = 17; - $baseTablesInstalled = preg_grep('/archive_numeric|archive_blob/', $tablesInstalled, PREG_GREP_INVERT); Piwik::createAccessObject(); Piwik::setUserIsSuperUser(); - if (count($baseTablesInstalled) >= $minimumCountPiwikTables && + if ($baseTablesInstalled >= $minimumCountPiwikTables && count(Piwik_SitesManager_API::getInstance()->getAllSitesId()) > 0 && count(Piwik_UsersManager_API::getInstance()->getUsers()) > 0 ) { diff --git a/plugins/Live/API.php b/plugins/Live/API.php index 11ae82c413..0cccab5d1b 100644 --- a/plugins/Live/API.php +++ b/plugins/Live/API.php @@ -535,13 +535,13 @@ class Piwik_Live_API $goalDetails = Piwik_FetchAll($sql, array($idVisit)); $sql = "SELECT - case idgoal when " . Piwik_Tracker_GoalManager::IDGOAL_CART . " then '" . Piwik_Archive::LABEL_ECOMMERCE_CART . "' else '" . Piwik_Archive::LABEL_ECOMMERCE_ORDER . "' end as type, + case idgoal when " . Piwik_Tracker_GoalManager::IDGOAL_CART . " then '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART . "' else '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER . "' end as type, idorder as orderId, - " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue') . " as revenue, - " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_subtotal') . " as revenueSubTotal, - " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_tax') . " as revenueTax, - " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_shipping') . " as revenueShipping, - " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_discount') . " as revenueDiscount, + " . Piwik_DataAccess_LogAggregator::getSqlRevenue('revenue') . " as revenue, + " . Piwik_DataAccess_LogAggregator::getSqlRevenue('revenue_subtotal') . " as revenueSubTotal, + " . Piwik_DataAccess_LogAggregator::getSqlRevenue('revenue_tax') . " as revenueTax, + " . Piwik_DataAccess_LogAggregator::getSqlRevenue('revenue_shipping') . " as revenueShipping, + " . Piwik_DataAccess_LogAggregator::getSqlRevenue('revenue_discount') . " as revenueDiscount, items as items, log_conversion.server_time as serverTimePretty @@ -553,7 +553,7 @@ class Piwik_Live_API $ecommerceDetails = Piwik_FetchAll($sql, array($idVisit)); foreach ($ecommerceDetails as &$ecommerceDetail) { - if ($ecommerceDetail['type'] == Piwik_Archive::LABEL_ECOMMERCE_CART) { + if ($ecommerceDetail['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { unset($ecommerceDetail['orderId']); unset($ecommerceDetail['revenueSubTotal']); unset($ecommerceDetail['revenueTax']); @@ -578,7 +578,7 @@ class Piwik_Live_API log_action_sku.name as itemSKU, log_action_name.name as itemName, log_action_category.name as itemCategory, - " . Piwik_ArchiveProcessing_Day::getSqlRevenue('price') . " as price, + " . Piwik_DataAccess_LogAggregator::getSqlRevenue('price') . " as price, quantity as quantity FROM " . Piwik_Common::prefixTable('log_conversion_item') . " INNER JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action_sku @@ -616,8 +616,8 @@ class Piwik_Live_API case 'goal': $details['icon'] = 'plugins/Zeitgeist/images/goal.png'; break; - case Piwik_Archive::LABEL_ECOMMERCE_ORDER: - case Piwik_Archive::LABEL_ECOMMERCE_CART: + case Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER: + case Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART: $details['icon'] = 'plugins/Zeitgeist/images/' . $details['type'] . '.gif'; break; case Piwik_Tracker_Action_Interface::TYPE_DOWNLOAD: diff --git a/plugins/Live/Controller.php b/plugins/Live/Controller.php index b049bbf80c..0d6e1e89d7 100644 --- a/plugins/Live/Controller.php +++ b/plugins/Live/Controller.php @@ -130,7 +130,7 @@ class Piwik_Live_Controller extends Piwik_Controller private function setCounters($view) { - $segment = Piwik_Common::getRequestVar('segment', false, 'string'); + $segment = Piwik_ViewDataTable::getRawSegmentFromRequest(); $last30min = Piwik_Live_API::getInstance()->getCounters($this->idSite, $lastMinutes = 30, $segment); $last30min = $last30min[0]; $today = Piwik_Live_API::getInstance()->getCounters($this->idSite, $lastMinutes = 24 * 60, $segment); diff --git a/plugins/Live/Visitor.php b/plugins/Live/Visitor.php index 4ceb88f9fd..aaf6904845 100644 --- a/plugins/Live/Visitor.php +++ b/plugins/Live/Visitor.php @@ -85,6 +85,7 @@ class Piwik_Live_Visitor 'latitude' => $this->getLatitude(), 'longitude' => $this->getLongitude(), 'provider' => $this->getProvider(), + 'providerName' => $this->getProviderName(), 'providerUrl' => $this->getProviderUrl(), 'referrerType' => $this->getRefererType(), @@ -106,6 +107,7 @@ class Piwik_Live_Visitor 'browserCode' => $this->getBrowserCode(), 'browserVersion' => $this->getBrowserVersion(), 'screenType' => $this->getScreenType(), + 'deviceType' => $this->getDeviceType(), 'resolution' => $this->getResolution(), 'screenTypeIcon' => $this->getScreenTypeIcon(), 'plugins' => $this->getPlugins(), @@ -342,7 +344,7 @@ class Piwik_Live_Visitor if (Piwik_PluginsManager::getInstance()->isPluginActivated('Referers') && $this->getRefererType() == 'search' ) { - $keyword = Piwik_Referers::getCleanKeyword($keyword); + $keyword = Piwik_Referers_API::getCleanKeyword($keyword); } return urldecode($keyword); } @@ -351,7 +353,7 @@ class Piwik_Live_Visitor { if ($this->getRefererType() == 'search') { if (Piwik_PluginsManager::getInstance()->isPluginActivated('Referers') - && $this->details['referer_keyword'] == Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED + && $this->details['referer_keyword'] == Piwik_Referers_API::LABEL_KEYWORD_NOT_DEFINED ) { return 'http://piwik.org/faq/general/#faq_144'; } // Case URL is google.XX/url.... then we rewrite to the search result page url @@ -506,6 +508,14 @@ class Piwik_Live_Visitor return Piwik_getScreenTypeFromResolution($this->details['config_resolution']); } + function getDeviceType() + { + if(Piwik_PluginsManager::getInstance()->isPluginActivated('DevicesDetection')) { + return Piwik_getDeviceTypeLabel($this->details['config_device_type']); + } + return false; + } + function getResolution() { return $this->details['config_resolution']; @@ -515,10 +525,19 @@ class Piwik_Live_Visitor { return Piwik_getScreensLogo($this->getScreenType()); } - + function getProvider() { - return Piwik_Provider_getPrettyProviderName(@$this->details['location_provider']); + if (isset($this->details['location_provider'])) { + return $this->details['location_provider']; + } else { + return Piwik_Translate('General_Unknown'); + } + } + + function getProviderName() + { + return Piwik_Provider_getPrettyProviderName($this->getProvider()); } function getProviderUrl() diff --git a/plugins/Live/templates/lastVisits.twig b/plugins/Live/templates/lastVisits.twig index f8cc9a9703..8134b7150a 100644 --- a/plugins/Live/templates/lastVisits.twig +++ b/plugins/Live/templates/lastVisits.twig @@ -8,7 +8,7 @@ <div title="{{ visitor.actionDetails|length }} {{ 'Live_Actions'|translate }}" class="datetime"> <span style='display:none' class='serverTimestamp'>{{ visitor.serverTimestamp|raw }}</span> {{ visitor.serverDatePretty }} - {{ visitor.serverTimePretty }} {% if visitor.visitDuration > 0 %}<i>({{ visitor.visitDurationPretty|raw }})</i>{% endif %} - <img src="{{ visitor.countryFlag }}" title="{{ visitor.location }}, {{ 'Provider_ColumnProvider'|translate }} {{ visitor.provider }}"/> + <img src="{{ visitor.countryFlag }}" title="{{ visitor.location }}, {{ 'Provider_ColumnProvider'|translate }} {{ visitor.providerName }}"/> <img src="{{ visitor.browserIcon }}" title="{{ visitor.browserName }}, {{ 'UserSettings_Plugins'|translate }}: {{ visitor.plugins }}"/> <img src="{{ visitor.operatingSystemIcon }}" title="{{ visitor.operatingSystem }}, {{ visitor.resolution }}"/> diff --git a/plugins/Live/templates/visitorLog.twig b/plugins/Live/templates/visitorLog.twig index de4147e43e..9303d526c7 100644 --- a/plugins/Live/templates/visitorLog.twig +++ b/plugins/Live/templates/visitorLog.twig @@ -47,7 +47,7 @@ {% for visitor in arrayDataTable %} {% set visitorColumnContent %} - <img src="{{ visitor.columns.countryFlag }}" title="{{ visitor.columns.location }}, Provider {{ visitor.columns.provider }}"/> + <img src="{{ visitor.columns.countryFlag }}" title="{{ visitor.columns.location }}, Provider {{ visitor.columns.providerName }}"/> {% if visitor.columns.plugins %} <img src="{{ visitor.columns.browserIcon }}" title="{{ 'UserSettings_BrowserWithPluginsEnabled'|translate(visitor.columns.browserName,visitor.columns.plugins) }}"/> @@ -108,11 +108,11 @@ GPS (lat/long): {{ visitor.columns.latitude }},{{ visitor.columns.longitude }}{% endif %}"> IP: {{ visitor.columns.visitIp }}</span>{% endif %} - {% if visitor.columns.provider is defined and visitor.columns.provider!='IP' %} + {% if visitor.columns.provider is defined and visitor.columns.providerName!='IP' %} <br/> {{ 'Provider_ColumnProvider'|translate }}: <a href="{{ visitor.columns.providerUrl }}" target="_blank" title="{{ visitor.columns.providerUrl }}" style="text-decoration:underline;"> - {{ visitor.columns.provider }} + {{ visitor.columns.providerName }} </a> {% endif %} {% if visitor.columns.customVariables is not empty %} diff --git a/plugins/MobileMessaging/ReportRenderer/Exception.php b/plugins/MobileMessaging/ReportRenderer/Exception.php index 5fa5796895..c6cdacf36b 100644 --- a/plugins/MobileMessaging/ReportRenderer/Exception.php +++ b/plugins/MobileMessaging/ReportRenderer/Exception.php @@ -59,7 +59,7 @@ class Piwik_MobileMessaging_ReportRenderer_Exception extends Piwik_ReportRendere return $this->rendering; } - public function renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata) + public function renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata, $segment) { // nothing to do } diff --git a/plugins/MobileMessaging/ReportRenderer/Sms.php b/plugins/MobileMessaging/ReportRenderer/Sms.php index d492dc1281..734484f3bf 100644 --- a/plugins/MobileMessaging/ReportRenderer/Sms.php +++ b/plugins/MobileMessaging/ReportRenderer/Sms.php @@ -47,7 +47,7 @@ class Piwik_MobileMessaging_ReportRenderer_Sms extends Piwik_ReportRenderer return $this->rendering; } - public function renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata) + public function renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata, $segment) { // nothing to do } @@ -121,6 +121,14 @@ class Piwik_MobileMessaging_ReportRenderer_Sms extends Piwik_ReportRenderer $smarty->assign("siteHasECommerce", $siteHasECommerce); $smarty->assign("displaySiteName", $processedReport['metadata']['action'] == 'getAll'); + // segment + $segment = $processedReport['segment']; + $displaySegment = ($segment != null); + $smarty->assign("displaySegment", $displaySegment); + if ($displaySegment) { + $smarty->assign("segmentName", $segment['name']); + } + $this->rendering .= $smarty->fetch(PIWIK_USER_PATH . '/plugins/MobileMessaging/templates/SMSReport.tpl'); } } diff --git a/plugins/MobileMessaging/templates/SMSReport.twig b/plugins/MobileMessaging/templates/SMSReport.twig index effb58e7e7..e9027e331c 100644 --- a/plugins/MobileMessaging/templates/SMSReport.twig +++ b/plugins/MobileMessaging/templates/SMSReport.twig @@ -1,5 +1,9 @@ {% spaceless %} - {{ prettyDate }}.{% endspaceless %} {% spaceless %} + {{ prettyDate }} + {% if displaySegment %} + ,{% endspaceless %}} {% spaceless %}}{{ segmentName }} + {% endif %} + .{% endspaceless %} {% spaceless %} {% if reportRows is empty %} {{ 'CoreHome_ThereIsNoDataForThisReport'|translate }} diff --git a/plugins/MultiSites/API.php b/plugins/MultiSites/API.php index b6b0a4d1ae..e3d398c259 100755 --- a/plugins/MultiSites/API.php +++ b/plugins/MultiSites/API.php @@ -218,9 +218,13 @@ class Piwik_MultiSites_API ) { $dataTable = $dataTable->mergeChildren(); } else { - if (!$dataTable instanceof Piwik_DataTable_Array) { + if (!($dataTable instanceof Piwik_DataTable_Array) + && $dataTable->getRowsCount() > 0 + ) { + $firstSite = is_array($sites) ? reset($sites) : $sites; + $firstDataTableRow = $dataTable->getFirstRow(); - $firstDataTableRow->setColumn('label', $sites); + $firstDataTableRow->setColumn('label', $firstSite); } } @@ -237,11 +241,14 @@ class Piwik_MultiSites_API // put there is put directly in Piwik_DataTable::metadata. $dataTable->setMetadata(self::getLastPeriodMetadataName('date'), $lastPeriod); } - - $pastArchive = Piwik_Archive::build('all', $period, $strLastDate, $segment, $_restrictSitesToLogin); + $pastArchive = Piwik_Archive::build($sites, $period, $strLastDate, $segment, $_restrictSitesToLogin); $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet); - - $pastData = $pastData->mergeChildren(); + + if ($pastData instanceof Piwik_DataTable_Array + && $multipleWebsitesRequested + ) { + $pastData = $pastData->mergeChildren(); + } // use past data to calculate evolution percentages $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics); @@ -312,6 +319,11 @@ class Piwik_MultiSites_API */ private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) { + if (get_class($currentData) != get_class($pastData)) { // sanity check for regressions + throw new Exception("Expected \$pastData to be of type ".get_class($currentData)." - got " + . get_class($pastData)."."); + } + if ($currentData instanceof Piwik_DataTable_Array) { $pastArray = $pastData->getArray(); foreach ($currentData->getArray() as $subTable) { @@ -349,7 +361,7 @@ class Piwik_MultiSites_API } else { $revenueMetric = ''; if (Piwik_Common::isGoalPluginEnabled()) { - $revenueMetric = Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC); + $revenueMetric = Piwik_Goals_Archiver::getRecordName(self::GOAL_REVENUE_METRIC); } $totals = array(); @@ -421,7 +433,7 @@ class Piwik_MultiSites_API $metrics[self::GOAL_REVENUE_METRIC] = array( self::METRIC_TRANSLATION_KEY => 'Goals_ColumnRevenue', self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_REVENUE_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC), + self::METRIC_RECORD_NAME_KEY => Piwik_Goals_Archiver::getRecordName(self::GOAL_REVENUE_METRIC), self::METRIC_IS_ECOMMERCE_KEY => false, ); @@ -430,7 +442,7 @@ class Piwik_MultiSites_API $metrics[self::GOAL_CONVERSION_METRIC] = array( self::METRIC_TRANSLATION_KEY => 'Goals_ColumnConversions', self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_CONVERSION_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC), + self::METRIC_RECORD_NAME_KEY => Piwik_Goals_Archiver::getRecordName(self::GOAL_CONVERSION_METRIC), self::METRIC_IS_ECOMMERCE_KEY => false, ); @@ -438,7 +450,7 @@ class Piwik_MultiSites_API $metrics[self::ECOMMERCE_ORDERS_METRIC] = array( self::METRIC_TRANSLATION_KEY => 'General_EcommerceOrders', self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_ORDERS_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC, 0), + self::METRIC_RECORD_NAME_KEY => Piwik_Goals_Archiver::getRecordName(self::GOAL_CONVERSION_METRIC, 0), self::METRIC_IS_ECOMMERCE_KEY => true, ); @@ -446,7 +458,7 @@ class Piwik_MultiSites_API $metrics[self::ECOMMERCE_REVENUE_METRIC] = array( self::METRIC_TRANSLATION_KEY => 'General_ProductRevenue', self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_REVENUE_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC, 0), + self::METRIC_RECORD_NAME_KEY => Piwik_Goals_Archiver::getRecordName(self::GOAL_REVENUE_METRIC, 0), self::METRIC_IS_ECOMMERCE_KEY => true, ); } diff --git a/plugins/MultiSites/Controller.php b/plugins/MultiSites/Controller.php index 151b080ac3..7439edfd3a 100644 --- a/plugins/MultiSites/Controller.php +++ b/plugins/MultiSites/Controller.php @@ -68,50 +68,60 @@ class Piwik_MultiSites_Controller extends Piwik_Controller foreach ($siteIds as $idSite) { $isEcommerceEnabled = Piwik_Site::isEcommerceEnabledFor($idSite); - $digestableData[$idSite] = array( - 'idsite' => $idSite, - 'main_url' => Piwik_Site::getMainUrlFor($idSite), - 'name' => Piwik_Site::getNameFor($idSite), - 'visits' => 0, - 'pageviews' => 0 +// allSites[{$i}] = new setRowData( +// 0 {$site.idsite}, +// 1 {$site.visits}, +// 2 {$site.pageviews}, +// 3 {if empty($site.revenue)}0{else}{$site.revenue}{/if}, +// 4 '{$site.name|escape:"javascript"}', +// 5 '{$site.main_url|escape:"javascript"}', +// 6 '{if isset($site.visits_evolution)}{$site.visits_evolution|replace:",":"."}{/if}', +// 7 '{if isset($site.pageviews_evolution)}{$site.pageviews_evolution|replace:",":"."}{/if}', +// 8 '{if isset($site.revenue_evolution)}{$site.revenue_evolution|replace:",":"."}{/if}'); + + $siteData = array($idSite,0,0.0, + Piwik_Site::getMainUrlFor($idSite), + Piwik_Site::getNameFor($idSite), ); if ($period != 'range') { - $digestableData[$idSite]['visits_evolution'] = 0; - $digestableData[$idSite]['pageviews_evolution'] = 0; + $siteData[6] = 0; + $siteData[7] = 0; } if ($displayRevenueColumn) { $revenueDefault = $isEcommerceEnabled ? 0 : "'-'"; if ($period != 'range') { - $digestableData[$idSite]['revenue_evolution'] = $revenueDefault; + $siteData[8] = $revenueDefault; } } + $digestableData[$idSite] = $siteData; } foreach ($dataTable->getRows() as $row) { $idsite = (int)$row->getMetadata('idsite'); - $site = & $digestableData[$idsite]; + $siteData = array(); - $site['visits'] = (int)$row->getColumn('nb_visits'); - $site['pageviews'] = (int)$row->getColumn('nb_pageviews'); + $siteData[1] = (int)$row->getColumn('nb_visits');http://pastebin.com/raw.php?i=1dvHmEUA + $siteData[2] = (int)$row->getColumn('nb_pageviews'); if ($displayRevenueColumn) { if ($row->getColumn('revenue') !== false) { - $site['revenue'] = $row->getColumn('revenue'); + $siteData[3] = $row->getColumn('revenue'); } } if ($period != 'range') { - $site['visits_evolution'] = $row->getColumn('visits_evolution'); - $site['pageviews_evolution'] = $row->getColumn('pageviews_evolution'); + $siteData[6] = $row->getColumn('visits_evolution'); + $siteData[7] = $row->getColumn('pageviews_evolution'); if ($displayRevenueColumn) { - $site['revenue_evolution'] = $row->getColumn('revenue_evolution'); + $siteData[8] = $row->getColumn('revenue_evolution'); } } + $digestableData[$idsite] = array_merge($digestableData[$idsite], $siteData); } $this->applyPrettyMoney($digestableData); diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php index 763b8f4797..aa23d48040 100644 --- a/plugins/MultiSites/MultiSites.php +++ b/plugins/MultiSites/MultiSites.php @@ -77,7 +77,7 @@ class Piwik_MultiSites extends Piwik_Plugin public function addTopMenu() { - $urlParams = array('module' => 'MultiSites', 'action' => 'index'); + $urlParams = array('module' => 'MultiSites', 'action' => 'index', 'segment' => false); $tooltip = Piwik_Translate('MultiSites_TopLinkTooltip'); Piwik_AddTopMenu('General_MultiSitesSummary', $urlParams, true, 3, $isHTML = false, $tooltip); } diff --git a/plugins/PDFReports/API.php b/plugins/PDFReports/API.php index aacba73e00..d3b0fccb95 100644 --- a/plugins/PDFReports/API.php +++ b/plugins/PDFReports/API.php @@ -76,10 +76,11 @@ class Piwik_PDFReports_API * @param string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook * @param array $reports array of reports * @param array $parameters array of parameters + * @param int $idSegment Segment Identifier * * @return int idReport generated */ - public function addReport($idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters) + public function addReport($idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters, $idSegment = false) { Piwik::checkUserIsNotAnonymous(); Piwik::checkUserHasViewAccess($idSite); @@ -87,7 +88,7 @@ class Piwik_PDFReports_API $currentUser = Piwik::getCurrentUserLogin(); self::ensureLanguageSetForUser($currentUser); - self::validateCommonReportAttributes($period, $hour, $description, $reportType, $reportFormat); + self::validateCommonReportAttributes($period, $hour, $description, $idSegment, $reportType, $reportFormat); // report parameters validations $parameters = self::validateReportParameters($reportType, $parameters); @@ -108,6 +109,7 @@ class Piwik_PDFReports_API 'idsite' => $idSite, 'login' => $currentUser, 'description' => $description, + 'idsegment' => $idSegment, 'period' => $period, 'hour' => $hour, 'type' => $reportType, @@ -134,7 +136,7 @@ class Piwik_PDFReports_API * * @see addReport() */ - public function updateReport($idReport, $idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters) + public function updateReport($idReport, $idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters, $idSegment = false) { Piwik::checkUserIsNotAnonymous(); Piwik::checkUserHasViewAccess($idSite); @@ -146,7 +148,7 @@ class Piwik_PDFReports_API $currentUser = Piwik::getCurrentUserLogin(); self::ensureLanguageSetForUser($currentUser); - self::validateCommonReportAttributes($period, $hour, $description, $reportType, $reportFormat); + self::validateCommonReportAttributes($period, $hour, $description, $idSegment, $reportType, $reportFormat); // report parameters validations $parameters = self::validateReportParameters($reportType, $parameters); @@ -157,6 +159,7 @@ class Piwik_PDFReports_API Zend_Registry::get('db')->update(Piwik_Common::prefixTable('report'), array( 'description' => $description, + 'idsegment' => $idSegment, 'period' => $period, 'hour' => $hour, 'type' => $reportType, @@ -199,10 +202,11 @@ class Piwik_PDFReports_API * @param int $idSite If specified, will filter reports that belong to a specific idsite * @param string $period If specified, will filter reports that are scheduled for this period (day,week,month) * @param int $idReport If specified, will filter the report that has the given idReport + * @param int $idSegment If specified, will filter the report that has the given idSegment * @return array * @throws Exception if $idReport was specified but the report wasn't found */ - public function getReports($idSite = false, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = false) + public function getReports($idSite = false, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = false, $idSegment = false) { Piwik::checkUserHasSomeViewAccess(); $cacheKey = (int)$idSite . '.' . (string)$period . '.' . (int)$idReport . '.' . (int)$ifSuperUserReturnOnlySuperUserReports; @@ -235,6 +239,10 @@ class Piwik_PDFReports_API $sqlWhere .= " AND idreport = ?"; $bind[] = $idReport; } + if (!empty($idSegment)) { + $sqlWhere .= " AND idsegment = ?"; + $bind[] = $idSegment; + } // Joining with the site table to work around pre-1.3 where reports could still be linked to a deleted site $reports = Piwik_FetchAll("SELECT * @@ -330,6 +338,7 @@ class Piwik_PDFReports_API $prettyDate = null; $processedReports = array(); + $segment = self::getSegment($report['idsegment']); foreach ($reportMetadata as $action) { $apiModule = $action['module']; $apiAction = $action['action']; @@ -361,9 +370,12 @@ class Piwik_PDFReports_API $processedReport = Piwik_API_API::getInstance()->getProcessedReport( $idSite, $period, $date, $apiModule, $apiAction, - $segment = false, $apiParameters, $idGoal = false, $language + $segment != null ? urlencode($segment['definition']) : false, + $apiParameters, $idGoal = false, $language ); + $processedReport['segment'] = $segment; + // TODO add static method getPrettyDate($period, $date) in Piwik_Period $prettyDate = $processedReport['prettyDate']; @@ -409,7 +421,7 @@ class Piwik_PDFReports_API list($reportSubject, $reportTitle) = self::getReportSubjectAndReportTitle(Piwik_Site::getNameFor($idSite), $report['reports']); $filename = "$reportTitle - $prettyDate - $description"; - $reportRenderer->renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata); + $reportRenderer->renderFrontPage($reportTitle, $prettyDate, $description, $reportMetadata, $segment); array_walk($processedReports, array($reportRenderer, 'renderReport')); switch ($outputType) { @@ -429,7 +441,8 @@ class Piwik_PDFReports_API $report['metadata'], Piwik_ReportRenderer_Html::IMAGE_GRAPH_WIDTH, Piwik_ReportRenderer_Html::IMAGE_GRAPH_HEIGHT, - $report['evolutionGraph'] + $report['evolutionGraph'], + $segment ); $additionalFile['mimeType'] = 'image/png'; $additionalFile['encoding'] = Zend_Mime::ENCODING_BASE64; @@ -609,11 +622,12 @@ class Piwik_PDFReports_API return Piwik_Common::json_encode($requestedReports); } - private static function validateCommonReportAttributes($period, $hour, &$description, $reportType, $reportFormat) + private static function validateCommonReportAttributes($period, $hour, &$description, &$idSegment, $reportType, $reportFormat) { self::validateReportPeriod($period); self::validateReportHour($hour); self::validateAndTruncateDescription($description); + self::validateIdSegment($idSegment); self::validateReportType($reportType); self::validateReportFormat($reportType, $reportFormat); } @@ -633,6 +647,22 @@ class Piwik_PDFReports_API } } + private static function validateIdSegment(&$idSegment) + { + if (empty($idSegment) || (is_numeric($idSegment) && $idSegment == 0)) { + + $idSegment = null; + + } elseif (!is_numeric($idSegment)) { + + throw new Exception('Invalid segment identifier. Should be an integer.'); + + } elseif (self::getSegment($idSegment) == null) { + + throw new Exception('Segment with id ' . $idSegment . ' does not exist or SegmentEditor is not activated.'); + } + } + private static function validateReportType($reportType) { $reportTypes = array_keys(self::getReportTypes()); @@ -740,4 +770,29 @@ class Piwik_PDFReports_API return $recipients; } + + /** + * @ignore + */ + static public function getSegment($idSegment) + { + if (self::isSegmentEditorActivated() && !empty($idSegment)) { + + $segment = Piwik_SegmentEditor_API::getInstance()->get($idSegment); + + if ($segment) { + return $segment; + } + } + + return null; + } + + /** + * @ignore + */ + public static function isSegmentEditorActivated() + { + return Piwik_PluginsManager::getInstance()->isPluginActivated('SegmentEditor'); + } } diff --git a/plugins/PDFReports/Controller.php b/plugins/PDFReports/Controller.php index 4769364d81..51d140d727 100644 --- a/plugins/PDFReports/Controller.php +++ b/plugins/PDFReports/Controller.php @@ -70,6 +70,17 @@ class Piwik_PDFReports_Controller extends Piwik_Controller $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); + $view->segmentEditorActivated = false; + if (Piwik_PDFReports_API::isSegmentEditorActivated()) { + + $savedSegmentsById = array(); + foreach (Piwik_SegmentEditor_API::getInstance()->getAll($this->idSite) as $savedSegment) { + $savedSegmentsById[$savedSegment['idsegment']] = $savedSegment['name']; + } + $view->savedSegmentsById = $savedSegmentsById; + $view->segmentEditorActivated = true; + } + echo $view->render(); } } diff --git a/plugins/PDFReports/PDFReports.php b/plugins/PDFReports/PDFReports.php index 15d0f534a8..7ffc5c1a24 100644 --- a/plugins/PDFReports/PDFReports.php +++ b/plugins/PDFReports/PDFReports.php @@ -67,22 +67,23 @@ class Piwik_PDFReports extends Piwik_Plugin public function getListHooksRegistered() { return array( - 'TopMenu.add' => 'addTopMenu', - 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', - 'AssetManager.getJsFiles' => 'getJsFiles', - 'PDFReports.getReportParameters' => 'getReportParameters', - 'PDFReports.validateReportParameters' => 'validateReportParameters', - 'PDFReports.getReportMetadata' => 'getReportMetadata', - 'PDFReports.getReportTypes' => 'getReportTypes', - 'PDFReports.getReportFormats' => 'getReportFormats', - 'PDFReports.getRendererInstance' => 'getRendererInstance', - 'PDFReports.getReportRecipients' => 'getReportRecipients', - 'PDFReports.processReports' => 'processReports', - 'PDFReports.allowMultipleReports' => 'allowMultipleReports', - 'PDFReports.sendReport' => 'sendReport', - 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports', - 'UsersManager.deleteUser' => 'deleteUserReport', - 'SitesManager.deleteSite' => 'deleteSiteReport', + 'TopMenu.add' => 'addTopMenu', + 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', + 'AssetManager.getJsFiles' => 'getJsFiles', + 'PDFReports.getReportParameters' => 'getReportParameters', + 'PDFReports.validateReportParameters' => 'validateReportParameters', + 'PDFReports.getReportMetadata' => 'getReportMetadata', + 'PDFReports.getReportTypes' => 'getReportTypes', + 'PDFReports.getReportFormats' => 'getReportFormats', + 'PDFReports.getRendererInstance' => 'getRendererInstance', + 'PDFReports.getReportRecipients' => 'getReportRecipients', + 'PDFReports.processReports' => 'processReports', + 'PDFReports.allowMultipleReports' => 'allowMultipleReports', + 'PDFReports.sendReport' => 'sendReport', + 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports', + 'UsersManager.deleteUser' => 'deleteUserReport', + 'SitesManager.deleteSite' => 'deleteSiteReport', + Piwik_SegmentEditor_API::DELETE_SEGMENT_EVENT => 'segmentDeletion', ); } @@ -310,7 +311,7 @@ class Piwik_PDFReports extends Piwik_Plugin $filename = $notificationInfo[Piwik_PDFReports_API::FILENAME_KEY]; $additionalFiles = $notificationInfo[Piwik_PDFReports_API::ADDITIONAL_FILES_KEY]; - $periods = self::getPeriodToFrequency(); + $periods = self::getPeriodToFrequencyAsAdjective(); $message = Piwik_Translate('PDFReports_EmailHello'); $subject = Piwik_Translate('General_Report') . ' ' . $reportTitle . " - " . $prettyDate; @@ -323,18 +324,36 @@ class Piwik_PDFReports extends Piwik_Plugin $attachmentName = $subject; $mail->setFrom($fromEmailAddress, $fromEmailName); + $displaySegmentInfo = false; + $segmentInfo = null; + $segment = Piwik_PDFReports_API::getSegment($report['idsegment']); + if($segment != null) { + $displaySegmentInfo = true; + $segmentInfo = Piwik_Translate('PDFReports_SegmentAppliedToReports', $segment['name']); + } + switch ($report['format']) { case 'html': // Needed when using images as attachment with cid $mail->setType(Zend_Mime::MULTIPART_RELATED); $message .= "<br/>" . Piwik_Translate('PDFReports_PleaseFindBelow', array($periods[$report['period']], $reportTitle)); + + if($displaySegmentInfo) { + $message .= " " . $segmentInfo; + } + $mail->setBodyHtml($message . "<br/><br/>" . $contents); break; default: case 'pdf': $message .= "\n" . Piwik_Translate('PDFReports_PleaseFindAttachedFile', array($periods[$report['period']], $reportTitle)); + + if($displaySegmentInfo) { + $message .= " " . $segmentInfo; + } + $mail->setBodyText($message); $mail->createAttachment( $contents, @@ -487,11 +506,33 @@ class Piwik_PDFReports extends Piwik_Plugin } } + /** + * @param Piwik_Event_Notification $notification notification object + */ + function segmentDeletion($notification) + { + $idSegment = & $notification->getNotificationObject(); + $reportsUsingSegment = Piwik_PDFReports_API::getInstance()->getReports(false, false, false, true, $idSegment); + + if (count($reportsUsingSegment) > 0) { + + $reportList = ''; + $reportNameJoinText = ' ' . Piwik_Translate('General_And') . ' '; + foreach ($reportsUsingSegment as $report) { + $reportList .= '\'' . $report['description'] . '\'' . $reportNameJoinText; + } + $reportList = rtrim($reportList, $reportNameJoinText); + + $errorMessage = Piwik_Translate('PDFReports_Segment_Deletion_Error', $reportList); + throw new Exception($errorMessage); + } + } + function addTopMenu() { Piwik_AddTopMenu( $this->getTopMenuTranslationKey(), - array('module' => 'PDFReports', 'action' => 'index'), + array('module' => 'PDFReports', 'action' => 'index', 'segment' => false), true, 13, $isHTML = false, @@ -555,6 +596,7 @@ class Piwik_PDFReports extends Piwik_Plugin `idsite` INTEGER(11) NOT NULL, `login` VARCHAR(100) NOT NULL, `description` VARCHAR(255) NOT NULL, + `idsegment` INT(11), `period` VARCHAR(10) NOT NULL, `hour` tinyint NOT NULL default 0, `type` VARCHAR(10) NOT NULL, @@ -606,6 +648,7 @@ class Piwik_PDFReports extends Piwik_Plugin } /** + * Used in the Report Listing * @ignore */ static public function getPeriodToFrequency() @@ -617,4 +660,19 @@ class Piwik_PDFReports extends Piwik_Plugin Piwik_ScheduledTime::PERIOD_MONTH => Piwik_Translate('General_Monthly'), ); } + + /** + * Used in the Report's email content, ie "monthly report" + * @ignore + */ + static public function getPeriodToFrequencyAsAdjective() + { + return array( + Piwik_ScheduledTime::PERIOD_DAY => Piwik_Translate('General_DailyReport'), + Piwik_ScheduledTime::PERIOD_WEEK => Piwik_Translate('General_WeeklyReport'), + Piwik_ScheduledTime::PERIOD_MONTH => Piwik_Translate('General_MonthlyReport'), + Piwik_ScheduledTime::PERIOD_YEAR => Piwik_Translate('General_YearlyReport'), + Piwik_ScheduledTime::PERIOD_RANGE => Piwik_Translate('General_RangeReports'), + ); + } } diff --git a/plugins/PDFReports/javascripts/pdf.js b/plugins/PDFReports/javascripts/pdf.js index 75e3ff831b..f6f3398a1e 100644 --- a/plugins/PDFReports/javascripts/pdf.js +++ b/plugins/PDFReports/javascripts/pdf.js @@ -30,8 +30,9 @@ function formSetEditReport(idReport) { toggleReportType(report.type); $('#report_description').html(report.description); - $('#report_type option[value=' + report.type + ']').prop('selected', 'selected'); - $('#report_period option[value=' + report.period + ']').prop('selected', 'selected'); + $('#report_segment').find('option[value=' + report.idsegment + ']').prop('selected', 'selected'); + $('#report_type').find('option[value=' + report.type + ']').prop('selected', 'selected'); + $('#report_period').find('option[value=' + report.period + ']').prop('selected', 'selected'); $('#report_hour').val(report.hour); $('[name=report_format].' + report.type + ' option[value=' + report.format + ']').prop('selected', 'selected'); @@ -61,7 +62,7 @@ function getReportAjaxRequest(idReport, defaultApiMethod) { function toggleReportType(reportType) { resetReportParametersFunctions[reportType](); - $('#report_type option').each(function (index, type) { + $('#report_type').find('option').each(function (index, type) { $('.' + $(type).val()).hide(); }); $('.' + reportType).show(); @@ -74,7 +75,8 @@ function initManagePdf() { var apiParameters = getReportAjaxRequest(idReport, 'PDFReports.updateReport'); apiParameters.idReport = idReport; apiParameters.description = $('#report_description').val(); - apiParameters.reportType = $('#report_type option:selected').val(); + apiParameters.idSegment = $('#report_segment').find('option:selected').val(); + apiParameters.reportType = $('#report_type').find('option:selected').val(); apiParameters.reportFormat = $('[name=report_format].' + apiParameters.reportType + ' option:selected').val(); var reports = []; @@ -89,7 +91,7 @@ function initManagePdf() { var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams(apiParameters, 'POST'); - ajaxHandler.addParams({period: $('#report_period option:selected').val()}, 'GET'); + ajaxHandler.addParams({period: $('#report_period').find('option:selected').val()}, 'GET'); ajaxHandler.addParams({hour: $('#report_hour').val()}, 'GET'); ajaxHandler.redirectOnSuccess(); ajaxHandler.setLoadingElement(); diff --git a/plugins/PDFReports/templates/add.twig b/plugins/PDFReports/templates/add.twig index b41d901efc..f54859bfba 100644 --- a/plugins/PDFReports/templates/add.twig +++ b/plugins/PDFReports/templates/add.twig @@ -27,6 +27,25 @@ </div> </td> </tr> + {% if segmentEditorActivated %} + <tr> + <td class="first">{{ 'SegmentEditor_ChooseASegment'|translate }} </td> + <td> + <select id='report_segment'> + <option value="">{{ 'SegmentEditor_DefaultAllVisits'|translate }}</option> + {% for savedSegmentId, savedSegmentName in savedSegmentsById %} + <option value="{{ savedSegmentId }}">{{ savedSegmentName }}</option> + {% endfor %} + </select> + + <div class="entityInlineHelp"> + {% set SegmentEditor_DefaultAllVisits %}{{ 'SegmentEditor_DefaultAllVisits'|translate }}{% endset %} + {% set SegmentEditor_AddNewSegment %}{{ 'SegmentEditor_AddNewSegment'|translate }}{% endset %} + {{ 'PDFReports_Segment_Help'|translate('<a href="./" target="_blank">','</a>',SegmentEditor_DefaultAllVisits,SegmentEditor_AddNewSegment)|raw }} + </div> + </td> + </tr> + {% endif %} <tr> <td class="first">{{ 'PDFReports_EmailSchedule'|translate }}</td> <td> diff --git a/plugins/PDFReports/templates/list.twig b/plugins/PDFReports/templates/list.twig index 2a8f580a24..8d39cef697 100644 --- a/plugins/PDFReports/templates/list.twig +++ b/plugins/PDFReports/templates/list.twig @@ -36,7 +36,14 @@ {% else %} {% for report in reports %} <tr> - <td class="first">{{ report.description }}</td> + <td class="first"> + {{ report.description }} + {% if segmentEditorActivated and report.idsegment %} + <div class="entityInlineHelp" style="font-size: 9pt;"> + {{ savedSegmentsById[report.idsegment] }} + </div> + {% endif %} + </td> <td>{{ periods[report.period] }} <!-- Last sent on {{ report.ts_last_sent }} --> </td> @@ -63,7 +70,7 @@ </td> <td> {# download link #} - <a href="{{ url({'module':'API', 'token_auth':token_auth, 'method':'PDFReports.generateReport', 'date':rawDate, 'idReport':report.idreport, 'outputType':downloadOutputType, 'language':language}) }}" + <a href="{{ url({'module':'API', 'segment': null, 'token_auth':token_auth, 'method':'PDFReports.generateReport', 'idReport':report.idreport, 'outputType':downloadOutputType, 'language':language}) }}" target="_blank" name="linkDownloadReport" id="{{ report.idreport }}" class="link_but"> <img src='{{ reportFormatsByReportType[report.type][report.format] }}' border="0"/> {{ 'General_Download'|translate }} diff --git a/plugins/PDFReports/templates/report_parameters.twig b/plugins/PDFReports/templates/report_parameters.twig index 0bf2e2bd14..2a6867b518 100644 --- a/plugins/PDFReports/templates/report_parameters.twig +++ b/plugins/PDFReports/templates/report_parameters.twig @@ -2,7 +2,7 @@ function updateEvolutionGraphParameterVisibility() { var evolutionGraphParameterInput = $('.report_evolution_graph'); var nonApplicableDisplayFormats = ['1', '4']; - $.inArray($('#display_format option:selected').val(), nonApplicableDisplayFormats) != -1 ? + $.inArray($('#display_format').find('option:selected').val(), nonApplicableDisplayFormats) != -1 ? evolutionGraphParameterInput.hide() : evolutionGraphParameterInput.show(); } @@ -25,7 +25,7 @@ if (reportParameters == null) return; - $('#display_format option[value=' + reportParameters.displayFormat + ']').prop('selected', 'selected'); + $('#display_format').find('option[value=' + reportParameters.displayFormat + ']').prop('selected', 'selected'); updateEvolutionGraphParameterVisibility(); if (reportParameters.emailMe === true) @@ -49,7 +49,7 @@ var parameters = Object(); - parameters.displayFormat = $('#display_format option:selected').val(); + parameters.displayFormat = $('#display_format').find('option:selected').val(); parameters.emailMe = $('#report_email_me').prop('checked'); parameters.evolutionGraph = $('#report_evolution_graph').prop('checked'); diff --git a/plugins/PrivacyManager/PrivacyManager.php b/plugins/PrivacyManager/PrivacyManager.php index a538a96e08..dc0b436c61 100644 --- a/plugins/PrivacyManager/PrivacyManager.php +++ b/plugins/PrivacyManager/PrivacyManager.php @@ -332,7 +332,7 @@ class Piwik_PrivacyManager extends Piwik_Plugin private static function getGoalMetricsToKeep() { // keep all goal metrics - return array_values(Piwik_Archive::$mappingFromIdToNameGoal); + return array_values(Piwik_Metrics::$mappingFromIdToNameGoal); } /** @@ -354,12 +354,12 @@ class Piwik_PrivacyManager extends Piwik_Plugin foreach ($goalMetricsToKeep as $metric) { for ($i = 1; $i <= $maxGoalId; ++$i) // maxGoalId can be 0 { - $metricsToKeep[] = Piwik_Goals::getRecordName($metric, $i); + $metricsToKeep[] = Piwik_Goals_Archiver::getRecordName($metric, $i); } - $metricsToKeep[] = Piwik_Goals::getRecordName($metric); - $metricsToKeep[] = Piwik_Goals::getRecordName($metric, Piwik_Tracker_GoalManager::IDGOAL_ORDER); - $metricsToKeep[] = Piwik_Goals::getRecordName($metric, Piwik_Tracker_GoalManager::IDGOAL_CART); + $metricsToKeep[] = Piwik_Goals_Archiver::getRecordName($metric); + $metricsToKeep[] = Piwik_Goals_Archiver::getRecordName($metric, Piwik_Tracker_GoalManager::IDGOAL_ORDER); + $metricsToKeep[] = Piwik_Goals_Archiver::getRecordName($metric, Piwik_Tracker_GoalManager::IDGOAL_CART); } } diff --git a/plugins/PrivacyManager/ReportsPurger.php b/plugins/PrivacyManager/ReportsPurger.php index 61c650a613..2fbc3fb6b6 100755 --- a/plugins/PrivacyManager/ReportsPurger.php +++ b/plugins/PrivacyManager/ReportsPurger.php @@ -203,24 +203,23 @@ class Piwik_PrivacyManager_ReportsPurger // reports whose creation date <= this month will be deleted // (NOTE: we ignore how far we are in the current month) $toRemoveDate = Piwik_Date::factory('today')->subMonth(1 + $this->deleteReportsOlderThan); - $toRemoveYear = (int)$toRemoveDate->toString('Y'); - $toRemoveMonth = (int)$toRemoveDate->toString('m'); // find all archive tables that are older than N months $oldNumericTables = array(); $oldBlobTables = array(); foreach (Piwik::getTablesInstalled() as $table) { - if (preg_match("/archive_(numeric|blob)_([0-9]+)_([0-9]+)/", $table, $matches)) { - $type = $matches[1]; - $year = (int)$matches[2]; - $month = (int)$matches[3]; - - if (self::shouldReportBePurged($year, $month, $toRemoveDate)) { - if ($type == "numeric") { - $oldNumericTables[] = $table; - } else { - $oldBlobTables[] = $table; - } + $type = Piwik_DataAccess_ArchiveTableCreator::getTypeFromTableName($table); + if($type === false) { + continue; + } + $date = Piwik_DataAccess_ArchiveTableCreator::getDateFromTableName($table); + list($year, $month) = explode('_', $date); + + if (self::shouldReportBePurged($year, $month, $toRemoveDate)) { + if ($type == Piwik_DataAccess_ArchiveTableCreator::NUMERIC_TABLE) { + $oldNumericTables[] = $table; + } else { + $oldBlobTables[] = $table; } } } @@ -285,7 +284,7 @@ class Piwik_PrivacyManager_ReportsPurger // if not keeping segments make sure segments w/ kept periods are also deleted if (!$this->keepSegmentReports) { $this->findSegmentArchives($oldNumericTables); - $archiveIds = $this->segmentArchiveIds[$this->getArchiveTableDate($table)]; + $archiveIds = $this->segmentArchiveIds[Piwik_DataAccess_ArchiveTableCreator::getDateFromTableName($table)]; if (!empty($archiveIds)) { $where .= " OR idarchive IN (" . implode(',', $archiveIds) . ")"; @@ -308,7 +307,7 @@ class Piwik_PrivacyManager_ReportsPurger } foreach ($numericTables as $table) { - $tableDate = $this->getArchiveTableDate($table); + $tableDate = Piwik_DataAccess_ArchiveTableCreator::getDateFromTableName($table); $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); @@ -326,12 +325,6 @@ class Piwik_PrivacyManager_ReportsPurger } } - private function getArchiveTableDate($table) - { - preg_match("/[a-zA-Z_]+([0-9]+_[0-9]+)/", $table, $matches); - return $matches[1]; - } - /** * Utility function. Creates a new instance of ReportsPurger with the supplied array * of settings. diff --git a/plugins/PrivacyManager/javascripts/privacySettings.js b/plugins/PrivacyManager/javascripts/privacySettings.js index 75de9b8caa..d420118fdc 100644 --- a/plugins/PrivacyManager/javascripts/privacySettings.js +++ b/plugins/PrivacyManager/javascripts/privacySettings.js @@ -95,10 +95,10 @@ $(document).ready(function () { }); // make sure the DB size estimate is reloaded every time a delete logs/reports setting is changed - $('#formDeleteSettings input[type=text]').each(function () { + $('#formDeleteSettings').find('input[type=text]').each(function () { $(this).change(reloadDbStats); }); - $('#formDeleteSettings input[type=checkbox]').each(function () { + $('#formDeleteSettings').find('input[type=checkbox]').each(function () { $(this).click(reloadDbStats); }); @@ -111,7 +111,7 @@ $(document).ready(function () { // hide all confirmation texts, then show the correct one based on what // type of deletion is enabled. - $('#confirmDeleteSettings>h2').each(function () { + $('#confirmDeleteSettings').find('>h2').each(function () { $(this).hide(); }); @@ -145,7 +145,7 @@ $(document).ready(function () { // if any option has been modified, abort purging and instruct user to save first var modified = false; - $('#formDeleteSettings input').each(function () { + $('#formDeleteSettings').find('input').each(function () { if (this.type === 'checkbox' || this.type === 'radio') { modified |= this.defaultChecked !== this.checked; } else { diff --git a/plugins/Provider/API.php b/plugins/Provider/API.php index 603a8dfc94..79e8fdaefb 100644 --- a/plugins/Provider/API.php +++ b/plugins/Provider/API.php @@ -35,8 +35,8 @@ class Piwik_Provider_API { Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - $dataTable = $archive->getDataTable('Provider_hostnameExt'); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS)); + $dataTable = $archive->getDataTable(Piwik_Provider_Archiver::PROVIDER_RECORD_NAME); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS)); $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getHostnameUrl')); $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Provider_getPrettyProviderName')); $dataTable->queueFilter('ReplaceColumnNames'); diff --git a/plugins/Provider/Archiver.php b/plugins/Provider/Archiver.php new file mode 100644 index 0000000000..d515c5cefc --- /dev/null +++ b/plugins/Provider/Archiver.php @@ -0,0 +1,27 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_Provider + */ +class Piwik_Provider_Archiver extends Piwik_PluginsArchiver +{ + const PROVIDER_RECORD_NAME = 'Provider_hostnameExt'; + const PROVIDER_FIELD = "location_provider"; + + public function archiveDay() + { + $metrics = $this->getProcessor()->getMetricsForDimension(self::PROVIDER_FIELD); + $tableProvider = $this->getProcessor()->getDataTableFromDataArray($metrics); + $this->getProcessor()->insertBlobRecord(self::PROVIDER_RECORD_NAME, $tableProvider->getSerialized($this->maximumRows, null, Piwik_Metrics::INDEX_NB_VISITS)); + } + + public function archivePeriod() + { + $this->getProcessor()->aggregateDataTableReports(array(self::PROVIDER_RECORD_NAME), $this->maximumRows); + } +}
\ No newline at end of file diff --git a/plugins/Provider/Provider.php b/plugins/Provider/Provider.php index 64d18ace74..f6d1ff1b68 100644 --- a/plugins/Provider/Provider.php +++ b/plugins/Provider/Provider.php @@ -115,42 +115,6 @@ class Piwik_Provider extends Piwik_Plugin } /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ - function archivePeriod($notification) - { - $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard']; - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $dataTableToSum = array('Provider_hostnameExt'); - $archiveProcessing->archiveDataTable($dataTableToSum, null, $maximumRowsInDataTable); - } - - /** - * Daily archive: processes the report Visits by Provider - * - * @param Piwik_Event_Notification $notification notification object - */ - function archiveDay($notification) - { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $recordName = 'Provider_hostnameExt'; - $labelSQL = "log_visit.location_provider"; - $interestByProvider = $archiveProcessing->getArrayInterestForLabel($labelSQL); - $tableProvider = $archiveProcessing->getDataTableFromArray($interestByProvider); - $columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS; - $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard']; - $archiveProcessing->insertBlobRecord($recordName, $tableProvider->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation)); - destroy($tableProvider); - } - - /** * Logs the provider in the log_visit table * * @param Piwik_Event_Notification $notification notification object @@ -250,4 +214,34 @@ class Piwik_Provider extends Piwik_Plugin $out .= '</div>'; } + /** + * Daily archive: processes the report Visits by Provider + * + * @param Piwik_Event_Notification $notification notification object + */ + function archiveDay($notification) + { + $archiveProcessor = $notification->getNotificationObject(); + + $archiving = new Piwik_Provider_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + * @return mixed + */ + function archivePeriod($notification) + { + $archiveProcessor = $notification->getNotificationObject(); + + $archiving = new Piwik_Provider_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); + } + } + + } diff --git a/plugins/Referers/API.php b/plugins/Referers/API.php index 6516228fd6..b87f7d0e8e 100644 --- a/plugins/Referers/API.php +++ b/plugins/Referers/API.php @@ -37,7 +37,7 @@ class Piwik_Referers_API protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null) { $dataTable = Piwik_Archive::getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); $dataTable->queueFilter('ReplaceColumnNames'); return $dataTable; } @@ -88,7 +88,7 @@ class Piwik_Referers_API } // get visits by referrer type - $dataTable = $this->getDataTable('Referers_type', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::REFERER_TYPE_RECORD_NAME, $idSite, $period, $date, $segment); if ($typeReferer !== false) // filter for a specific referrer type { @@ -112,8 +112,7 @@ class Piwik_Referers_API */ public function getAll($idSite, $period, $date, $segment = false) { - $dataTable = $this->getRefererType($idSite, $period, $date, $segment, $typeReferer = false, - $idSubtable = false, $expanded = true); + $dataTable = $this->getRefererType($idSite, $period, $date, $segment, $typeReferer = false, $idSubtable = false, $expanded = true); if ($dataTable instanceof Piwik_DataTable_Array) { throw new Exception("Referrers.getAll with multiple sites or dates is not supported (yet)."); @@ -121,8 +120,7 @@ class Piwik_Referers_API $dataTable = $dataTable->mergeSubtables($labelColumn = 'referrer_type', $useMetadataColumn = true); - // presentation filters - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc')); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS, 'desc')); $dataTable->queueFilter('ReplaceColumnNames'); $dataTable->queueFilter('ReplaceSummaryRowLabel'); @@ -131,17 +129,37 @@ class Piwik_Referers_API public function getKeywords($idSite, $period, $date, $segment = false, $expanded = false) { - $dataTable = $this->getDataTable('Referers_searchEngineByKeyword', $idSite, $period, $date, $segment, $expanded); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::KEYWORDS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); $dataTable = $this->handleKeywordNotDefined($dataTable); return $dataTable; } protected function handleKeywordNotDefined($dataTable) { - $dataTable->queueFilter('ColumnCallbackReplace', array('label', array('Piwik_Referers', 'getCleanKeyword'))); + $dataTable->queueFilter('ColumnCallbackReplace', array('label', array('Piwik_Referers_API', 'getCleanKeyword'))); return $dataTable; } + const LABEL_KEYWORD_NOT_DEFINED = ""; + + /** + * @ignore + */ + static public function getKeywordNotDefinedString() + { + return Piwik_Translate('General_NotDefined', Piwik_Translate('Referers_ColumnKeyword')); + } + + /** + * @ignore + */ + static public function getCleanKeyword($label) + { + return $label == self::LABEL_KEYWORD_NOT_DEFINED + ? self::getKeywordNotDefinedString() + : $label; + } + public function getKeywordsForPageUrl($idSite, $period, $date, $url) { // Fetch the Top keywords for this page @@ -194,7 +212,7 @@ class Piwik_Referers_API public function getSearchEnginesFromKeywordId($idSite, $period, $date, $idSubtable, $segment = false) { - $dataTable = $this->getDataTable('Referers_searchEngineByKeyword', $idSite, $period, $date, $segment, $expanded = false, $idSubtable); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::KEYWORDS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getSearchEngineUrlFromName')); $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik_getSearchEngineLogoFromUrl')); @@ -210,7 +228,7 @@ class Piwik_Referers_API public function getSearchEngines($idSite, $period, $date, $segment = false, $expanded = false) { - $dataTable = $this->getDataTable('Referers_keywordBySearchEngine', $idSite, $period, $date, $segment, $expanded); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::SEARCH_ENGINES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getSearchEngineUrlFromName')); $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik_getSearchEngineLogoFromUrl')); return $dataTable; @@ -218,7 +236,7 @@ class Piwik_Referers_API public function getKeywordsFromSearchEngineId($idSite, $period, $date, $idSubtable, $segment = false) { - $dataTable = $this->getDataTable('Referers_keywordBySearchEngine', $idSite, $period, $date, $segment, $expanded = false, $idSubtable); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::SEARCH_ENGINES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); // get the search engine and create the URL to the search result page $searchEngines = $this->getSearchEngines($idSite, $period, $date, $segment); @@ -249,25 +267,25 @@ class Piwik_Referers_API public function getCampaigns($idSite, $period, $date, $segment = false, $expanded = false) { - $dataTable = $this->getDataTable('Referers_keywordByCampaign', $idSite, $period, $date, $segment, $expanded); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::CAMPAIGNS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); return $dataTable; } public function getKeywordsFromCampaignId($idSite, $period, $date, $idSubtable, $segment = false) { - $dataTable = $this->getDataTable('Referers_keywordByCampaign', $idSite, $period, $date, $segment, $expanded = false, $idSubtable); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::CAMPAIGNS_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); return $dataTable; } public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false) { - $dataTable = $this->getDataTable('Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); return $dataTable; } public function getUrlsFromWebsiteId($idSite, $period, $date, $idSubtable, $segment = false) { - $dataTable = $this->getDataTable('Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded = false, $idSubtable); + $dataTable = $this->getDataTable(Piwik_Referers_Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); // the htmlspecialchars_decode call is for BC for before 1.1 // as the Referer URL was previously encoded in the log tables, but is now recorded raw $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', create_function('$label', 'return htmlspecialchars_decode($label);'))); @@ -290,7 +308,7 @@ class Piwik_Referers_API { require PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php'; - $dataTable = $this->getDataTable('Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded); + $dataTable = $this->getDataTable( Piwik_Referers_Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); $dataTable->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_Referrers_isSocialUrl')); @@ -323,8 +341,7 @@ class Piwik_Referers_API { require PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php'; - $dataTable = $this->getDataTable( - 'Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded = true); + $dataTable = $this->getDataTable( Piwik_Referers_Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = true); // get the social network domain referred to by $idSubtable $social = false; @@ -350,7 +367,7 @@ class Piwik_Referers_API // prettify the DataTable $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_Referrers_removeUrlProtocol')); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); $dataTable->queueFilter('ReplaceColumnNames'); return $dataTable; @@ -358,27 +375,27 @@ class Piwik_Referers_API public function getNumberOfDistinctSearchEngines($idSite, $period, $date, $segment = false) { - return $this->getNumeric('Referers_distinctSearchEngines', $idSite, $period, $date, $segment); + return $this->getNumeric(Piwik_Referers_Archiver::METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME, $idSite, $period, $date, $segment); } public function getNumberOfDistinctKeywords($idSite, $period, $date, $segment = false) { - return $this->getNumeric('Referers_distinctKeywords', $idSite, $period, $date, $segment); + return $this->getNumeric(Piwik_Referers_Archiver::METRIC_DISTINCT_KEYWORD_RECORD_NAME, $idSite, $period, $date, $segment); } public function getNumberOfDistinctCampaigns($idSite, $period, $date, $segment = false) { - return $this->getNumeric('Referers_distinctCampaigns', $idSite, $period, $date, $segment); + return $this->getNumeric(Piwik_Referers_Archiver::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME, $idSite, $period, $date, $segment); } public function getNumberOfDistinctWebsites($idSite, $period, $date, $segment = false) { - return $this->getNumeric('Referers_distinctWebsites', $idSite, $period, $date, $segment); + return $this->getNumeric(Piwik_Referers_Archiver::METRIC_DISTINCT_WEBSITE_RECORD_NAME, $idSite, $period, $date, $segment); } public function getNumberOfDistinctWebsitesUrls($idSite, $period, $date, $segment = false) { - return $this->getNumeric('Referers_distinctWebsitesUrls', $idSite, $period, $date, $segment); + return $this->getNumeric(Piwik_Referers_Archiver::METRIC_DISTINCT_URLS_RECORD_NAME, $idSite, $period, $date, $segment); } private function getNumeric($name, $idSite, $period, $date, $segment) diff --git a/plugins/Referers/Archiver.php b/plugins/Referers/Archiver.php new file mode 100644 index 0000000000..6e8152ed5e --- /dev/null +++ b/plugins/Referers/Archiver.php @@ -0,0 +1,265 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_Referers + */ + +class Piwik_Referers_Archiver extends Piwik_PluginsArchiver +{ + const SEARCH_ENGINES_RECORD_NAME = 'Referers_keywordBySearchEngine'; + const KEYWORDS_RECORD_NAME = 'Referers_searchEngineByKeyword'; + const CAMPAIGNS_RECORD_NAME = 'Referers_keywordByCampaign'; + const WEBSITES_RECORD_NAME = 'Referers_urlByWebsite'; + const REFERER_TYPE_RECORD_NAME = 'Referers_type'; + const METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME = 'Referers_distinctSearchEngines'; + const METRIC_DISTINCT_KEYWORD_RECORD_NAME = 'Referers_distinctKeywords'; + const METRIC_DISTINCT_CAMPAIGN_RECORD_NAME = 'Referers_distinctCampaigns'; + const METRIC_DISTINCT_WEBSITE_RECORD_NAME = 'Referers_distinctWebsites'; + const METRIC_DISTINCT_URLS_RECORD_NAME = 'Referers_distinctWebsitesUrls'; + protected $columnToSortByBeforeTruncation; + protected $maximumRowsInDataTableLevelZero; + protected $maximumRowsInSubDataTable; + /* @var array[Piwik_DataArray] $arrays */ + protected $arrays = array(); + protected $distinctUrls = array(); + + function __construct($processor) + { + parent::__construct($processor); + $this->columnToSortByBeforeTruncation = Piwik_Metrics::INDEX_NB_VISITS; + $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_referers']; + $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers']; + } + + public function archiveDay() + { + foreach ($this->getRecordNames() as $record) { + $this->arrays[$record] = new Piwik_DataArray(); + } + $query = $this->getLogAggregator()->queryVisitsByDimension(array("referer_type", "referer_name", "referer_keyword", "referer_url")); + $this->aggregateFromVisits($query); + + $query = $this->getLogAggregator()->queryConversionsByDimension(array("referer_type", "referer_name", "referer_keyword")); + $this->aggregateFromConversions($query); + + Piwik_PostEvent('Referers.archiveDay', $this); + $this->recordDayReports(); + } + + protected function getRecordNames() + { + return array( + self::REFERER_TYPE_RECORD_NAME, + self::KEYWORDS_RECORD_NAME, + self::SEARCH_ENGINES_RECORD_NAME, + self::WEBSITES_RECORD_NAME, + self::CAMPAIGNS_RECORD_NAME, + ); + } + + protected function aggregateFromVisits($query) + { + while ($row = $query->fetch()) { + $this->makeRefererTypeNonEmpty($row); + $this->aggregateVisit($row); + } + } + + protected function makeRefererTypeNonEmpty(&$row) + { + if (empty($row['referer_type'])) { + $row['referer_type'] = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; + } + } + + protected function aggregateVisit($row) + { + switch ($row['referer_type']) { + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + if (empty($row['referer_keyword'])) { + $row['referer_keyword'] = Piwik_Referers_API::LABEL_KEYWORD_NOT_DEFINED; + } + $searchEnginesArray = $this->getDataArray(self::SEARCH_ENGINES_RECORD_NAME); + $searchEnginesArray->sumMetricsVisits($row['referer_name'], $row); + $searchEnginesArray->sumMetricsVisitsPivot($row['referer_name'], $row['referer_keyword'], $row); + $keywordsDataArray = $this->getDataArray(self::KEYWORDS_RECORD_NAME); + $keywordsDataArray->sumMetricsVisits($row['referer_keyword'], $row); + $keywordsDataArray->sumMetricsVisitsPivot($row['referer_keyword'], $row['referer_name'], $row); + break; + + case Piwik_Common::REFERER_TYPE_WEBSITE: + $this->getDataArray(self::WEBSITES_RECORD_NAME)->sumMetricsVisits($row['referer_name'], $row); + $this->getDataArray(self::WEBSITES_RECORD_NAME)->sumMetricsVisitsPivot($row['referer_name'], $row['referer_url'], $row); + + $urlHash = substr(md5($row['referer_url']), 0, 10); + if (!isset($this->distinctUrls[$urlHash])) { + $this->distinctUrls[$urlHash] = true; + } + break; + + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + if (!empty($row['referer_keyword'])) { + $this->getDataArray(self::CAMPAIGNS_RECORD_NAME)->sumMetricsVisitsPivot($row['referer_name'], $row['referer_keyword'], $row); + } + $this->getDataArray(self::CAMPAIGNS_RECORD_NAME)->sumMetricsVisits($row['referer_name'], $row); + break; + + case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: + // direct entry are aggregated below in $this->metricsByType array + break; + + default: + throw new Exception("Non expected referer_type = " . $row['referer_type']); + break; + } + $this->getDataArray(self::REFERER_TYPE_RECORD_NAME)->sumMetricsVisits($row['referer_type'], $row); + } + + /** + * @param $name + * @return Piwik_DataArray + */ + protected function getDataArray($name) + { + return $this->arrays[$name]; + } + + protected function aggregateFromConversions($query) + { + if ($query === false) { + return; + } + while ($row = $query->fetch()) { + $this->makeRefererTypeNonEmpty($row); + + $skipAggregateByType = $this->aggregateConversion($row); + if (!$skipAggregateByType) { + $this->getDataArray(self::REFERER_TYPE_RECORD_NAME)->sumMetricsGoals($row['referer_type'], $row); + } + } + + foreach ($this->arrays as $dataArray) { + /* @var Piwik_DataArray $dataArray */ + $dataArray->enrichMetricsWithConversions(); + } + } + + protected function aggregateConversion($row) + { + $skipAggregateByType = false; + switch ($row['referer_type']) { + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + if (empty($row['referer_keyword'])) { + $row['referer_keyword'] = Piwik_Referers_API::LABEL_KEYWORD_NOT_DEFINED; + } + + $this->getDataArray(self::SEARCH_ENGINES_RECORD_NAME)->sumMetricsGoals($row['referer_name'], $row); + $this->getDataArray(self::KEYWORDS_RECORD_NAME)->sumMetricsGoals($row['referer_keyword'], $row); + break; + + case Piwik_Common::REFERER_TYPE_WEBSITE: + $this->getDataArray(self::WEBSITES_RECORD_NAME)->sumMetricsGoals($row['referer_name'], $row); + break; + + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + if (!empty($row['referer_keyword'])) { + $this->getDataArray(self::CAMPAIGNS_RECORD_NAME)->sumMetricsGoalsPivot($row['referer_name'], $row['referer_keyword'], $row); + } + $this->getDataArray(self::CAMPAIGNS_RECORD_NAME)->sumMetricsGoals($row['referer_name'], $row); + break; + + case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: + // Direct entry, no sub dimension + break; + + default: + // The referer type is user submitted for goal conversions, we ignore any malformed value + // Continue to the next while iteration + $skipAggregateByType = true; + break; + } + return $skipAggregateByType; + } + + /** + * Records the daily stats (numeric or datatable blob) into the archive tables. + * + * @param Piwik_ArchiveProcessor $this->getProcessor() + */ + protected function recordDayReports() + { + $this->recordDayNumeric(); + $this->recordDayBlobs(); + } + + protected function recordDayNumeric() + { + $numericRecords = array( + self::METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME => count($this->getDataArray(self::SEARCH_ENGINES_RECORD_NAME)), + self::METRIC_DISTINCT_KEYWORD_RECORD_NAME => count($this->getDataArray(self::KEYWORDS_RECORD_NAME)), + self::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME => count($this->getDataArray(self::CAMPAIGNS_RECORD_NAME)), + self::METRIC_DISTINCT_WEBSITE_RECORD_NAME => count($this->getDataArray(self::WEBSITES_RECORD_NAME)), + self::METRIC_DISTINCT_URLS_RECORD_NAME => count($this->distinctUrls), + ); + + $this->getProcessor()->insertNumericRecords($numericRecords); + } + + protected function recordDayBlobs() + { + foreach ($this->getRecordNames() as $recordName) { + $dataArray = $this->getDataArray($recordName); + $table = $this->getProcessor()->getDataTableFromDataArray($dataArray); + $blob = $table->getSerialized($this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation); + $this->getProcessor()->insertBlobRecord($recordName, $blob); + } + } + + public function archivePeriod() + { + $dataTableToSum = $this->getRecordNames(); + $nameToCount = $this->getProcessor()->aggregateDataTableReports($dataTableToSum, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation); + + $mappingFromArchiveName = array( + self::METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME => + array('typeCountToUse' => 'level0', + 'nameTableToUse' => self::SEARCH_ENGINES_RECORD_NAME, + ), + self::METRIC_DISTINCT_KEYWORD_RECORD_NAME => + array('typeCountToUse' => 'level0', + 'nameTableToUse' => self::KEYWORDS_RECORD_NAME, + ), + self::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME => + array('typeCountToUse' => 'level0', + 'nameTableToUse' => self::CAMPAIGNS_RECORD_NAME, + ), + self::METRIC_DISTINCT_WEBSITE_RECORD_NAME => + array('typeCountToUse' => 'level0', + 'nameTableToUse' => self::WEBSITES_RECORD_NAME, + ), + self::METRIC_DISTINCT_URLS_RECORD_NAME => + array('typeCountToUse' => 'recursive', + 'nameTableToUse' => self::WEBSITES_RECORD_NAME, + ), + ); + + foreach ($mappingFromArchiveName as $name => $infoMapping) { + $typeCountToUse = $infoMapping['typeCountToUse']; + $nameTableToUse = $infoMapping['nameTableToUse']; + + if ($typeCountToUse == 'recursive') { + + $countValue = $nameToCount[$nameTableToUse]['recursive'] + - $nameToCount[$nameTableToUse]['level0']; + } else { + $countValue = $nameToCount[$nameTableToUse]['level0']; + } + $this->getProcessor()->insertNumericRecord($name, $countValue); + } + } +}
\ No newline at end of file diff --git a/plugins/Referers/Controller.php b/plugins/Referers/Controller.php index 6aee58ed5c..60259d57aa 100644 --- a/plugins/Referers/Controller.php +++ b/plugins/Referers/Controller.php @@ -437,7 +437,7 @@ class Piwik_Referers_Controller extends Piwik_Controller $value = 0; $row = $dataTableReferersType->getRowFromLabel($columnId); if ($row !== false) { - $value = $row->getColumn(Piwik_Archive::INDEX_NB_VISITS); + $value = $row->getColumn(Piwik_Metrics::INDEX_NB_VISITS); } $return[$nameVar] = $value; } diff --git a/plugins/Referers/Referers.php b/plugins/Referers/Referers.php index 3e40f2d9a9..2c45b57592 100644 --- a/plugins/Referers/Referers.php +++ b/plugins/Referers/Referers.php @@ -19,21 +19,14 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/Referers/functions.php'; */ class Piwik_Referers extends Piwik_Plugin { - public $archiveProcessing; - protected $columnToSortByBeforeTruncation; - protected $maximumRowsInDataTableLevelZero; - protected $maximumRowsInSubDataTable; - public function getInformation() { - $info = array( + return array( 'description' => Piwik_Translate('Referers_PluginDescription'), 'author' => 'Piwik', 'author_homepage' => 'http://piwik.org/', 'version' => Piwik_Version::VERSION, ); - - return $info; } function getListHooksRegistered() @@ -234,7 +227,7 @@ class Piwik_Referers extends Piwik_Plugin Piwik_AddWidget('Referers_Referers', 'Referers_WidgetCampaigns', 'Referers', 'getCampaigns'); Piwik_AddWidget('Referers_Referers', 'Referers_WidgetOverview', 'Referers', 'getRefererType'); Piwik_AddWidget('Referers_Referers', 'Referers_WidgetGetAll', 'Referers', 'getAll'); - if (Piwik_Archive::isSegmentationEnabled()) { + if (Piwik::isSegmentationEnabled()) { Piwik_AddWidget('SEO', 'Referers_WidgetTopKeywordsForPages', 'Referers', 'getKeywordsForPage'); } } @@ -289,300 +282,33 @@ class Piwik_Referers extends Piwik_Plugin )); } - function __construct() - { - $this->columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS; - $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_referers']; - $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers']; - } - - /** - * Period archiving: sums up daily stats and sums report tables, - * making sure that tables are still truncated. - * - * @param Piwik_Event_Notification $notification notification object - * @return void - */ - function archivePeriod($notification) - { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $dataTableToSum = array( - 'Referers_type', - 'Referers_keywordBySearchEngine', - 'Referers_searchEngineByKeyword', - 'Referers_keywordByCampaign', - 'Referers_urlByWebsite', - ); - $nameToCount = $archiveProcessing->archiveDataTable($dataTableToSum, null, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation); - - $mappingFromArchiveName = array( - 'Referers_distinctSearchEngines' => - array('typeCountToUse' => 'level0', - 'nameTableToUse' => 'Referers_keywordBySearchEngine', - ), - 'Referers_distinctKeywords' => - array('typeCountToUse' => 'level0', - 'nameTableToUse' => 'Referers_searchEngineByKeyword', - ), - 'Referers_distinctCampaigns' => - array('typeCountToUse' => 'level0', - 'nameTableToUse' => 'Referers_keywordByCampaign', - ), - 'Referers_distinctWebsites' => - array('typeCountToUse' => 'level0', - 'nameTableToUse' => 'Referers_urlByWebsite', - ), - 'Referers_distinctWebsitesUrls' => - array('typeCountToUse' => 'recursive', - 'nameTableToUse' => 'Referers_urlByWebsite', - ), - ); - - foreach ($mappingFromArchiveName as $name => $infoMapping) { - $typeCountToUse = $infoMapping['typeCountToUse']; - $nameTableToUse = $infoMapping['nameTableToUse']; - - if ($typeCountToUse == 'recursive') { - - $countValue = $nameToCount[$nameTableToUse]['recursive'] - - $nameToCount[$nameTableToUse]['level0']; - } else { - $countValue = $nameToCount[$nameTableToUse]['level0']; - } - $archiveProcessing->insertNumericRecord($name, $countValue); - } - } - - const LABEL_KEYWORD_NOT_DEFINED = ""; - - static public function getKeywordNotDefinedString() - { - return Piwik_Translate('General_NotDefined', Piwik_Translate('Referers_ColumnKeyword')); - } - - static public function getCleanKeyword($label) - { - return $label == Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED - ? self::getKeywordNotDefinedString() - : $label; - } - /** * Hooks on daily archive to trigger various log processing * * @param Piwik_Event_Notification $notification notification object - * @return void */ public function archiveDay($notification) { - /** - * @var Piwik_ArchiveProcessing_Day - */ - $this->archiveProcessing = $notification->getNotificationObject(); - if (!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->archiveDayAggregateVisits($this->archiveProcessing); - $this->archiveDayAggregateGoals($this->archiveProcessing); - Piwik_PostEvent('Referers.archiveDay', $this); - $this->archiveDayRecordInDatabase($this->archiveProcessing); - $this->cleanup(); - } - - protected function cleanup() - { - destroy($this->interestBySearchEngine); - destroy($this->interestByKeyword); - destroy($this->interestBySearchEngineAndKeyword); - destroy($this->interestByKeywordAndSearchEngine); - destroy($this->interestByWebsite); - destroy($this->interestByWebsiteAndUrl); - destroy($this->interestByCampaignAndKeyword); - destroy($this->interestByCampaign); - destroy($this->interestByType); - destroy($this->distinctUrls); - } - - /** - * Daily archive: processes all Referers reports, eg. Visits by Keyword, - * Visits by websites, etc. - * - * @param Piwik_ArchiveProcessing $archiveProcessing - * @throws Exception - * @return void - */ - protected function archiveDayAggregateVisits(Piwik_ArchiveProcessing_Day $archiveProcessing) - { - $dimension = array("referer_type", "referer_name", "referer_keyword", "referer_url"); - $query = $archiveProcessing->queryVisitsByDimension($dimension); - - $this->interestBySearchEngine = - $this->interestByKeyword = - $this->interestBySearchEngineAndKeyword = - $this->interestByKeywordAndSearchEngine = - $this->interestByWebsite = - $this->interestByWebsiteAndUrl = - $this->interestByCampaignAndKeyword = - $this->interestByCampaign = - $this->interestByType = - $this->distinctUrls = array(); - while ($row = $query->fetch()) { - if (empty($row['referer_type'])) { - $row['referer_type'] = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - } else { - switch ($row['referer_type']) { - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - if (empty($row['referer_keyword'])) { - $row['referer_keyword'] = self::LABEL_KEYWORD_NOT_DEFINED; - } - if (!isset($this->interestBySearchEngine[$row['referer_name']])) $this->interestBySearchEngine[$row['referer_name']] = $archiveProcessing->getNewInterestRow(); - if (!isset($this->interestByKeyword[$row['referer_keyword']])) $this->interestByKeyword[$row['referer_keyword']] = $archiveProcessing->getNewInterestRow(); - if (!isset($this->interestBySearchEngineAndKeyword[$row['referer_name']][$row['referer_keyword']])) $this->interestBySearchEngineAndKeyword[$row['referer_name']][$row['referer_keyword']] = $archiveProcessing->getNewInterestRow(); - if (!isset($this->interestByKeywordAndSearchEngine[$row['referer_keyword']][$row['referer_name']])) $this->interestByKeywordAndSearchEngine[$row['referer_keyword']][$row['referer_name']] = $archiveProcessing->getNewInterestRow(); - - $archiveProcessing->updateInterestStats($row, $this->interestBySearchEngine[$row['referer_name']]); - $archiveProcessing->updateInterestStats($row, $this->interestByKeyword[$row['referer_keyword']]); - $archiveProcessing->updateInterestStats($row, $this->interestBySearchEngineAndKeyword[$row['referer_name']][$row['referer_keyword']]); - $archiveProcessing->updateInterestStats($row, $this->interestByKeywordAndSearchEngine[$row['referer_keyword']][$row['referer_name']]); - break; - - case Piwik_Common::REFERER_TYPE_WEBSITE: - - if (!isset($this->interestByWebsite[$row['referer_name']])) $this->interestByWebsite[$row['referer_name']] = $archiveProcessing->getNewInterestRow(); - $archiveProcessing->updateInterestStats($row, $this->interestByWebsite[$row['referer_name']]); - - if (!isset($this->interestByWebsiteAndUrl[$row['referer_name']][$row['referer_url']])) $this->interestByWebsiteAndUrl[$row['referer_name']][$row['referer_url']] = $archiveProcessing->getNewInterestRow(); - $archiveProcessing->updateInterestStats($row, $this->interestByWebsiteAndUrl[$row['referer_name']][$row['referer_url']]); + $archiveProcessor = $notification->getNotificationObject(); - if (!isset($this->distinctUrls[$row['referer_url']])) { - $this->distinctUrls[$row['referer_url']] = true; - } - break; - - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - if (!empty($row['referer_keyword'])) { - if (!isset($this->interestByCampaignAndKeyword[$row['referer_name']][$row['referer_keyword']])) $this->interestByCampaignAndKeyword[$row['referer_name']][$row['referer_keyword']] = $archiveProcessing->getNewInterestRow(); - $archiveProcessing->updateInterestStats($row, $this->interestByCampaignAndKeyword[$row['referer_name']][$row['referer_keyword']]); - } - if (!isset($this->interestByCampaign[$row['referer_name']])) $this->interestByCampaign[$row['referer_name']] = $archiveProcessing->getNewInterestRow(); - $archiveProcessing->updateInterestStats($row, $this->interestByCampaign[$row['referer_name']]); - break; - - case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: - // direct entry are aggregated below in $this->interestByType array - break; - - default: - throw new Exception("Non expected referer_type = " . $row['referer_type']); - break; - } - } - if (!isset($this->interestByType[$row['referer_type']])) $this->interestByType[$row['referer_type']] = $archiveProcessing->getNewInterestRow(); - $archiveProcessing->updateInterestStats($row, $this->interestByType[$row['referer_type']]); + $archiving = new Piwik_Referers_Archiver($archiveProcessor); + if ($archiving->shouldArchive()) { + $archiving->archiveDay(); } } /** - * Daily Goal archiving: processes reports of Goal conversions by Keyword, - * Goal conversions by Referer Websites, etc. - * - * @param Piwik_ArchiveProcessing $archiveProcessing - * @return void - */ - protected function archiveDayAggregateGoals($archiveProcessing) - { - $query = $archiveProcessing->queryConversionsByDimension(array("referer_type", "referer_name", "referer_keyword")); - - if ($query === false) return; - while ($row = $query->fetch()) { - if (empty($row['referer_type'])) { - $row['referer_type'] = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - } else { - switch ($row['referer_type']) { - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - if (empty($row['referer_keyword'])) { - $row['referer_keyword'] = self::LABEL_KEYWORD_NOT_DEFINED; - } - if (!isset($this->interestBySearchEngine[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestBySearchEngine[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - if (!isset($this->interestByKeyword[$row['referer_keyword']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByKeyword[$row['referer_keyword']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - - $archiveProcessing->updateGoalStats($row, $this->interestBySearchEngine[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - $archiveProcessing->updateGoalStats($row, $this->interestByKeyword[$row['referer_keyword']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - break; - - case Piwik_Common::REFERER_TYPE_WEBSITE: - if (!isset($this->interestByWebsite[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByWebsite[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - $archiveProcessing->updateGoalStats($row, $this->interestByWebsite[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - break; - - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - if (!empty($row['referer_keyword'])) { - if (!isset($this->interestByCampaignAndKeyword[$row['referer_name']][$row['referer_keyword']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCampaignAndKeyword[$row['referer_name']][$row['referer_keyword']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - $archiveProcessing->updateGoalStats($row, $this->interestByCampaignAndKeyword[$row['referer_name']][$row['referer_keyword']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - } - if (!isset($this->interestByCampaign[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCampaign[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - $archiveProcessing->updateGoalStats($row, $this->interestByCampaign[$row['referer_name']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - break; - - case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: - // Direct entry, no sub dimension - break; - - default: - // The referer type is user submitted for goal conversions, we ignore any malformed value - // Continue to the next while iteration - continue 2; - break; - } - } - if (!isset($this->interestByType[$row['referer_type']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByType[$row['referer_type']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - $archiveProcessing->updateGoalStats($row, $this->interestByType[$row['referer_type']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); - } - - $archiveProcessing->enrichConversionsByLabelArray($this->interestByType); - $archiveProcessing->enrichConversionsByLabelArray($this->interestBySearchEngine); - $archiveProcessing->enrichConversionsByLabelArray($this->interestByKeyword); - $archiveProcessing->enrichConversionsByLabelArray($this->interestByWebsite); - $archiveProcessing->enrichConversionsByLabelArray($this->interestByCampaign); - $archiveProcessing->enrichConversionsByLabelArrayHasTwoLevels($this->interestByCampaignAndKeyword); - } - - /** - * Records the daily stats (numeric or datatable blob) into the archive tables. + * Period archiving: sums up daily stats and sums report tables, + * making sure that tables are still truncated. * - * @param Piwik_ArchiveProcessing $archiveProcessing - * @return void + * @param Piwik_Event_Notification $notification notification object */ - protected function archiveDayRecordInDatabase($archiveProcessing) + function archivePeriod($notification) { - $numericRecords = array( - 'Referers_distinctSearchEngines' => count($this->interestBySearchEngineAndKeyword), - 'Referers_distinctKeywords' => count($this->interestByKeywordAndSearchEngine), - 'Referers_distinctCampaigns' => count($this->interestByCampaign), - 'Referers_distinctWebsites' => count($this->interestByWebsite), - 'Referers_distinctWebsitesUrls' => count($this->distinctUrls), - ); - - foreach ($numericRecords as $name => $value) { - $archiveProcessing->insertNumericRecord($name, $value); - } - - $dataTable = $archiveProcessing->getDataTableSerialized($this->interestByType); - $archiveProcessing->insertBlobRecord('Referers_type', $dataTable); - destroy($dataTable); - - $blobRecords = array( - 'Referers_keywordBySearchEngine' => $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestBySearchEngineAndKeyword, $this->interestBySearchEngine), - 'Referers_searchEngineByKeyword' => $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestByKeywordAndSearchEngine, $this->interestByKeyword), - 'Referers_keywordByCampaign' => $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestByCampaignAndKeyword, $this->interestByCampaign), - 'Referers_urlByWebsite' => $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestByWebsiteAndUrl, $this->interestByWebsite), - ); - foreach ($blobRecords as $recordName => $table) { - $blob = $table->getSerialized($this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation); - $archiveProcessing->insertBlobRecord($recordName, $blob); - destroy($table); + $archiveProcessor = $notification->getNotificationObject(); + $archiving = new Piwik_Referers_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); } } } diff --git a/plugins/Referers/functions.php b/plugins/Referers/functions.php index c7d69386c7..46d9e18000 100644 --- a/plugins/Referers/functions.php +++ b/plugins/Referers/functions.php @@ -178,7 +178,7 @@ function Piwik_getSearchEngineHostPathFromUrl($url) */ function Piwik_getSearchEngineUrlFromUrlAndKeyword($url, $keyword) { - if ($keyword === Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED) { + if ($keyword === Piwik_Referers_API::LABEL_KEYWORD_NOT_DEFINED) { return 'http://piwik.org/faq/general/#faq_144'; } $searchEngineUrls = Piwik_Common::getSearchEngineUrls(); diff --git a/plugins/Referers/images/searchEngines/search.imesh.com.png b/plugins/Referers/images/searchEngines/search.imesh.com.png Binary files differnew file mode 100644 index 0000000000..8d8f1a782e --- /dev/null +++ b/plugins/Referers/images/searchEngines/search.imesh.com.png diff --git a/plugins/Referers/images/searchEngines/search.snap.do.png b/plugins/Referers/images/searchEngines/search.snap.do.png Binary files differnew file mode 100644 index 0000000000..fc8131e8bc --- /dev/null +++ b/plugins/Referers/images/searchEngines/search.snap.do.png diff --git a/plugins/Referers/images/searchEngines/www.talimba.com.png b/plugins/Referers/images/searchEngines/www.talimba.com.png Binary files differnew file mode 100644 index 0000000000..4403a249b1 --- /dev/null +++ b/plugins/Referers/images/searchEngines/www.talimba.com.png diff --git a/plugins/Referers/images/searchEngines/www.trusted--search.com.png b/plugins/Referers/images/searchEngines/www.trusted-search.com.png Binary files differindex 0740cef1a7..0740cef1a7 100644 --- a/plugins/Referers/images/searchEngines/www.trusted--search.com.png +++ b/plugins/Referers/images/searchEngines/www.trusted-search.com.png diff --git a/plugins/SEO/API.php b/plugins/SEO/API.php index 26ac00c5bd..bc94a2663d 100644 --- a/plugins/SEO/API.php +++ b/plugins/SEO/API.php @@ -100,8 +100,6 @@ class Piwik_SEO_API $data[Piwik_Translate('SEO_Dmoz')] = $dmozRank; } - $dataTable = new Piwik_DataTable(); - $dataTable->addRowsFromArrayWithIndexLabel($data); - return $dataTable; + return Piwik_DataTable::makeFromIndexedArray($data); } } diff --git a/plugins/SEO/templates/index.twig b/plugins/SEO/templates/index.twig index 1d6e9deb22..0a147b4f62 100644 --- a/plugins/SEO/templates/index.twig +++ b/plugins/SEO/templates/index.twig @@ -1,5 +1,5 @@ <div id='SeoRanks'> - <script type="text/javascript" src="plugins/SEO/javascripts/rank.js"></script> + <script type="text/javascript" src="plugins/SEO/templates/rank.js"></script> <form method="post" style="padding: 8px;"> <div align="left" class="mediumtext"> @@ -10,8 +10,8 @@ </span> </div> - {% import 'ajaxMacros.twig' as ajax %} - {{ ajax.loadingDiv('ajaxLoadingSEO') }} + {% import "ajaxMacros.twig" as ajax %} + {{ ajax.LoadingDiv('ajaxLoadingSEO') }} <div id="rankStats" align="left" style='margin-top:10px'> {% if ranks is empty %} @@ -24,27 +24,26 @@ <table cellspacing='2' style='margin:auto;line-height:1.5em;padding-top:10px'> {% for rank in ranks %} <tr> - {% set seoLink %}<a class="linkContent" href="?module=Proxy&action=redirect&url={{ rank.logo_link|url_encode }}" target="_blank" - {% if rank.logo_tooltip is not empty %}title="{{ rank.logo_tooltip }}"{% endif %}> - {% endset %} +{% set seoLink %}{% if rank.logo_link is not empty %}<a class="linkContent" href="?module=Proxy&action=redirect&url={{ rank.logo_link|url_encode }}" target="_blank" + {% if rank.logo_tooltip is not empty %}title="{{ rank.logo_tooltip }}"{% endif %}>{% endif %}{% endset %} {% set majesticLink %}{{ seoLink }}Majestic</a>{% endset %} - <td> - {% if rank.logo_link is not empty %}{{ seoLink }}{% endif %} - <img style='vertical-align:middle;margin-right:6px;' src='{{ rank.logo }}' border='0' - alt="{{ rank.label }}">{% if rank.logo_link is not empty %}</a>{% endif %} {{ rank.label|replace({"Majestic":majesticLink}) }} + <td>{% if rank.logo_link is not empty %}{{ seoLink }}{% endif %}<img + style='vertical-align:middle;margin-right:6px;' src='{{ rank.logo }}' border='0' + alt="{{ rank.label }}">{% if rank.logo_link is not empty %}</a>{% endif %} {{ rank.label|replace({"Majestic": majesticLink}) }} </td> <td> <div style='margin-left:15px'> - {% if rank.logo_link is not empty %}{{ seoLink }}{% endif %} - {% if rank.rank is defined %}{{ rank.rank }}{% else %}-{% endif %} - {% if rank.id=='pagerank' %} /10 - {% elseif rank.id=='google-index' or rank.id=='bing-index' %} {{ 'SEO_Pages'|translate }} - {% endif %} + {% if rank.logo_link is not empty }{{ seoLink }}{% endif %} + {% if rank.rank %}{{ rank.rank }}{% else% }-{% endif %} + {% if rank.id=='pagerank' %} /10 + {% elseif rank.id=='google-index' or rank.id=='bing-index' %} {{ 'SEO_Pages'|translate }} + {% endif %} {% if rank.logo_link is not empty %}</a>{% endif %} </div> </td> </tr> {% endfor %} + </table> {% endif %} </div> diff --git a/plugins/SegmentEditor/API.php b/plugins/SegmentEditor/API.php index 19083f8e42..ad420009ea 100644 --- a/plugins/SegmentEditor/API.php +++ b/plugins/SegmentEditor/API.php @@ -10,12 +10,14 @@ */ /** - * The SegmentEditor API lets you add, update, delete custom Segments, and list saved segments. + * The SegmentEditor API lets you add, update, delete custom Segments, and list saved segments.a * * @package Piwik_SegmentEditor */ class Piwik_SegmentEditor_API { + const DELETE_SEGMENT_EVENT = 'SegmentEditor.delete'; + static private $instance = null; /** @@ -31,12 +33,19 @@ class Piwik_SegmentEditor_API protected function checkSegmentValue($definition, $idSite) { + // unsanitize so we don't record the HTML entitied segment + $definition = Piwik_Common::unsanitizeInputValue($definition); + $definition = str_replace("#", '%23', $definition); // hash delimiter + $definition = str_replace("'", '%27', $definition); // not encoded in JS + $definition = str_replace("&", '%26', $definition); + try { $segment = new Piwik_Segment($definition, $idSite); $segment->getHash(); } catch (Exception $e) { throw new Exception("The specified segment is invalid: " . $e->getMessage()); } + return $definition; } protected function checkSegmentName($name) @@ -52,16 +61,11 @@ class Piwik_SegmentEditor_API if ($enabledAllUsers && !Piwik::isUserIsSuperUser() ) { - throw new Exception("&enabledAllUsers=1 requires Super User access"); + throw new Exception("enabledAllUsers=1 requires Super User access"); } return $enabledAllUsers; } - - /** - * @param $idSite - * @throws Exception - */ protected function checkIdSite($idSite) { if (empty($idSite)) { @@ -74,6 +78,8 @@ class Piwik_SegmentEditor_API } Piwik::checkUserHasViewAccess($idSite); } + $idSite = (int)$idSite; + return $idSite; } protected function checkAutoArchive($autoArchive, $idSite) @@ -94,23 +100,63 @@ class Piwik_SegmentEditor_API return $autoArchive; } + protected function getSegmentOrFail($idSegment) + { + $segment = $this->get($idSegment); + + if (empty($segment)) { + throw new Exception("Requested segment not found"); + } + return $segment; + } + + protected function checkUserIsNotAnonymous() + { + if(Piwik::isUserIsAnonymous()) { + throw new Exception("To create, edit or delete Custom Segments, please sign in first."); + } + } + + /** + * Deletes a stored segment. + * + * @param $idSegment + */ public function delete($idSegment) { + $this->checkUserIsNotAnonymous(); + + // allow plugins using the segment to throw an exception or propagate the deletion + Piwik_PostEvent(self::DELETE_SEGMENT_EVENT, $idSegment); + $segment = $this->getSegmentOrFail($idSegment); $db = Zend_Registry::get('db'); $db->delete(Piwik_Common::prefixTable('segment'), 'idsegment = ' . $idSegment); return true; } + /** + * Modifies an existing stored segment. + * + * @param $idSegment The ID of the stored segment to modify. + * @param $name The new name of the segment. + * @param $definition The new definition of the segment. + * @param bool $idSite If supplied, associates the stored segment with as single site. + * @param bool $autoArchive Whether to automatically archive data with the segment or not. + * @param bool $enabledAllUsers Whether the stored segment is viewable by all users or just the one that created it. + * + */ public function update($idSegment, $name, $definition, $idSite = false, $autoArchive = false, $enabledAllUsers = false) { - $this->checkIdSite($idSite); + $this->checkUserIsNotAnonymous(); + $segment = $this->getSegmentOrFail($idSegment); + + $idSite = $this->checkIdSite($idSite); $this->checkSegmentName($name); - $this->checkSegmentValue($definition, $idSite); + $definition = $this->checkSegmentValue($definition, $idSite); $enabledAllUsers = $this->checkEnabledAllUsers($enabledAllUsers); $autoArchive = $this->checkAutoArchive($autoArchive, $idSite); - $segment = $this->getSegmentOrFail($idSegment); $bind = array( 'name' => $name, 'definition' => $definition, @@ -128,13 +174,23 @@ class Piwik_SegmentEditor_API return true; } - + /** + * Adds a new stored segment. + * + * @param $name The new name of the segment. + * @param $definition The new definition of the segment. + * @param bool $idSite If supplied, associates the stored segment with as single site. + * @param bool $autoArchive Whether to automatically archive data with the segment or not. + * @param bool $enabledAllUsers Whether the stored segment is viewable by all users or just the one that created it. + * + * @return int The newly created segment Id + */ public function add($name, $definition, $idSite = false, $autoArchive = false, $enabledAllUsers = false) { - Piwik::checkUserIsNotAnonymous(); - $this->checkIdSite($idSite); + $this->checkUserIsNotAnonymous(); + $idSite = $this->checkIdSite($idSite); $this->checkSegmentName($name); - $this->checkSegmentValue($definition, $idSite); + $definition = $this->checkSegmentValue($definition, $idSite); $enabledAllUsers = $this->checkEnabledAllUsers($enabledAllUsers); $autoArchive = $this->checkAutoArchive($autoArchive, $idSite); @@ -153,25 +209,11 @@ class Piwik_SegmentEditor_API return $db->lastInsertId(); } - public function getSegmentsToAutoArchive($idSite = false) - { - Piwik::checkUserIsSuperUser(); - - $sqlRestrictSite = ''; - $bind = array(); - if ($idSite) { - $sqlRestrictSite = 'OR enable_only_idsite = ?'; - $bind = array($idSite); - } - $segments = Zend_Registry::get('db')->fetchAll("SELECT * - FROM " . Piwik_Common::prefixTable("segment") . " - WHERE auto_archive = 1 - AND deleted = 0 - AND (enable_only_idsite IS NULL " . $sqlRestrictSite . " )", $bind - ); - return $segments; - } - + /** + * Returns a stored segment by ID + * + * @param $idSegment + */ public function get($idSegment) { Piwik::checkUserHasSomeViewAccess(); @@ -179,8 +221,8 @@ class Piwik_SegmentEditor_API throw new Exception("idSegment should be numeric."); } $segment = Zend_Registry::get('db')->fetchRow("SELECT * " . - " FROM " . Piwik_Common::prefixTable("segment") . - " WHERE idsegment = ?", $idSegment); + " FROM " . Piwik_Common::prefixTable("segment") . + " WHERE idsegment = ?", $idSegment); if (empty($segment)) { return false; @@ -188,40 +230,54 @@ class Piwik_SegmentEditor_API try { Piwik::checkUserIsSuperUserOrTheUser($segment['login']); } catch (Exception $e) { - throw new Exception("You can only manage your own segments (unless you are Super User)."); + throw new Exception("You can only edit the custom segments you have created yourself. This segment was created and 'shared with you' by the Super User. " . + "To modify this segment, you can first create a new one by clicking on 'Add new segment'. Then you can customize the segment's definition."); } if ($segment['deleted']) { - throw new Exception("This segment is marked as deleted."); + throw new Exception("This segment is marked as deleted. "); } return $segment; } /** - * @param $idSegment - * @throws Exception + * Returns all stored segments. + * + * @param bool $idSite Whether to return stored segments that are only auto-archived for a specific idSite, or all of them. If supplied, must be a valid site ID. + * @param bool $returnOnlyAutoArchived Whether to only return stored segments that are auto-archived or not. + * @return array */ - protected function getSegmentOrFail($idSegment) + public function getAll($idSite = false, $returnOnlyAutoArchived = false) { - $segment = $this->get($idSegment); + if(!empty($idSite) ) { + Piwik::checkUserHasViewAccess($idSite); + } else { + Piwik::checkUserHasSomeViewAccess(); + } + $bind = array(); - if (empty($segment)) { - throw new Exception("Requested segment not found"); + // Build basic segment filtering + $whereIdSite = ''; + if(!empty($idSite)) { + $whereIdSite = 'enable_only_idsite = ? OR '; + $bind[] = $idSite; } - return $segment; - } - public function getAll($idSite) - { - Piwik::checkUserHasViewAccess($idSite); + $bind[] = Piwik::getCurrentUserLogin(); + + $extraWhere = ''; + if($returnOnlyAutoArchived) { + $extraWhere = ' AND auto_archive = 1'; + } + // Query $sql = "SELECT * " . - " FROM " . Piwik_Common::prefixTable("segment") . - " WHERE (enable_only_idsite = ? OR enable_only_idsite IS NULL) - AND (enable_all_users = 1 OR login = ?) - AND deleted = 0 - ORDER BY name ASC"; - $bind = array($idSite, Piwik::getCurrentUserLogin()); + " FROM " . Piwik_Common::prefixTable("segment") . + " WHERE ($whereIdSite enable_only_idsite = 0) + AND (enable_all_users = 1 OR login = ?) + AND deleted = 0 + $extraWhere + ORDER BY name ASC"; $segments = Zend_Registry::get('db')->fetchAll($sql, $bind); return $segments; diff --git a/plugins/SegmentEditor/Controller.php b/plugins/SegmentEditor/Controller.php index aeb69978f8..7c96058ddb 100644 --- a/plugins/SegmentEditor/Controller.php +++ b/plugins/SegmentEditor/Controller.php @@ -24,29 +24,67 @@ class Piwik_SegmentEditor_Controller extends Piwik_Controller $segmentsByCategory = $customVariablesSegments = array(); foreach($segments as $segment) { - if($segment['category'] == 'Visit' + if($segment['category'] == Piwik_Translate('General_Visit') && $segment['type'] == 'metric') { - $segment['category'] .= ' (' . lcfirst(Piwik_Translate('General_Metrics')) . ')'; + $metricsLabel = Piwik_Translate('General_Metrics'); + $metricsLabel[0] = strtolower($metricsLabel[0]); + $segment['category'] .= ' (' . $metricsLabel . ')'; } $segmentsByCategory[$segment['category']][] = $segment; } - uksort($segmentsByCategory, array($this, 'sortCustomVariablesLast')); + uksort($segmentsByCategory, array($this, 'sortSegmentCategories')); $view->segmentsByCategory = $segmentsByCategory; - $savedSegments = Piwik_SegmentEditor_API::getInstance()->getAll($idSite); + foreach($savedSegments as &$savedSegment) { + $savedSegment['name'] = Piwik_Common::sanitizeInputValue($savedSegment['name']); + } $view->savedSegmentsJson = Piwik_Common::json_encode($savedSegments); + $view->authorizedToCreateSegments = !Piwik::isUserIsAnonymous(); + $view->segmentTranslations = Piwik_Common::json_encode($this->getTranslations()); $out = $view->render(); echo $out; } - public function sortCustomVariablesLast($a, $b) + public function sortSegmentCategories($a, $b) { + // Custom Variables last if($a == Piwik_Translate('CustomVariables_CustomVariables')) { return 1; } - return -1; + return 0; + } + + private function getTranslations() + { + $translationKeys = array( + 'General_OperationEquals', + 'General_OperationNotEquals', + 'General_OperationAtMost', + 'General_OperationAtLeast', + 'General_OperationLessThan', + 'General_OperationGreaterThan', + 'General_OperationContains', + 'General_OperationDoesNotContain', + 'General_OperationIs', + 'General_OperationIsNot', + 'General_OperationContains', + 'General_OperationDoesNotContain', + 'SegmentEditor_DefaultAllVisits', + 'General_DefaultAppended', + 'SegmentEditor_AddNewSegment', + 'General_Edit', + 'General_Search', + 'General_SearchNoResults', + '', + '', + '', + ); + foreach($translationKeys as $key) { + $translations[$key] = Piwik_Translate($key); + } + return $translations; } } diff --git a/plugins/SegmentEditor/SegmentEditor.php b/plugins/SegmentEditor/SegmentEditor.php index 7615873a95..d95f9b8a8c 100644 --- a/plugins/SegmentEditor/SegmentEditor.php +++ b/plugins/SegmentEditor/SegmentEditor.php @@ -45,9 +45,9 @@ class Piwik_SegmentEditor extends Piwik_Plugin public function getKnownSegmentsToArchiveAllSites($notification) { $segments =& $notification->getNotificationObject(); - $segmentToAutoArchive = Piwik_SegmentEditor_API::getInstance()->getSegmentsToAutoArchive(); - if (!empty($segmentToAutoArchive)) { - $segments = array_merge($segments, $segmentToAutoArchive); + $segmentsToAutoArchive = Piwik_SegmentEditor_API::getInstance()->getAll($idSite = false, $returnAutoArchived = true); + foreach ($segmentsToAutoArchive as $segment) { + $segments[] = $segment['definition']; } } @@ -55,7 +55,7 @@ class Piwik_SegmentEditor extends Piwik_Plugin { $segments =& $notification->getNotificationObject(); $idSite = $notification->getNotificationInfo(); - $segmentToAutoArchive = Piwik_SegmentEditor_API::getInstance()->getSegmentsToAutoArchive($idSite); + $segmentToAutoArchive = Piwik_SegmentEditor_API::getInstance()->getAll($idSite, $returnAutoArchived = true); foreach ($segmentToAutoArchive as $segmentInfo) { $segments[] = $segmentInfo['definition']; diff --git a/plugins/SegmentEditor/images/dashboard_h_bg_hover.png b/plugins/SegmentEditor/images/dashboard_h_bg_hover.png Binary files differindex 37e4bd54e8..e46d8cc6df 100644 --- a/plugins/SegmentEditor/images/dashboard_h_bg_hover.png +++ b/plugins/SegmentEditor/images/dashboard_h_bg_hover.png diff --git a/plugins/SegmentEditor/javascripts/Segmentation.js b/plugins/SegmentEditor/javascripts/Segmentation.js index d18dc9ee47..16bd22f5d4 100644 --- a/plugins/SegmentEditor/javascripts/Segmentation.js +++ b/plugins/SegmentEditor/javascripts/Segmentation.js @@ -10,12 +10,12 @@ Segmentation = (function($) { var segmentation = function segmentation(config) { var self = this; - // set defaults for widget + self.currentSegmentStr = ""; self.targetId = "segmentEditorPanel"; self.segmentAccess = "read"; - self.segmentList = []; - // ----------- + self.availableSegments = []; + for(var item in config) { self[item] = config[item]; @@ -23,35 +23,40 @@ Segmentation = (function($) { self.timer = ""; // variable for further use in timing events self.searchAllowed = true; - //---------- self.availableMatches = []; self.availableMatches["metric"] = []; - self.availableMatches["metric"]["=="] = "Equals"; - self.availableMatches["metric"]["!="] = "Not Equals"; - self.availableMatches["metric"]["<="] = "At most"; - self.availableMatches["metric"][">="] = "At least"; - self.availableMatches["metric"]["<"] = "Less than"; - self.availableMatches["metric"][">"] = "Greater than"; + self.availableMatches["metric"]["=="] = self.translations['General_OperationEquals']; + self.availableMatches["metric"]["!="] = self.translations['General_OperationNotEquals']; + self.availableMatches["metric"]["<="] = self.translations['General_OperationAtMost']; + self.availableMatches["metric"][">="] = self.translations['General_OperationAtLeast']; + self.availableMatches["metric"]["<"] = self.translations['General_OperationLessThan']; + self.availableMatches["metric"][">"] = self.translations['General_OperationGreaterThan']; self.availableMatches["dimension"] = []; - self.availableMatches["dimension"]["=="] = "Is"; - self.availableMatches["dimension"]["!="] = "Is not"; - self.availableMatches["dimension"]["=@"] = "Contains"; - self.availableMatches["dimension"]["!@"] = "Does not contain"; + self.availableMatches["dimension"]["=="] = self.translations['General_OperationIs']; + self.availableMatches["dimension"]["!="] = self.translations['General_OperationIsNot']; + self.availableMatches["dimension"]["=@"] = self.translations['General_OperationContains']; + self.availableMatches["dimension"]["!@"] = self.translations['General_OperationDoesNotContain']; segmentation.prototype.getSegment = function(){ var self = this; - return self.currentSegmentStr; + if($.browser.mozilla) { + return self.currentSegmentStr; + } + return decodeURIComponent(self.currentSegmentStr); } var setSegment = function(segmentStr){ + if(!$.browser.mozilla) { + segmentStr = encodeURIComponent(segmentStr); + } self.currentSegmentStr = segmentStr; } segmentation.prototype.shortenSegmentName = function(name, length){ - if(typeof length === "undefined") length = 30; + if(typeof length === "undefined") length = 26; if(typeof name === "undefined") name = ""; var i; @@ -73,24 +78,22 @@ Segmentation = (function($) { var markCurrentSegment = function(){ var current = self.getSegment(); -// window.setTimeout(function(){ - var segmentationTitle = $(self.content).find(".segmentationTitle"); - if( current != "") - { - var foundItems = $(self.content).find('div.segmentList > ul > li[data-definition="'+current+'"]'); - if( foundItems.length > 0) - { - var name = $(foundItems).first().find("span.segname").text(); - segmentationTitle.html("<b>"+name+"</b>"); - } - else{ - segmentationTitle.html("<b>Custom Segment</b>"); - } - } - else { - $(self.content).find(".segmentationTitle").text("All visits"); + + var segmentationTitle = $(self.content).find(".segmentationTitle"); + if( current != "") + { + var selector = 'div.segmentList ul li[data-definition="'+current+'"]'; + var foundItems = $(selector); + if( foundItems.length > 0) { + var name = $(foundItems).first().find("span.segname").text(); + segmentationTitle.html("<b>"+name+"</b>"); + } else { + segmentationTitle.html("<b>Custom Segment</b>"); } -// }, 20); + } + else { + $(self.content).find(".segmentationTitle").text(self.translations['SegmentEditor_DefaultAllVisits']); + } } var getAndDiv = function(){ @@ -179,35 +182,50 @@ Segmentation = (function($) { $(self.form).find(".segment-content").append(getInitialStateRowsHtml()); doDragDropBindings(); } - + + var getSegmentFromId = function (id) { + if(self.availableSegments.length > 0) { + for(var i = 0; i < self.availableSegments.length; i++) + { + segment = self.availableSegments[i]; + if(segment.idsegment == id) { + return segment; + } + } + } + return false; + } + var getListHtml = function() { var html = $("#SegmentEditor > .listHtml").clone(); var segment, injClass; var listHtml = '<li data-idsegment="" ' + - (self.currentSegmentsGlobal == "" ? " class='segmentSelected' " : "") - + ' data-definition=""><span class="segname">All Visits (default)</span></li> '; - if(self.segmentList.length > 0) { - for(var key in self.segmentList) + (self.currentSegmentStr == "" ? " class='segmentSelected' " : "") + + ' data-definition=""><span class="segname">' + self.translations['SegmentEditor_DefaultAllVisits'] + + ' ' + self.translations['General_DefaultAppended'] + + '</span></li> '; + if(self.availableSegments.length > 0) { + for(var i = 0; i < self.availableSegments.length; i++) { - segment = self.segmentList[key]; + segment = self.availableSegments[i]; injClass = ""; - if( segment.definition == self.currentSegmentsGlobal){ + if( segment.definition == self.currentSegmentStr){ injClass = 'class="segmentSelected"'; } - listHtml += '<li data-idsegment="'+segment.idsegment+'" data-definition=\''+segment.definition+'\' ' + listHtml += '<li data-idsegment="'+segment.idsegment+'" data-definition="'+ (segment.definition).replace(/"/g, '"') +'" ' + injClass +' title="'+segment.name+'"><span class="segname">' + self.shortenSegmentName(segment.name)+'</span>'; if(self.segmentAccess == "write") { - listHtml += '<span class="editSegment">[edit]</span>'; + listHtml += '<span class="editSegment">['+ self.translations['General_Edit'].toLocaleLowerCase() +']</span>'; } listHtml += '</li>'; } $(html).find(".segmentList > ul").append(listHtml); if(self.segmentAccess === "write"){ - $(html).find(".add_new_segment").html(_pk_translate('General_AddNewSegment_js')); + $(html).find(".add_new_segment").html(self.translations['SegmentEditor_AddNewSegment']); } - else{ + else { $(html).find(".add_new_segment").hide();; } } @@ -224,12 +242,15 @@ Segmentation = (function($) { //$("body").append(html); var segmentsDropdown = $(html).find("#available_segments_select"); var segment, newOption; - newOption = '<option data-idsegment="" data-definition="" >New segment</option>'; + newOption = '<option data-idsegment="" data-definition="" title="' + + self.translations['SegmentEditor_AddNewSegment'] + + '">' + self.translations['SegmentEditor_AddNewSegment'] + + '</option>'; segmentsDropdown.append(newOption); - for(var key in self.segmentList) + for(var i = 0; i < self.availableSegments.length; i++) { - segment = self.segmentList[key]; - newOption = '<option data-idsegment="'+segment.idsegment+'" data-definition=\''+segment.definition+'\' title="'+segment.name+'">'+self.shortenSegmentName(segment.name)+'</option>'; + segment = self.availableSegments[i]; + newOption = '<option data-idsegment="'+segment.idsegment+'" data-definition="'+(segment.definition).replace(/"/g, '"')+'" title="'+segment.name+'">'+self.shortenSegmentName(segment.name)+'</option>'; segmentsDropdown.append(newOption); } $(html).find(".segment-content > h3").after(getInitialStateRowsHtml()).show(); @@ -274,7 +295,7 @@ Segmentation = (function($) { if( index != -1){ if(index < minPos){ minPos = index; - if(match == ">" || match == "<"){ + if(match.length == 1){ singleChar = true; } } @@ -287,13 +308,10 @@ Segmentation = (function($) { newMetric.metric = metric.substr(0,minPos); newMetric.match = metric.substr(minPos,1); newMetric.value = metric.substr(minPos+1); - - } - else{ + } else { newMetric.metric = metric.substr(0,minPos); newMetric.match = metric.substr(minPos,2); newMetric.value = metric.substr(minPos+2); - } // if value is only "" -> change to empty string if(newMetric.value == '""') @@ -301,6 +319,8 @@ Segmentation = (function($) { newMetric.value = ""; } } + + newMetric.value = decodeURIComponent(newMetric.value); return newMetric; } @@ -318,11 +338,15 @@ Segmentation = (function($) { } var openEditForm = function(segment){ - addForm(); + addForm("edit", segment); + $(self.form).find(".segment-content > h3 > span").text(segment.name); $(self.form).find('#available_segments_select > option[data-idsegment="'+segment.idsegment+'"]').prop("selected",true); + $(self.form).find('#available_segments a.dropList').html(self.shortenSegmentName(segment.name, 16)); - + + + if(segment.definition != ""){ revokeInitialStateRows(); var blocks = parseSegmentStr(segment.definition); @@ -361,11 +385,8 @@ Segmentation = (function($) { $(self.content).off("click",".editSegment").on("click", ".editSegment", function(e){ $(this).parents(".segmentationContainer").trigger("click"); var target = $(this).parent("li"); - var segment = {}; - segment.idsegment = target.attr("data-idsegment"); - segment.definition = target.attr("data-definition"); - segment.name = target.attr("title"); - openEditForm(segment); + + openEditFormGivenSegment(target); e.stopPropagation(); e.preventDefault(); }); @@ -374,9 +395,11 @@ Segmentation = (function($) { if($(e.currentTarget).hasClass("grayed") !== true){ var segment = {}; segment.idsegment = $(this).attr("data-idsegment"); - segment.definition = $(this).attr("data-definition"); + segment.definition = $(this).data("definition"); segment.name = $(this).attr("title"); - self.segmentSelectMethod(segment); + + self.segmentSelectMethod( segment.definition ); + toggleLoadingMessage( segment.definition.length ); setSegment(segment.definition); markCurrentSegment(); } @@ -390,11 +413,14 @@ Segmentation = (function($) { persist = false; } alterMatchesList(this, persist); + doDragDropBindings(); + autoSuggestValues(this, persist); } ); } + // Request auto-suggest values var autoSuggestValues = function(select, persist) { var type = $(select).find("option:selected").attr("value"); if(!persist) { @@ -404,14 +430,12 @@ Segmentation = (function($) { var inputElement = parents.find(".metricValueBlock input"); var segmentName = $('option:selected',select).attr('value'); - // Request auto-suggest values var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ module: 'API', format: 'json', method: 'API.getSuggestedValuesForSegment', - segmentName: segmentName, - idSite: piwik.idSite + segmentName: segmentName }, 'GET'); ajaxHandler.setCallback(function(response) { loadingElement.hide(); @@ -425,9 +449,11 @@ Segmentation = (function($) { } }); - inputElement.click(function(e){ inputElement.keydown() }); + inputElement.click(function(e){ + inputElement.autocomplete('search', $(inputElement).val()); + }); }); - ajaxHandler.send(true); + ajaxHandler.send(); } } @@ -437,8 +463,7 @@ Segmentation = (function($) { var matchSelector = $(select).parents(".segment-input").siblings(".metricMatchBlock").find("select"); if(persist === true){ oldMatch = matchSelector.find("option:selected").val(); - } - else{ + } else { oldMatch = ""; } @@ -482,9 +507,27 @@ Segmentation = (function($) { }); } + function openEditFormGivenSegment(option) { + var segment = {}; + segment.idsegment = option.attr("data-idsegment"); + + + var segmentExtra = getSegmentFromId(segment.idsegment); + for(var item in segmentExtra) + { + segment[item] = segmentExtra[item]; + } + + segment.name = option.attr("title"); + + segment.definition = option.data("definition"); + + openEditForm(segment); + } + var bindFormEvents = function(){ - $(self.form).on("click", "a", function(e){ + $(self.form).on("click", "a:not(.crowdfundingLink)", function(e){ e.preventDefault(); }); @@ -501,11 +544,18 @@ Segmentation = (function($) { $(e.currentTarget).siblings("#edit_segment_name").focus().val(oldName); }); + + $(self.form).off("click", ".segmentName").on("click", ".segmentName", function(e) { + $(self.form).find("a.editSegmentName").trigger('click'); + }); + $(self.form).off("blur", "input#edit_segment_name").on("blur", "input#edit_segment_name", function(e){ var newName = $(this).val(); - $(e.currentTarget).parents("h3").find("span").text(newName).show(); - $(self.form).find("a.editSegmentName").show(); - $(this).remove(); + if(newName.trim() != '') { + $(e.currentTarget).parents("h3").find("span").text(newName).show(); + $(self.form).find("a.editSegmentName").show(); + $(this).remove(); + } }); $(self.form).on("click", '.segment-element', function(event) { @@ -515,12 +565,7 @@ Segmentation = (function($) { $(self.form).find("#available_segments_select").bind("change", function(e){ var option = $(e.currentTarget).find('option:selected'); - var segment = {}; - segment.idsegment = option.attr("data-idsegment"); - segment.name = option.attr("title"); - segment.definition = option.attr("data-definition"); - openEditForm(segment); - + openEditFormGivenSegment(option); }); // attach event that shows/hides child elements of each metric category @@ -531,23 +576,22 @@ Segmentation = (function($) { }); $(self.form).off("click", ".custom_select_search a").on("click", ".custom_select_search a", function(e){ - $(self.form).find("#segmentSearch").val("").trigger("keyup").val("Search"); + $(self.form).find("#segmentSearch").val("").trigger("keyup").val(self.translations['General_Search']); }); // attach event that will clear search input upon focus if its content is default $(self.form).find("#segmentSearch").on("focus", function(e){ var search = $(e.currentTarget).val(); - if(search == "Search") + if(search == self.translations['General_Search']) $(e.currentTarget).val(""); }); - // attach event that will set search input value upon blur if its content is not null $(self.form).find("#segmentSearch").on("blur", function(e){ var search = $(e.currentTarget).val(); if(search == ""){ clearSearchMetricHighlight(); - $(e.currentTarget).val("Search"); + $(e.currentTarget).val(self.translations['General_Search']); } }); @@ -568,17 +612,15 @@ Segmentation = (function($) { } }); - $(self.form).on("click", ".delete", function(){ - var segmentName = $(self.form).find(".segment-content > h3 > span").text(); var segmentId = $(self.form).find("#available_segments_select option:selected").attr("data-idsegment") var params = { "idsegment" : segmentId }; - $('#confirm').find('#name').text( segmentName ); + $('#segment-delete-confirm').find('#name').text( segmentName ); if(segmentId != ""){ - piwikHelper.modalConfirm( '#confirm', { + piwikHelper.modalConfirm( '#segment-delete-confirm', { yes: function(){ self.deleteMethod(params); } @@ -601,7 +643,6 @@ Segmentation = (function($) { bindChangeMetricSelectEvent(); placeSegmentationFormControls(); - } var doDragDropBindings = function(){ @@ -643,27 +684,30 @@ Segmentation = (function($) { } var searchSegments = function(search){ - // pre-proces search string to normalized form + // pre-process search string to normalized form search = normalizeSearchString(search); + // --- // clear all previous search highlights and hide all categories // to allow further showing only matching ones, while others remain invisible clearSearchMetricHighlight(); $(self.form).find('.segment-nav div > ul > li').hide(); var curStr = ""; + var curMetric = ""; + // 1 - do most obvious selection -> mark whole categories matching search string // also expand whole category $(self.form).find('.segment-nav div > ul > li').each( function(){ - curStr = normalizeSearchString($(this).text()) - if(curStr.indexOf(search) > -1){ - $(this).addClass("searchFound"); - $(this).find("ul").show(); - $(this).find("li").show(); - $(this).show(); + curStr = normalizeSearchString($(this).find("a.metric_category").text()) + if(curStr.indexOf(search) > -1) { + $(this).addClass("searchFound"); + $(this).find("ul").show(); + $(this).find("li").show(); + $(this).show(); + } } - }); + ); - var curMetric = ""; // 2 - among all unselected categories find metrics which match and mark parent as search result $(self.form).find(".segment-nav div > ul > li:not(.searchFound)").each(function(){ var parent = this; @@ -677,19 +721,11 @@ Segmentation = (function($) { $(parent).addClass("searchFound").show(); } }); - - // if(curStr.indexOf(search) > -1 || curMetric.indexOf(search) > -1) - // { - // $(this).addClass("searchFound"); - // $(this).find('li').hide(); - // $(this).find('li[data*="'+search+'"]').show(); - // } }); if( $(self.form).find("li.searchFound").length == 0) { - $(self.form).find("div > ul").prepend('<li class="no_results"><a>No results</a></li>').show(); - + $(self.form).find("div > ul").prepend('<li class="no_results"><a>'+self.translations['General_SearchNoResults']+'</a></li>').show(); } // check if search allow flag was revoked - then clear all search results if(self.searchAllowed == false) @@ -743,7 +779,7 @@ Segmentation = (function($) { } }); - // add new OR block + // add new OR block $(self.form).on("click", ".segment-add-or a", function(event, data){ $(event.currentTarget).parents(".segment-rows").find(".segment-or:last").after(getOrDiv()).after(getMockedInputRowHtml()); if(typeof data !== "undefined"){ @@ -771,7 +807,8 @@ Segmentation = (function($) { }); } - var addForm = function(mode){ + // Mode = 'new' or 'edit' + var addForm = function(mode, segment){ $("#segmentEditorPanel").find(".segment-element:visible").unbind().remove(); if(typeof self.form !== "undefined") @@ -786,18 +823,32 @@ Segmentation = (function($) { setLeftMargin('#segmentEditorPanel > .segment-element'); bindFormEvents(); bindSegmentManipulationEvents(); - makeDropList("#enabledAllUsers" , "#enabledAllUsers_select"); + + if(mode == "edit") { + $(self.form).find('#enable_all_users_select > option[value="'+segment.enable_all_users+'"]').prop("selected",true); + $(self.form).find('#visible_to_website_select > option[value="'+segment.enable_only_idsite+'"]').prop("selected",true); + $(self.form).find('#auto_archive_select > option[value="'+segment.auto_archive+'"]').prop("selected",true); + + } + + makeDropList("#enable_all_users" , "#enable_all_users_select"); makeDropList("#visible_to_website" , "#visible_to_website_select"); + makeDropList("#auto_archive" , "#auto_archive_select"); makeDropList("#available_segments" , "#available_segments_select"); $(self.form).find(".saveAndApply").bind("click", function(e){ e.preventDefault(); parseFormAndSave(); }); + $(self.form).find('.segment-footer').hover( function() { + $('.segmentFooterNote').fadeIn(); + }, function() { + $('.segmentFooterNote').fadeOut(); + }); + if(typeof mode !== "undefined" && mode == "new") { $(self.form).find(".editSegmentName").trigger('click'); - $(self.form).find("#edit_segment_name").val(""); } $("#segmentList").hide(); @@ -816,9 +867,6 @@ Segmentation = (function($) { var metric = $(this).find(".metricList option:selected").val(); var match = $(this).find(".metricMatchBlock > select option:selected").val(); var value = $(this).find(".segment-input input").val(); - /*if(value == ""){ - value= ''; - }*/ subSegmentStr += metric + match + encodeURIComponent(value); }); }); @@ -835,13 +883,16 @@ Segmentation = (function($) { var segmentName = $(self.form).find(".segment-content > h3 >span").text(); var segmentStr = parseForm(); var segmentId = $(self.form).find('#available_segments_select > option:selected').attr("data-idsegment"); - var user = $(self.form).find("#enabledAllUsers_select option:selected").val(); + var user = $(self.form).find("#enable_all_users_select option:selected").val(); + var autoArchive = $(self.form).find("#auto_archive_select option:selected").val() || 0; var params = { "name": segmentName, "definition": segmentStr, "enabledAllUsers": user, - "idSite": $('#visible_to_website option:selected').attr('value') + "autoArchive": autoArchive, + "idSite": $(self.form).find("#visible_to_website_select option:selected").val() }; + // determine if save or update should be performed if(segmentId === ""){ self.addMethod(params); @@ -876,10 +927,10 @@ Segmentation = (function($) { select: function( event, ui ) { event.preventDefault(); ui.item.option.selected = true; - if(ui.item.value) { - dropList.text(ui.item.label); - $(self.form).find(selectId).trigger("change"); - } + // Mark original select>option + $('#SegmentEditor ' + spanId + ' option[value="' + ui.item.value + '"]').prop('selected', true); + dropList.text(ui.item.label); + $(self.form).find(selectId).trigger("change"); } }) .click(function() { @@ -904,9 +955,15 @@ Segmentation = (function($) { } var setLeftMargin = function(selector) { -// setTimeout( function() { - $(selector).css({left: Math.max($('#periodString')[0].offsetWidth) + 10}); -// }, 500); + $(selector).css({left: Math.max($('#periodString')[0].offsetWidth) + 10}); + } + + function toggleLoadingMessage(segmentIsSet) { + if (segmentIsSet) { + $('#ajaxLoading .loadingSegment').show(); + } else { + $('#ajaxLoading .loadingSegment').hide(); + } } var initHtml = function() { @@ -926,6 +983,10 @@ Segmentation = (function($) { // assign content to object attribute to make it easil accesible through all widget methods bindListEvents(); markCurrentSegment(); + + // Loading message + var segmentIsSet = self.getSegment().length; + toggleLoadingMessage(segmentIsSet); } initHtml(); }; @@ -940,12 +1001,19 @@ $(document).ready( function(){ return; } - var changeSegment = function(params){ + var changeSegment = function(segmentDefinition){ $('#segmentEditorPanel a.close').click(); - - return broadcast.propagateNewPage('segment=' + params.definition, true, true); + segmentDefinition = cleanupSegmentDefinition(segmentDefinition); + segmentDefinition = encodeURIComponent(segmentDefinition); + return broadcast.propagateNewPage('segment=' + segmentDefinition, true); }; + var cleanupSegmentDefinition = function(definition) { + definition = definition.replace("'", "%29"); + definition = definition.replace("&", "%26"); + return definition; + } + var addSegment = function(params){ var ajaxHandler = new ajaxHelper(); ajaxHandler.setLoadingElement(); @@ -954,13 +1022,15 @@ $(document).ready( function(){ "format": 'json', "method": 'SegmentEditor.add' }); + params.definition = cleanupSegmentDefinition(params.definition); + ajaxHandler.addParams(params, 'GET'); ajaxHandler.useCallbackInCaseOfError(); ajaxHandler.setCallback(function (response) { if (response && response.result == 'error') { alert(response.message); } else { - changeSegment(params); + changeSegment(params.definition); } }); ajaxHandler.send(true); @@ -974,13 +1044,15 @@ $(document).ready( function(){ "format": 'json', "method": 'SegmentEditor.update' }); + params.definition = cleanupSegmentDefinition(params.definition); + ajaxHandler.addParams(params, 'GET'); ajaxHandler.useCallbackInCaseOfError(); ajaxHandler.setCallback(function (response) { if (response && response.result == 'error') { alert(response.message); } else { - changeSegment(params); + changeSegment(params.definition); } }); ajaxHandler.send(true); @@ -997,26 +1069,31 @@ $(document).ready( function(){ ajaxHandler.addParams({ idSegment: params.idsegment }, 'POST'); - ajaxHandler.redirectOnSuccess(); +// ajaxHandler.redirectOnSuccess(); ajaxHandler.setLoadingElement(); + ajaxHandler.useCallbackInCaseOfError(); + ajaxHandler.setCallback(function (response) { + if (response && response.result == 'error') { + alert(response.message); + } else { + return broadcast.propagateNewPage('segment='); + } + }); + ajaxHandler.send(true); }; - var testSegment = function(segmentStr){ - console.log(segmentStr); - } - + var segmentFromHash = broadcast.getParamValue('segment', location.hash); var segmentationFtw = new Segmentation({ "targetId" : "segmentList", "segmentAccess" : "write", - "segmentList" : availableSegments, + "availableSegments" : availableSegments, "addMethod": addSegment, "updateMethod": updateSegment, "deleteMethod": deleteSegment, "segmentSelectMethod": changeSegment, - "testSegmentMethod": testSegment, - "currentSegmentStr": broadcast.getValueFromHash('segment'), - "currentSegmentsGlobal": broadcast.getValueFromHash('segment') + "currentSegmentStr": segmentFromHash, + "translations": segmentTranslations }); $('body').on('mouseup',function(e){ @@ -1026,10 +1103,9 @@ $(document).ready( function(){ $("#segmentList").show(); } - if($(e.target).parents('.segmentList').length === 0 && $(".segmentationContainer").hasClass("visible")){ + if($(e.target).parents('#segmentList').length === 0 && $(".segmentationContainer").hasClass("visible")){ $(".segmentationContainer").trigger("click"); } - }); -});
\ No newline at end of file +}); diff --git a/plugins/SegmentEditor/stylesheets/segmentation.css b/plugins/SegmentEditor/stylesheets/segmentation.css index 184592fb28..4160090cd6 100644 --- a/plugins/SegmentEditor/stylesheets/segmentation.css +++ b/plugins/SegmentEditor/stylesheets/segmentation.css @@ -1,4 +1,22 @@ /* ADDITIONAL STYLES*/ +.youMustBeLoggedIn { + font-size:8pt; + font-style: italic; + +} +.segment-footer .segmentFooterNote { + display:none; + float: left; + padding-top: 9px; +} +.segment-footer .segmentFooterNote, .segment-element .segment-footer .segmentFooterNote a { + font-size: 8pt; + color: #888172; +} +.segment-element .segment-footer .segmentFooterNote a { + padding:0; margin:0; + text-decoration:underline; +} .searchFound { border: 0px solid red; } @@ -66,7 +84,7 @@ div.scrollable { font: 11px Arial; color: #454545; width: 125px; - padding: 3px 0px 3px 5px; + padding: 4px 0 3px 7px; border: none; background: none; } @@ -349,7 +367,7 @@ div.scrollable { } .segment-element .segment-footer button { - width: 178px; + min-width: 178px; height: 30px; background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDE3OCAzMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+PGxpbmVhckdyYWRpZW50IGlkPSJoYXQwIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjUwJSIgeTE9IjEwMCUiIHgyPSI1MCUiIHkyPSItMS40MjEwODU0NzE1MjAyZS0xNCUiPgo8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjODM3OTZiIiBzdG9wLW9wYWNpdHk9IjEiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjYWJhMzkzIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgPC9saW5lYXJHcmFkaWVudD4KCjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxNzgiIGhlaWdodD0iMzAiIGZpbGw9InVybCgjaGF0MCkiIC8+Cjwvc3ZnPg==); background-image: -moz-linear-gradient(bottom, #83796b 0%, #aba393 100%); @@ -371,7 +389,7 @@ div.scrollable { z-index: 2; background: #f7f7f7; border: 1px solid #e4e5e4; - padding: 5px 10px 6px 5px; + padding: 5px 10px 6px 10px; border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; @@ -387,7 +405,6 @@ div.scrollable { } .segmentationContainer .submenu { font-size: 13px; - font-weight: bold; min-width: 180px; } .segmentationContainer .submenu ul { @@ -402,13 +419,14 @@ div.scrollable { padding-top: 10px; } .segmentationContainer .submenu ul li { - padding: 2px 0px 1px 10px; + padding: 2px 0px 1px 6px; margin: 3px 0 0 0; cursor: pointer; } .segmentationContainer .submenu ul li:hover { color: #255792; margin: 0; + margin-left:-3px; border: 1px solid #d5d2c6; border-bottom: 2px solid #918f88; border-radius: 4px; @@ -446,6 +464,7 @@ div.scrollable { margin: 12px 0 10px; padding: 3px 10px; text-decoration: none; + width:130px; } .segmentationContainer > ul.submenu > li { padding: 5px 0; diff --git a/plugins/SegmentEditor/templates/selector.twig b/plugins/SegmentEditor/templates/selector.twig index 0cabb467e5..86939a1480 100644 --- a/plugins/SegmentEditor/templates/selector.twig +++ b/plugins/SegmentEditor/templates/selector.twig @@ -1,29 +1,38 @@ <div id="SegmentEditor" style="display:none;"> <div class="segmentationContainer listHtml"> - <span class="segmentationTitle"><b>Add segment</b></span> + <span class="segmentationTitle"></span> + <ul class="submenu"> - <li> Select a segment of visitors + <li>{{ 'SegmentEditor_SelectSegmentOfVisitors'|translate }} <div class="segmentList"> <ul> </ul> </div> </li> </ul> - <a class="add_new_segment">Add new segment</a> + {% if authorizedToCreateSegments %} + <a class="add_new_segment">{{ 'SegmentEditor_AddNewSegment'|translate }}</a> + {% else %} + <ul class="submenu"> + <li> <span class='youMustBeLoggedIn'>{{ 'SegmentEditor_YouMustBeLoggedInToCreateSegments'|translate }} + <br/>› <a href='index.php?module={{ loginModule }}'>{{ 'Login_LogIn'|translate }}</a> </span></strong> + </li> + </ul> + {% endif %} </div> <div class="initial-state-rows">{# no space here important for jquery #}<div class="segment-add-row initial"><div> - <span>+ Drag & Drop condition</span> + <span>+ {{ 'SegmentEditor_DragDropCondition'|translate }}</span> </div></div> - <div class="segment-and">AND</div> + <div class="segment-and">{{ 'SegmentEditor_OperatorAND'|translate }}</div> <div class="segment-add-row initial"><div> - <span>+ Drag & Drop condition</span> + <span>+ {{ 'SegmentEditor_DragDropCondition'|translate }}</span> </div></div> </div> <div class="segment-row-inputs"> <div class="segment-input metricListBlock"> - <select title="Choose a segment" class="metricList"> + <select title="{{ 'SegmentEditor_ChooseASegment'|translate }}" class="metricList"> {% for category,segmentsInCategory in segmentsByCategory %} <optgroup label="{{ category }}"> {% for segmentInCategory in segmentsInCategory %} @@ -34,19 +43,19 @@ </select> </div> <div class="segment-input metricMatchBlock"> - <select title="Matches"> - <option value="==">Equals</option> - <option value="!=">Not Equals</option> - <option value="<=">At most</option> - <option value=">=">At least</option> - <option value="<">Less</option> - <option value=">">Greater</option> - <option value="=@">Contains</option> - <option value="!@">Does not contain</option> + <select title="{{ 'General_Matches'|translate }}"> + <option value="==">{{ 'General_OperationEquals'|translate }}</option> + <option value="!=">{{ 'General_OperationNotEquals'|translate }}</option> + <option value="<=">{{ 'General_OperationAtMost'|translate }}</option> + <option value=">=">{{ 'General_OperationAtLeast'|translate }}</option> + <option value="<">{{ 'General_OperationLessThan'|translate }}</option> + <option value=">">{{ 'General_OperationGreaterThan'|translate }}</option> + <option value="=@">{{ 'General_OperationContains'|translate }}</option> + <option value="!@">{{ 'General_OperationDoesNotContain'|translate }}</option> </select> </div> <div class="segment-input metricValueBlock"> - <input type="text" title="Value"> + <input type="text" title="{{ 'General_Value'|translate }}"> </div> <div class="clear"></div> </div> @@ -56,14 +65,16 @@ <a href="#" class="segment-loading"></a> </div> </div> - <div class="segment-or">OR</div> + <div class="segment-or">{{ 'SegmentEditor_OperatorOR'|translate }}</div> <div class="segment-add-or"><div> - <a href="#"> + Add <span>OR</span> condition </a> + {% set orCondition %}<span>{{ 'SegmentEditor_OperatorOR'|translate }}</span>{% endset %} + <a href="#"> + {{ 'SegmentEditor_AddANDorORCondition'|translate(orCondition) }} </a> </div> </div> - <div class="segment-and">AND</div> + <div class="segment-and">{{ 'SegmentEditor_OperatorAND'|translate }}</div> <div class="segment-add-row"><div> - <a href="#">+ Add <span>AND</span> condition </a> + {% set andCondition %}<span>{{ 'SegmentEditor_OperatorAND'|translate }}</span>{% endset %} + <a href="#">+ {{ 'SegmentEditor_AddANDorORCondition'|translate(andCondition) }}</a> </div> </div> <div style="position: absolute; z-index:999; width:1040px;" class="segment-element"> @@ -87,33 +98,41 @@ </div> <div class="custom_select_search"> <a href="#"></a> - <input type="text" aria-haspopup="true" aria-autocomplete="list" role="textbox" autocomplete="off" class="inp ui-autocomplete-input" id="segmentSearch" value="Search" length="15"> + <input type="text" aria-haspopup="true" aria-autocomplete="list" role="textbox" autocomplete="off" class="inp ui-autocomplete-input" id="segmentSearch" value="{{ 'General_Search'|translate }}" length="15"> </div> </div> <div class="segment-content"> {% if isSuperUser %} <div class="segment-top"> - This segment is visible to: <span id="enabledAllUsers"><strong> - <select id="enabledAllUsers_select"> - <option selected="" value="0">me</option> - <option value="1">All users</option> + {{ 'SegmentEditor_ThisSegmentIsVisibleTo'|translate }} <span id="enable_all_users"><strong> + <select id="enable_all_users_select"> + <option selected="1" value="0">{{ 'SegmentEditor_VisibleToMe'|translate }}</option> + <option value="1">{{ 'SegmentEditor_VisibleToAllUsers'|translate }}</option> </select> </strong></span> - and displayed for <span id="visible_to_website"><strong> + {{ 'SegmentEditor_SegmentIsDisplayedForWebsite'|translate }}<span id="visible_to_website"><strong> <select id="visible_to_website_select"> - <option selected="" value="{{ idSite }}">this website only</option> - <option value="0">all websites</option> + <option selected="" value="{{ idSite }}">{{ 'SegmentEditor_SegmentDisplayedThisWebsiteOnly'|translate }}</option> + <option value="0">{{ 'SegmentEditor_SegmentDisplayedAllWebsites'|translate }}</option> </select> </strong></span> + {{ 'General_And'|translate }} <span id="auto_archive"><strong> + <select id="auto_archive_select"> + <option selected="1" value="0">{{ 'SegmentEditor_AutoArchiveRealTime'|translate }} {{ 'General_DefaultAppended'|translate }}</option> + <option value="1">{{ 'SegmentEditor_AutoArchivePreProcessed'|translate }} </option> + </select> + </strong></span> + </div> {% endif %} - <h3>Name: <span>New segment</span> <a class="editSegmentName" href="#">edit</a></h3> + <h3>{{ 'General_Name'|translate }}: <span class="segmentName"></span> <a class="editSegmentName" href="#">{{ 'General_Edit'|translate|lower }}</a></h3> </div> <div class="segment-footer"> - <a class="delete" href="#">Delete</a> - <a class="close" href="#">Close</a> - <button class="saveAndApply">Save & Apply</button> + <span class="segmentFooterNote">The Segment Editor was <a class='crowdfundingLink' href='http://crowdfunding.piwik.org/custom-segments-editor/' target='_blank'>crowdfunded</a> with the awesome support of 80 companies and Piwik users worldwide!</span> + <a class="delete" href="#">{{ 'General_Delete'|translate }}</a> + <a class="close" href="#">{{ 'General_Close'|translate }}</a> + <button class="saveAndApply">{{ 'SegmentEditor_SaveAndApply'|translate }}</button> </div> </div> </div> @@ -122,12 +141,13 @@ <div id="segmentList"></div> </span> -<div class="ui-confirm" id="confirm"> - <h2>Are you sure you want to delete this segment?</h2> +<div class="ui-confirm" id="segment-delete-confirm"> + <h2>{{ 'SegmentEditor_AreYouSureDeleteSegment'|translate }}</h2> <input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/> <input role="no" type="button" value="{{ 'General_No'|translate }}"/> </div> <script type="text/javascript"> var availableSegments = {{ savedSegmentsJson|raw }}; +var segmentTranslations = {{ segmentTranslations|raw }}; </script> diff --git a/plugins/SitesManager/javascripts/SitesManager.js b/plugins/SitesManager/javascripts/SitesManager.js index 4d32b283f3..4ff38d2159 100644 --- a/plugins/SitesManager/javascripts/SitesManager.js +++ b/plugins/SitesManager/javascripts/SitesManager.js @@ -117,8 +117,8 @@ function SitesManager(_timezones, _currencies, _defaultTimezone, _defaultCurrenc } function sendGlobalSettingsAJAX() { - var timezone = $('#defaultTimezone option:selected').val(); - var currency = $('#defaultCurrency option:selected').val(); + var timezone = $('#defaultTimezone').find('option:selected').val(); + var currency = $('#defaultCurrency').find('option:selected').val(); var excludedIps = $('textarea#globalExcludedIps').val(); excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); var excludedQueryParameters = $('textarea#globalExcludedQueryParameters').val(); @@ -157,6 +157,11 @@ function SitesManager(_timezones, _currencies, _defaultTimezone, _defaultCurrenc $('.addRowSite').click(function () { piwikHelper.hideAjaxError(); $('.addRowSite').toggle(); + + var excludedUserAgentCell = ''; + if ($('#exclude-user-agent-header').is(':visible')) { + excludedUserAgentCell = '<td><textarea cols="20" rows="4" id="excludedUserAgents"></textarea><br />' + excludedUserAgentsHelp + '</td>'; + } var numberOfRows = $('table#editSites')[0].rows.length; var newRowId = 'rowNew' + numberOfRows; @@ -166,9 +171,9 @@ function SitesManager(_timezones, _currencies, _defaultTimezone, _defaultCurrenc <td><input id="name" value="Name" size="15" /><br/><br/><br/>' + submitButtonHtml + '</td>\ <td><textarea cols="25" rows="3" id="urls">http://siteUrl.com/\nhttp://siteUrl2.com/</textarea><br />' + aliasUrlsHelp + keepURLFragmentSelectHTML + '</td>\ <td><textarea cols="20" rows="4" id="excludedIps"></textarea><br />' + excludedIpHelp + '</td>\ - <td><textarea cols="20" rows="4" id="excludedQueryParameters"></textarea><br />' + excludedQueryParametersHelp + '</td>\ - <td><textarea cols="20" rows="4" id="excludedUserAgents"></textarea><br />' + excludedUserAgentsHelp + '</td>\ - <td>' + getSitesearchSelector(false) + '</td>\ + <td><textarea cols="20" rows="4" id="excludedQueryParameters"></textarea><br />' + excludedQueryParametersHelp + '</td>' + + excludedUserAgentCell + + '<td>' + getSitesearchSelector(false) + '</td>\ <td>' + getTimezoneSelector(defaultTimezone) + '<br />' + timezoneHelp + '</td>\ <td>' + getCurrencySelector(defaultCurrency) + '<br />' + currencyHelp + '</td>\ <td>' + getEcommerceSelector(0) + '<br />' + ecommerceHelp + '</td>\ @@ -199,7 +204,7 @@ function SitesManager(_timezones, _currencies, _defaultTimezone, _defaultCurrenc var nameToDelete = $(this).parent().parent().find('input#siteName').val() || $(this).parent().parent().find('td#siteName').html(); var idsiteToDelete = $(this).parent().parent().find('#idSite').html(); - $('#confirm h2').text(sprintf(_pk_translate('SitesManager_DeleteConfirm_js'), '"' + nameToDelete + '" (idSite = ' + idsiteToDelete + ')')); + $('#confirm').find('h2').text(sprintf(_pk_translate('SitesManager_DeleteConfirm_js'), '"' + nameToDelete + '" (idSite = ' + idsiteToDelete + ')')); piwikHelper.modalConfirm('#confirm', {yes: function () { sendDeleteSiteAJAX(idsiteToDelete); }}); @@ -213,7 +218,7 @@ function SitesManager(_timezones, _currencies, _defaultTimezone, _defaultCurrenc var idRow = $(this).attr('id'); if (alreadyEdited[idRow] == 1) return; if (siteBeingEdited) { - $('#alert h2').text(sprintf(_pk_translate('SitesManager_OnlyOneSiteAtTime_js'), '"' + $("<div/>").html(siteBeingEditedName).text() + '"')); + $('#alert').find('h2').text(sprintf(_pk_translate('SitesManager_OnlyOneSiteAtTime_js'), '"' + $("<div/>").html(siteBeingEditedName).text() + '"')); piwikHelper.modalConfirm('#alert', {}); return; } diff --git a/plugins/Transitions/API.php b/plugins/Transitions/API.php index 4457d71046..1c2934ca74 100644 --- a/plugins/Transitions/API.php +++ b/plugins/Transitions/API.php @@ -62,15 +62,14 @@ class Piwik_Transitions_API } // prepare archive processing that can be used by the archiving code - $archiveProcessing = new Piwik_ArchiveProcessing_Day(); - $archiveProcessing->setSite(new Piwik_Site($idSite)); - $archiveProcessing->setPeriod(Piwik_Period::advancedFactory($period, $date)); - $archiveProcessing->setSegment(new Piwik_Segment($segment, $idSite)); - $archiveProcessing->initForLiveUsage(); - + $segment = new Piwik_Segment($segment, $idSite); + $site = new Piwik_Site($idSite); + $period = Piwik_Period::advancedFactory($period, $date); + $archiveProcessor = new Piwik_ArchiveProcessor_Day($period, $site, $segment); + $logAggregator = $archiveProcessor->getLogAggregator(); // prepare the report $report = array( - 'date' => Piwik_Period_Day::advancedFactory($period, $date)->getLocalizedShortString() + 'date' => Piwik_Period_Day::advancedFactory($period->getLabel(), $date)->getLocalizedShortString() ); // add data to the report @@ -82,23 +81,20 @@ class Piwik_Transitions_API $partsArray = explode(',', $parts); if ($parts == 'all' || in_array('internalReferrers', $partsArray)) { - $this->addInternalReferrers($transitionsArchiving, $archiveProcessing, $report, $idaction, - $actionType, $limitBeforeGrouping); + $this->addInternalReferrers($logAggregator, $report, $idaction, $actionType, $limitBeforeGrouping); } if ($parts == 'all' || in_array('followingActions', $partsArray)) { $includeLoops = $parts != 'all' && !in_array('internalReferrers', $partsArray); - $this->addFollowingActions($transitionsArchiving, $archiveProcessing, $report, $idaction, - $actionType, $limitBeforeGrouping, $includeLoops); + $this->addFollowingActions($logAggregator, $report, $idaction, $actionType, $limitBeforeGrouping, $includeLoops); } if ($parts == 'all' || in_array('externalReferrers', $partsArray)) { - $this->addExternalReferrers($transitionsArchiving, $archiveProcessing, $report, $idaction, - $actionType, $limitBeforeGrouping); + $this->addExternalReferrers($logAggregator, $report, $idaction, $actionType, $limitBeforeGrouping); } // derive the number of exits from the other metrics if ($parts == 'all') { $report['pageMetrics']['exits'] = $report['pageMetrics']['pageviews'] - - $transitionsArchiving->getTotalTransitionsToFollowingActions() + - $this->getTotalTransitionsToFollowingActions() - $report['pageMetrics']['loops']; } @@ -113,9 +109,9 @@ class Piwik_Transitions_API ); foreach ($reportNames as $reportName => $replaceLabel) { if (isset($report[$reportName])) { - $columnNames = array(Piwik_Archive::INDEX_NB_ACTIONS => 'referrals'); + $columnNames = array(Piwik_Metrics::INDEX_NB_ACTIONS => 'referrals'); if ($replaceLabel) { - $columnNames[Piwik_Archive::INDEX_NB_ACTIONS] = 'referrals'; + $columnNames[Piwik_Metrics::INDEX_NB_ACTIONS] = 'referrals'; } $report[$reportName]->filter('ReplaceColumnNames', array($columnNames)); } @@ -139,7 +135,7 @@ class Piwik_Transitions_API if ($id < 0) { // an example where this is needed is urls containing < or > $actionName = $originalActionName; - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url', Piwik_SegmentExpression::MATCH_EQUALs, 'pageUrl'); + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url', Piwik_SegmentExpression::MATCH_EQUAL, 'pageUrl'); } return $id; @@ -167,20 +163,17 @@ class Piwik_Transitions_API * Add the internal referrers to the report: * previous pages and previous site searches * - * @param Piwik_Transitions $transitionsArchiving - * @param $archiveProcessing + * @param Piwik_DataAccess_LogAggregator $logAggregator * @param $report * @param $idaction * @param string $actionType * @param $limitBeforeGrouping * @throws Exception */ - private function addInternalReferrers($transitionsArchiving, $archiveProcessing, &$report, - $idaction, $actionType, $limitBeforeGrouping) + private function addInternalReferrers($logAggregator, &$report, $idaction, $actionType, $limitBeforeGrouping) { - $data = $transitionsArchiving->queryInternalReferrers( - $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping); + $data = $this->queryInternalReferrers($idaction, $actionType, $logAggregator, $limitBeforeGrouping); if ($data['pageviews'] == 0) { throw new Exception('NoDataForAction'); @@ -196,49 +189,375 @@ class Piwik_Transitions_API * Add the following actions to the report: * following pages, downloads, outlinks * - * @param Piwik_Transitions $transitionsArchiving - * @param $archiveProcessing + * @param Piwik_DataAccess_LogAggregator $logAggregator * @param $report * @param $idaction * @param string $actionType * @param $limitBeforeGrouping * @param boolean $includeLoops */ - private function addFollowingActions($transitionsArchiving, $archiveProcessing, &$report, - $idaction, $actionType, $limitBeforeGrouping, $includeLoops = false) + private function addFollowingActions($logAggregator, &$report, $idaction, $actionType, $limitBeforeGrouping, $includeLoops = false) { - $data = $transitionsArchiving->queryFollowingActions( - $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping, $includeLoops); + $data = $this->queryFollowingActions( + $idaction, $actionType, $logAggregator, $limitBeforeGrouping, $includeLoops); foreach ($data as $tableName => $table) { $report[$tableName] = $table; } } + + + /** + * Get information about the following actions (following pages, site searches, outlinks, downloads) + * + * @param $idaction + * @param $actionType + * @param Piwik_DataAccess_LogAggregator $logAggregator + * @param $limitBeforeGrouping + * @param $includeLoops + * @return array(followingPages:Piwik_DataTable, outlinks:Piwik_DataTable, downloads:Piwik_DataTable) + */ + public function queryFollowingActions($idaction, $actionType, Piwik_DataAccess_LogAggregator $logAggregator, + $limitBeforeGrouping = false, $includeLoops = false) + { + $types = array(); + + $isTitle = ($actionType == 'title'); + if (!$isTitle) { + // specific setup for page urls + $types[Piwik_Tracker_Action::TYPE_ACTION_URL] = 'followingPages'; + $dimension = 'IF( idaction_url IS NULL, idaction_name, idaction_url )'; + // site search referrers are logged with url=NULL + // when we find one, we have to join on name + $joinLogActionColumn = $dimension; + $selects = array('log_action.name', 'log_action.url_prefix', 'log_action.type'); + } else { + // specific setup for page titles: + $types[Piwik_Tracker_Action::TYPE_ACTION_NAME] = 'followingPages'; + // join log_action on name and url and pick depending on url type + // the table joined on url is log_action1 + $joinLogActionColumn = array('idaction_url', 'idaction_name'); + $dimension = ' + CASE + ' /* following site search */ . ' + WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.idaction + ' /* following page view: use page title */ . ' + WHEN log_action1.type = ' . Piwik_Tracker_Action::TYPE_ACTION_URL . ' THEN log_action2.idaction + ' /* following download or outlink: use url */ . ' + ELSE log_action1.idaction + END + '; + $selects = array( + 'CASE + ' /* following site search */ . ' + WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.name + ' /* following page view: use page title */ . ' + WHEN log_action1.type = ' . Piwik_Tracker_Action::TYPE_ACTION_URL . ' THEN log_action2.name + ' /* following download or outlink: use url */ . ' + ELSE log_action1.name + END AS `name`', + 'CASE + ' /* following site search */ . ' + WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.type + ' /* following page view: use page title */ . ' + WHEN log_action1.type = ' . Piwik_Tracker_Action::TYPE_ACTION_URL . ' THEN log_action2.type + ' /* following download or outlink: use url */ . ' + ELSE log_action1.type + END AS `type`', + 'NULL AS `url_prefix`' + ); + } + + // these types are available for both titles and urls + $types[Piwik_Tracker_Action::TYPE_SITE_SEARCH] = 'followingSiteSearches'; + $types[Piwik_Tracker_Action::TYPE_OUTLINK] = 'outlinks'; + $types[Piwik_Tracker_Action::TYPE_DOWNLOAD] = 'downloads'; + + $rankingQuery = new Piwik_RankingQuery($limitBeforeGrouping ? $limitBeforeGrouping : $this->limitBeforeGrouping); + $rankingQuery->addLabelColumn(array('name', 'url_prefix')); + $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($types)); + + $type = $this->getColumnTypeSuffix($actionType); + $where = 'log_link_visit_action.idaction_' . $type . '_ref = ' . intval($idaction); + if (!$includeLoops) { + $where .= ' AND (log_link_visit_action.idaction_' . $type . ' IS NULL OR ' + . 'log_link_visit_action.idaction_' . $type . ' != ' . intval($idaction) . ')'; + } + + $metrics = array(Piwik_Metrics::INDEX_NB_ACTIONS); + $data = $logAggregator->queryActionsByDimension(array($dimension), $where, $selects, $metrics, $rankingQuery, $joinLogActionColumn); + + $this->totalTransitionsToFollowingActions = 0; + $dataTables = array(); + foreach ($types as $type => $recordName) { + $dataTable = new Piwik_DataTable; + if (isset($data[$type])) { + foreach ($data[$type] as &$record) { + $actions = intval($record[Piwik_Metrics::INDEX_NB_ACTIONS]); + $dataTable->addRow(new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => array( + 'label' => $this->getPageLabel($record, $isTitle), + Piwik_Metrics::INDEX_NB_ACTIONS => $actions + ) + ))); + $this->totalTransitionsToFollowingActions += $actions; + } + } + $dataTables[$recordName] = $dataTable; + } + + return $dataTables; + } + + + /** + * After calling this method, the query*()-Methods will return urls in their + * normalized form (without the prefix reconstructed) + */ + public function returnNormalizedUrls() + { + $this->returnNormalizedUrls = true; + } + + /** + * Get information about external referrers (i.e. search engines, websites & campaigns) + * + * @param $idaction + * @param $actionType + * @param Piwik_ArchiveProcessor_Day $logAggregator + * @param $limitBeforeGrouping + * @return Piwik_DataTable + */ + public function queryExternalReferrers($idaction, $actionType, $logAggregator, + $limitBeforeGrouping = false) + { + $rankingQuery = new Piwik_RankingQuery($limitBeforeGrouping ? $limitBeforeGrouping : $this->limitBeforeGrouping); + + // we generate a single column that contains the interesting data for each referrer. + // the reason we cannot group by referer_* becomes clear when we look at search engine keywords. + // referer_url contains the url from the search engine, referer_keyword the keyword we want to + // group by. when we group by both, we don't get a single column for the keyword but instead + // one column per keyword + search engine url. this way, we could not get the top keywords using + // the ranking query. + $dimensions = array('referrer_data', 'referer_type'); + $rankingQuery->addLabelColumn('referrer_data'); + $selects = array( + 'CASE referer_type + WHEN ' . Piwik_Common::REFERER_TYPE_DIRECT_ENTRY . ' THEN \'\' + WHEN ' . Piwik_Common::REFERER_TYPE_SEARCH_ENGINE . ' THEN referer_keyword + WHEN ' . Piwik_Common::REFERER_TYPE_WEBSITE . ' THEN referer_url + WHEN ' . Piwik_Common::REFERER_TYPE_CAMPAIGN . ' THEN CONCAT(referer_name, \' \', referer_keyword) + END AS `referrer_data`'); + + // get one limited group per referrer type + $rankingQuery->partitionResultIntoMultipleGroups('referer_type', array( + Piwik_Common::REFERER_TYPE_DIRECT_ENTRY, + Piwik_Common::REFERER_TYPE_SEARCH_ENGINE, + Piwik_Common::REFERER_TYPE_WEBSITE, + Piwik_Common::REFERER_TYPE_CAMPAIGN + )); + + $type = $this->getColumnTypeSuffix($actionType); + $where = 'visit_entry_idaction_' . $type . ' = ' . intval($idaction); + + $metrics = array(Piwik_Metrics::INDEX_NB_VISITS); + $data = $logAggregator->queryVisitsByDimension($dimensions, $where, $selects, $metrics, $rankingQuery); + + $referrerData = array(); + $referrerSubData = array(); + + foreach ($data as $referrerType => &$subData) { + $referrerData[$referrerType] = array(Piwik_Metrics::INDEX_NB_VISITS => 0); + if ($referrerType != Piwik_Common::REFERER_TYPE_DIRECT_ENTRY) { + $referrerSubData[$referrerType] = array(); + } + + foreach ($subData as &$row) { + if ($referrerType == Piwik_Common::REFERER_TYPE_SEARCH_ENGINE && empty($row['referrer_data'])) { + $row['referrer_data'] = Piwik_Referers_API::LABEL_KEYWORD_NOT_DEFINED; + } + + $referrerData[$referrerType][Piwik_Metrics::INDEX_NB_VISITS] += $row[Piwik_Metrics::INDEX_NB_VISITS]; + + $label = $row['referrer_data']; + if ($label) { + $referrerSubData[$referrerType][$label] = array( + Piwik_Metrics::INDEX_NB_VISITS => $row[Piwik_Metrics::INDEX_NB_VISITS] + ); + } + } + } + + //FIXMEA refactor after integration tests written + $array = new Piwik_DataArray($referrerData, $referrerSubData); + return Piwik_ArchiveProcessor_Day::getDataTableFromDataArray($array); + } + + /** + * Get information about internal referrers (previous pages & loops, i.e. page refreshes) + * + * @param $idaction + * @param $actionType + * @param Piwik_ArchiveProcessor_Day $logAggregator + * @param $limitBeforeGrouping + * @return array(previousPages:Piwik_DataTable, loops:integer) + */ + protected function queryInternalReferrers($idaction, $actionType, $logAggregator, + $limitBeforeGrouping = false) + { + $rankingQuery = new Piwik_RankingQuery($limitBeforeGrouping ? $limitBeforeGrouping : $this->limitBeforeGrouping); + $rankingQuery->addLabelColumn(array('name', 'url_prefix')); + $rankingQuery->setColumnToMarkExcludedRows('is_self'); + $rankingQuery->partitionResultIntoMultipleGroups('action_partition', array(0, 1, 2)); + + $type = $this->getColumnTypeSuffix($actionType); + $mainActionType = Piwik_Tracker_Action::TYPE_ACTION_URL; + $dimension = 'idaction_url_ref'; + $isTitle = $actionType == 'title'; + + if ($isTitle) { + $mainActionType = Piwik_Tracker_Action::TYPE_ACTION_NAME; + $dimension = 'idaction_name_ref'; + } + + $selects = array( + 'log_action.name', + 'log_action.url_prefix', + 'CASE WHEN log_link_visit_action.idaction_' . $type . '_ref = ' . intval($idaction) . ' THEN 1 ELSE 0 END AS `is_self`', + 'CASE + WHEN log_action.type = ' . $mainActionType . ' THEN 1 + WHEN log_action.type = ' . Piwik_Tracker_Action::TYPE_SITE_SEARCH . ' THEN 2 + ELSE 0 + END AS `action_partition`' + ); + + $where = ' + log_link_visit_action.idaction_' . $type . ' = ' . intval($idaction); + + if ($dimension == 'idaction_url_ref') { + // site search referrers are logged with url_ref=NULL + // when we find one, we have to join on name_ref + $dimension = 'IF( idaction_url_ref IS NULL, idaction_name_ref, idaction_url_ref )'; + $joinLogActionOn = $dimension; + } else { + $joinLogActionOn = $dimension; + } + $metrics = array(Piwik_Metrics::INDEX_NB_ACTIONS); + $data = $logAggregator->queryActionsByDimension(array($dimension), $where, $selects, $metrics, $rankingQuery, $joinLogActionOn); + + $loops = 0; + $nbPageviews = 0; + $previousPagesDataTable = new Piwik_DataTable; + if (isset($data['result'][1])) { + foreach ($data['result'][1] as &$page) { + $nbActions = intval($page[Piwik_Metrics::INDEX_NB_ACTIONS]); + $previousPagesDataTable->addRow(new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => array( + 'label' => $this->getPageLabel($page, $isTitle), + Piwik_Metrics::INDEX_NB_ACTIONS => $nbActions + ) + ))); + $nbPageviews += $nbActions; + } + } + + $previousSearchesDataTable = new Piwik_DataTable; + if (isset($data['result'][2])) { + foreach ($data['result'][2] as &$search) { + $nbActions = intval($search[Piwik_Metrics::INDEX_NB_ACTIONS]); + $previousSearchesDataTable->addRow(new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => array( + 'label' => $search['name'], + Piwik_Metrics::INDEX_NB_ACTIONS => $nbActions + ) + ))); + $nbPageviews += $nbActions; + } + } + + if (isset($data['result'][0])) { + foreach ($data['result'][0] as &$referrer) { + $nbPageviews += intval($referrer[Piwik_Metrics::INDEX_NB_ACTIONS]); + } + } + + if (count($data['excludedFromLimit'])) { + $loops += intval($data['excludedFromLimit'][0][Piwik_Metrics::INDEX_NB_ACTIONS]); + $nbPageviews += $loops; + } + + return array( + 'pageviews' => $nbPageviews, + 'previousPages' => $previousPagesDataTable, + 'previousSiteSearches' => $previousSearchesDataTable, + 'loops' => $loops + ); + } + + private function getPageLabel(&$pageRecord, $isTitle) + { + if ($isTitle) { + $label = $pageRecord['name']; + if (empty($label)) { + $label = Piwik_Actions_ArchivingHelper::getUnknownActionName( + Piwik_Tracker_Action::TYPE_ACTION_NAME); + } + return $label; + } else if ($this->returnNormalizedUrls) { + return $pageRecord['name']; + } else { + return Piwik_Tracker_Action::reconstructNormalizedUrl( + $pageRecord['name'], $pageRecord['url_prefix']); + } + } + + private function getColumnTypeSuffix($actionType) + { + if ($actionType == 'title') { + return 'name'; + } + return 'url'; + } + + private $limitBeforeGrouping = 5; + private $totalTransitionsToFollowingActions = 0; + + private $returnNormalizedUrls = false; + + + /** + * Get the sum of all transitions to following actions (pages, outlinks, downloads). + * Only works if queryFollowingActions() has been used directly before. + */ + protected function getTotalTransitionsToFollowingActions() + { + return $this->totalTransitionsToFollowingActions; + } + /** * Add the external referrers to the report: * direct entries, websites, campaigns, search engines * - * @param Piwik_Transitions $transitionsArchiving - * @param $archiveProcessing + * @param Piwik_DataAccess_LogAggregator $logAggregator * @param $report * @param $idaction * @param string $actionType * @param $limitBeforeGrouping */ - private function addExternalReferrers($transitionsArchiving, $archiveProcessing, &$report, + private function addExternalReferrers($logAggregator, &$report, $idaction, $actionType, $limitBeforeGrouping) { - $data = $transitionsArchiving->queryExternalReferrers( - $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping); + $data = $this->queryExternalReferrers( + $idaction, $actionType, $logAggregator, $limitBeforeGrouping); $report['pageMetrics']['entries'] = 0; $report['referrers'] = array(); foreach ($data->getRows() as $row) { $referrerId = $row->getColumn('label'); - $visits = $row->getColumn(Piwik_Archive::INDEX_NB_VISITS); + $visits = $row->getColumn(Piwik_Metrics::INDEX_NB_VISITS); if ($visits) { // load details (i.e. subtables) $details = array(); @@ -247,7 +566,7 @@ class Piwik_Transitions_API foreach ($subTable->getRows() as $subRow) { $details[] = array( 'label' => $subRow->getColumn('label'), - 'referrals' => $subRow->getColumn(Piwik_Archive::INDEX_NB_VISITS) + 'referrals' => $subRow->getColumn(Piwik_Metrics::INDEX_NB_VISITS) ); } } @@ -289,9 +608,6 @@ class Piwik_Transitions_API } } - /** - * @ignore - */ public function getTranslations() { $controller = new Piwik_Transitions_Controller(); diff --git a/plugins/Transitions/Transitions.php b/plugins/Transitions/Transitions.php index 0e43c1cef9..b503266fc3 100644 --- a/plugins/Transitions/Transitions.php +++ b/plugins/Transitions/Transitions.php @@ -15,11 +15,6 @@ class Piwik_Transitions extends Piwik_Plugin { - private $limitBeforeGrouping = 5; - private $totalTransitionsToFollowingActions = 0; - - private $returnNormalizedUrls = false; - public function getInformation() { return array( @@ -50,331 +45,5 @@ class Piwik_Transitions extends Piwik_Plugin $jsFiles[] = 'plugins/Transitions/javascripts/transitions.js'; } - /** - * After calling this method, the query*()-Methods will return urls in their - * normalized form (without the prefix reconstructed) - */ - public function returnNormalizedUrls() - { - $this->returnNormalizedUrls = true; - } - - /** - * Get information about external referrers (i.e. search engines, websites & campaigns) - * - * @param $idaction - * @param $actionType - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - * @param $limitBeforeGrouping - * @return Piwik_DataTable - */ - public function queryExternalReferrers($idaction, $actionType, $archiveProcessing, - $limitBeforeGrouping = false) - { - $rankingQuery = new Piwik_RankingQuery($limitBeforeGrouping ? $limitBeforeGrouping : $this->limitBeforeGrouping); - - // we generate a single column that contains the interesting data for each referrer. - // the reason we cannot group by referer_* becomes clear when we look at search engine keywords. - // referer_url contains the url from the search engine, referer_keyword the keyword we want to - // group by. when we group by both, we don't get a single column for the keyword but instead - // one column per keyword + search engine url. this way, we could not get the top keywords using - // the ranking query. - $dimension = 'referrer_data'; - $rankingQuery->addLabelColumn('referrer_data'); - $select = ' - CASE referer_type - WHEN ' . Piwik_Common::REFERER_TYPE_DIRECT_ENTRY . ' THEN \'\' - WHEN ' . Piwik_Common::REFERER_TYPE_SEARCH_ENGINE . ' THEN referer_keyword - WHEN ' . Piwik_Common::REFERER_TYPE_WEBSITE . ' THEN referer_url - WHEN ' . Piwik_Common::REFERER_TYPE_CAMPAIGN . ' THEN CONCAT(referer_name, \' \', referer_keyword) - END AS referrer_data, - referer_type'; - - // get one limited group per referrer type - $rankingQuery->partitionResultIntoMultipleGroups('referer_type', array( - Piwik_Common::REFERER_TYPE_DIRECT_ENTRY, - Piwik_Common::REFERER_TYPE_SEARCH_ENGINE, - Piwik_Common::REFERER_TYPE_WEBSITE, - Piwik_Common::REFERER_TYPE_CAMPAIGN - )); - - $orderBy = '`' . Piwik_Archive::INDEX_NB_VISITS . '` DESC'; - - $type = $this->getColumnTypeSuffix($actionType); - $where = 'visit_entry_idaction_' . $type . ' = ' . intval($idaction); - - $metrics = array(Piwik_Archive::INDEX_NB_VISITS); - $data = $archiveProcessing->queryVisitsByDimension($dimension, $where, $metrics, $orderBy, - $rankingQuery, $select, $selectGeneratesLabelColumn = true); - - $referrerData = array(); - $referrerSubData = array(); - - foreach ($data as $referrerType => &$subData) { - $referrerData[$referrerType] = array(Piwik_Archive::INDEX_NB_VISITS => 0); - if ($referrerType != Piwik_Common::REFERER_TYPE_DIRECT_ENTRY) { - $referrerSubData[$referrerType] = array(); - } - - foreach ($subData as &$row) { - if ($referrerType == Piwik_Common::REFERER_TYPE_SEARCH_ENGINE && empty($row['referrer_data'])) { - $row['referrer_data'] = Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED; - } - - $referrerData[$referrerType][Piwik_Archive::INDEX_NB_VISITS] += $row[Piwik_Archive::INDEX_NB_VISITS]; - - $label = $row['referrer_data']; - if ($label) { - $referrerSubData[$referrerType][$label] = array( - Piwik_Archive::INDEX_NB_VISITS => $row[Piwik_Archive::INDEX_NB_VISITS] - ); - } - } - } - - return $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($referrerSubData, $referrerData); - } - - /** - * Get information about internal referrers (previous pages & loops, i.e. page refreshes) - * - * @param $idaction - * @param $actionType - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - * @param $limitBeforeGrouping - * @return array(previousPages:Piwik_DataTable, loops:integer) - */ - public function queryInternalReferrers($idaction, $actionType, $archiveProcessing, - $limitBeforeGrouping = false) - { - $rankingQuery = new Piwik_RankingQuery($limitBeforeGrouping ? $limitBeforeGrouping : $this->limitBeforeGrouping); - $rankingQuery->addLabelColumn(array('name', 'url_prefix')); - $rankingQuery->setColumnToMarkExcludedRows('is_self'); - $rankingQuery->partitionResultIntoMultipleGroups('action_partition', array(0, 1, 2)); - - $type = $this->getColumnTypeSuffix($actionType); - $mainActionType = Piwik_Tracker_Action::TYPE_ACTION_URL; - $dimension = 'idaction_url_ref'; - $isTitle = $actionType == 'title'; - if ($isTitle) { - $mainActionType = Piwik_Tracker_Action::TYPE_ACTION_NAME; - $dimension = 'idaction_name_ref'; - } - - $addSelect = ' - log_action.name, log_action.url_prefix, - CASE WHEN log_link_visit_action.idaction_' . $type . '_ref = ' . intval($idaction) . ' THEN 1 ELSE 0 END AS is_self, - CASE - WHEN log_action.type = ' . $mainActionType . ' THEN 1 - WHEN log_action.type = ' . Piwik_Tracker_Action::TYPE_SITE_SEARCH . ' THEN 2 - ELSE 0 - END AS action_partition'; - - $where = ' - log_link_visit_action.idaction_' . $type . ' = ' . intval($idaction); - - if ($dimension == 'idaction_url_ref') { - // site search referrers are logged with url_ref=NULL - // when we find one, we have to join on name_ref - $dimension = 'IF( idaction_url_ref IS NULL, idaction_name_ref, idaction_url_ref )'; - $joinLogActionOn = $dimension; - } else { - $joinLogActionOn = $dimension; - $dimension = array($dimension); - } - - $orderBy = '`' . Piwik_Archive::INDEX_NB_ACTIONS . '` DESC'; - - $metrics = array(Piwik_Archive::INDEX_NB_ACTIONS); - $data = $archiveProcessing->queryActionsByDimension($dimension, $where, $metrics, $orderBy, - $rankingQuery, $joinLogActionOn, $addSelect); - - $loops = 0; - $nbPageviews = 0; - $previousPagesDataTable = new Piwik_DataTable; - if (isset($data['result'][1])) { - foreach ($data['result'][1] as &$page) { - $nbActions = intval($page[Piwik_Archive::INDEX_NB_ACTIONS]); - $previousPagesDataTable->addRow(new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => array( - 'label' => $this->getPageLabel($page, $isTitle), - Piwik_Archive::INDEX_NB_ACTIONS => $nbActions - ) - ))); - $nbPageviews += $nbActions; - } - } - - $previousSearchesDataTable = new Piwik_DataTable; - if (isset($data['result'][2])) { - foreach ($data['result'][2] as &$search) { - $nbActions = intval($search[Piwik_Archive::INDEX_NB_ACTIONS]); - $previousSearchesDataTable->addRow(new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => array( - 'label' => $search['name'], - Piwik_Archive::INDEX_NB_ACTIONS => $nbActions - ) - ))); - $nbPageviews += $nbActions; - } - } - - if (isset($data['result'][0])) { - foreach ($data['result'][0] as &$referrer) { - $nbPageviews += intval($referrer[Piwik_Archive::INDEX_NB_ACTIONS]); - } - } - - if (count($data['excludedFromLimit'])) { - $loops += intval($data['excludedFromLimit'][0][Piwik_Archive::INDEX_NB_ACTIONS]); - $nbPageviews += $loops; - } - - return array( - 'pageviews' => $nbPageviews, - 'previousPages' => $previousPagesDataTable, - 'previousSiteSearches' => $previousSearchesDataTable, - 'loops' => $loops - ); - } - - private function getPageLabel(&$pageRecord, $isTitle) - { - if ($isTitle) { - $label = $pageRecord['name']; - if (empty($label)) { - $label = Piwik_Actions_ArchivingHelper::getUnknownActionName( - Piwik_Tracker_Action::TYPE_ACTION_NAME); - } - return $label; - } else if ($this->returnNormalizedUrls) { - return $pageRecord['name']; - } else { - return Piwik_Tracker_Action::reconstructNormalizedUrl( - $pageRecord['name'], $pageRecord['url_prefix']); - } - } - - /** - * Get information about the following actions (following pages, site searches, outlinks, downloads) - * - * @param $idaction - * @param $actionType - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - * @param $limitBeforeGrouping - * @param $includeLoops - * @return array(followingPages:Piwik_DataTable, outlinks:Piwik_DataTable, downloads:Piwik_DataTable) - */ - public function queryFollowingActions($idaction, $actionType, Piwik_ArchiveProcessing_Day $archiveProcessing, - $limitBeforeGrouping = false, $includeLoops = false) - { - $types = array(); - - $isTitle = ($actionType == 'title'); - if (!$isTitle) { - // specific setup for page urls - $types[Piwik_Tracker_Action::TYPE_ACTION_URL] = 'followingPages'; - $dimension = 'IF( idaction_url IS NULL, idaction_name, idaction_url )'; - // site search referrers are logged with url=NULL - // when we find one, we have to join on name - $joinLogActionColumn = $dimension; - $addSelect = 'log_action.name, log_action.url_prefix, log_action.type'; - } else { - // specific setup for page titles: - $types[Piwik_Tracker_Action::TYPE_ACTION_NAME] = 'followingPages'; - // join log_action on name and url and pick depending on url type - // the table joined on url is log_action1 - $joinLogActionColumn = array('idaction_url', 'idaction_name'); - $dimension = ' - CASE - ' /* following site search */ . ' - WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.idaction - ' /* following page view: use page title */ . ' - WHEN log_action1.type = ' . Piwik_Tracker_Action::TYPE_ACTION_URL . ' THEN log_action2.idaction - ' /* following download or outlink: use url */ . ' - ELSE log_action1.idaction - END - '; - $addSelect = ' - CASE - ' /* following site search */ . ' - WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.name - ' /* following page view: use page title */ . ' - WHEN log_action1.type = ' . Piwik_Tracker_Action::TYPE_ACTION_URL . ' THEN log_action2.name - ' /* following download or outlink: use url */ . ' - ELSE log_action1.name - END AS name, - CASE - ' /* following site search */ . ' - WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.type - ' /* following page view: use page title */ . ' - WHEN log_action1.type = ' . Piwik_Tracker_Action::TYPE_ACTION_URL . ' THEN log_action2.type - ' /* following download or outlink: use url */ . ' - ELSE log_action1.type - END AS type, - NULL AS url_prefix - '; - } - - // these types are available for both titles and urls - $types[Piwik_Tracker_Action::TYPE_SITE_SEARCH] = 'followingSiteSearches'; - $types[Piwik_Tracker_Action::TYPE_OUTLINK] = 'outlinks'; - $types[Piwik_Tracker_Action::TYPE_DOWNLOAD] = 'downloads'; - - $rankingQuery = new Piwik_RankingQuery($limitBeforeGrouping ? $limitBeforeGrouping : $this->limitBeforeGrouping); - $rankingQuery->addLabelColumn(array('name', 'url_prefix')); - $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($types)); - - $type = $this->getColumnTypeSuffix($actionType); - $where = 'log_link_visit_action.idaction_' . $type . '_ref = ' . intval($idaction); - if (!$includeLoops) { - $where .= ' AND (log_link_visit_action.idaction_' . $type . ' IS NULL OR ' - . 'log_link_visit_action.idaction_' . $type . ' != ' . intval($idaction) . ')'; - } - - $orderBy = '`' . Piwik_Archive::INDEX_NB_ACTIONS . '` DESC'; - - $metrics = array(Piwik_Archive::INDEX_NB_ACTIONS); - $data = $archiveProcessing->queryActionsByDimension($dimension, $where, $metrics, $orderBy, - $rankingQuery, $joinLogActionColumn, $addSelect); - - $this->totalTransitionsToFollowingActions = 0; - $dataTables = array(); - foreach ($types as $type => $recordName) { - $dataTable = new Piwik_DataTable; - if (isset($data[$type])) { - foreach ($data[$type] as &$record) { - $actions = intval($record[Piwik_Archive::INDEX_NB_ACTIONS]); - $dataTable->addRow(new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => array( - 'label' => $this->getPageLabel($record, $isTitle), - Piwik_Archive::INDEX_NB_ACTIONS => $actions - ) - ))); - $this->totalTransitionsToFollowingActions += $actions; - } - } - $dataTables[$recordName] = $dataTable; - } - - return $dataTables; - } - - /** - * Get the sum of all transitions to following actions (pages, outlinks, downloads). - * Only works if queryFollowingActions() has been used directly before. - */ - public function getTotalTransitionsToFollowingActions() - { - return $this->totalTransitionsToFollowingActions; - } - - private function getColumnTypeSuffix($actionType) - { - if ($actionType == 'title') { - return 'name'; - } - return 'url'; - } }
\ No newline at end of file diff --git a/plugins/UserCountry/API.php b/plugins/UserCountry/API.php index d8cdb266b6..261fda8f06 100644 --- a/plugins/UserCountry/API.php +++ b/plugins/UserCountry/API.php @@ -32,11 +32,9 @@ class Piwik_UserCountry_API public function getCountry($idSite, $period, $date, $segment = false) { - $recordName = Piwik_UserCountry::VISITS_BY_COUNTRY_RECORD_NAME; - $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserCountry_Archiver::COUNTRY_RECORD_NAME, $idSite, $period, $date, $segment); - // apply filter on the whole datatable in order the inline search to work (searches - // are done on "beautiful" label) + // apply filter on the whole datatable in order the inline search to work (searches are done on "beautiful" label) $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'code')); $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getFlagFromCode')); $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_CountryTranslate')); @@ -48,8 +46,7 @@ class Piwik_UserCountry_API public function getContinent($idSite, $period, $date, $segment = false) { - $recordName = Piwik_UserCountry::VISITS_BY_COUNTRY_RECORD_NAME; - $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserCountry_Archiver::COUNTRY_RECORD_NAME, $idSite, $period, $date, $segment); $getContinent = array('Piwik_Common', 'getContinent'); $dataTable->filter('GroupBy', array('label', $getContinent)); @@ -71,10 +68,9 @@ class Piwik_UserCountry_API */ public function getRegion($idSite, $period, $date, $segment = false) { - $recordName = Piwik_UserCountry::VISITS_BY_REGION_RECORD_NAME; - $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserCountry_Archiver::REGION_RECORD_NAME, $idSite, $period, $date, $segment); - $separator = Piwik_UserCountry::LOCATION_SEPARATOR; + $separator = Piwik_UserCountry_Archiver::LOCATION_SEPARATOR; $unk = Piwik_Tracker_Visit::UNKNOWN_CODE; // split the label and put the elements into the 'region' and 'country' metadata fields @@ -114,10 +110,9 @@ class Piwik_UserCountry_API */ public function getCity($idSite, $period, $date, $segment = false) { - $recordName = Piwik_UserCountry::VISITS_BY_CITY_RECORD_NAME; - $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserCountry_Archiver::CITY_RECORD_NAME, $idSite, $period, $date, $segment); - $separator = Piwik_UserCountry::LOCATION_SEPARATOR; + $separator = Piwik_UserCountry_Archiver::LOCATION_SEPARATOR; $unk = Piwik_Tracker_Visit::UNKNOWN_CODE; // split the label and put the elements into the 'city_name', 'region', 'country', @@ -195,7 +190,7 @@ class Piwik_UserCountry_API Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); $dataTable = $archive->getDataTable($name); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS)); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS)); $dataTable->queueFilter('ReplaceColumnNames'); return $dataTable; } @@ -204,6 +199,6 @@ class Piwik_UserCountry_API { Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - return $archive->getDataTableFromNumeric('UserCountry_distinctCountries'); + return $archive->getDataTableFromNumeric(Piwik_UserCountry_Archiver::DISTINCT_COUNTRIES_METRIC); } } diff --git a/plugins/UserCountry/Archiver.php b/plugins/UserCountry/Archiver.php new file mode 100644 index 0000000000..2fa2573d55 --- /dev/null +++ b/plugins/UserCountry/Archiver.php @@ -0,0 +1,175 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_UserCountry + */ + +class Piwik_UserCountry_Archiver extends Piwik_PluginsArchiver +{ + const COUNTRY_RECORD_NAME = 'UserCountry_country'; + const REGION_RECORD_NAME = 'UserCountry_region'; + const CITY_RECORD_NAME = 'UserCountry_city'; + const DISTINCT_COUNTRIES_METRIC = 'UserCountry_distinctCountries'; + + // separate region, city & country info in stored report labels + const LOCATION_SEPARATOR = '|'; + + private $latLongForCities = array(); + + private $dataArrays = array(); + + protected $maximumRows; + + const COUNTRY_FIELD = 'location_country'; + + const REGION_FIELD = 'location_region'; + + const CITY_FIELD = 'location_city'; + + protected $dimensions = array( self::COUNTRY_FIELD, self::REGION_FIELD, self::CITY_FIELD ); + + protected $arrays; + const LATITUDE_FIELD = 'location_latitude'; + const LONGITUDE_FIELD = 'location_longitude'; + + + public function archiveDay() + { + foreach($this->dimensions as $dimension) { + $this->arrays[$dimension] = new Piwik_DataArray(); + } + $this->aggregateFromVisits(); + $this->aggregateFromConversions(); + $this->recordDayReports(); + } + + protected function aggregateFromVisits() + { + $additionalSelects = array('MAX(log_visit.location_latitude) as location_latitude', + 'MAX(log_visit.location_longitude) as location_longitude'); + $query = $this->getLogAggregator()->queryVisitsByDimension($this->dimensions, $where = false, $additionalSelects); + if ($query === false) { + return; + } + + while ($row = $query->fetch()) { + $this->makeRegionCityLabelsUnique($row); + $this->rememberCityLatLong($row); + + /* @var $dataArray Piwik_DataArray */ + foreach ($this->arrays as $dimension => $dataArray) { + $dataArray->sumMetricsVisits($row[$dimension], $row); + } + } + } + + /** + * Makes sure the region and city of a query row are unique. + * + * @param array $row + */ + private function makeRegionCityLabelsUnique(&$row) + { + // remove the location separator from the region/city/country we get from the query + foreach ($this->dimensions as $column) { + $row[$column] = str_replace(self::LOCATION_SEPARATOR, '', $row[$column]); + } + + if (!empty($row[self::REGION_FIELD])) { + $row[self::REGION_FIELD] = $row[self::REGION_FIELD] . self::LOCATION_SEPARATOR . $row[self::COUNTRY_FIELD]; + } + + if (!empty($row[self::CITY_FIELD])) { + $row[self::CITY_FIELD] = $row[self::CITY_FIELD] . self::LOCATION_SEPARATOR . $row[self::REGION_FIELD]; + } + } + + protected function rememberCityLatLong($row) + { + if ( !empty($row[self::CITY_FIELD]) + && !empty($row[self::LATITUDE_FIELD]) + && !empty($row[self::LONGITUDE_FIELD]) + && empty($this->latLongForCities[$row[self::CITY_FIELD]])) { + $this->latLongForCities[$row[self::CITY_FIELD]] = array($row[self::LATITUDE_FIELD], $row[self::LONGITUDE_FIELD]); + } + } + + protected function aggregateFromConversions() + { + $query = $this->getLogAggregator()->queryConversionsByDimension($this->dimensions); + + if ($query === false) { + return; + } + + while ($row = $query->fetch()) { + $this->makeRegionCityLabelsUnique($row); + + /* @var $dataArray Piwik_DataArray */ + foreach ($this->arrays as $dimension => $dataArray) { + $dataArray->sumMetricsGoals($row[$dimension], $row); + } + } + + /* @var $dataArray Piwik_DataArray */ + foreach ($this->arrays as $dataArray) { + $dataArray->enrichMetricsWithConversions(); + } + } + + protected function recordDayReports() + { + $tableCountry = Piwik_ArchiveProcessor_Day::getDataTableFromDataArray($this->arrays[self::COUNTRY_FIELD]); + $this->getProcessor()->insertBlobRecord(self::COUNTRY_RECORD_NAME, $tableCountry->getSerialized()); + $this->getProcessor()->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, $tableCountry->getRowsCount()); + + $tableRegion = Piwik_ArchiveProcessor_Day::getDataTableFromDataArray($this->arrays[self::REGION_FIELD]); + $serialized = $tableRegion->getSerialized($this->maximumRows, $this->maximumRows, Piwik_Metrics::INDEX_NB_VISITS); + $this->getProcessor()->insertBlobRecord(self::REGION_RECORD_NAME, $serialized); + + $tableCity = Piwik_ArchiveProcessor_Day::getDataTableFromDataArray($this->arrays[self::CITY_FIELD]); + $this->setLatitudeLongitude($tableCity); + $serialized = $tableCity->getSerialized($this->maximumRows, $this->maximumRows, Piwik_Metrics::INDEX_NB_VISITS); + $this->getProcessor()->insertBlobRecord(self::CITY_RECORD_NAME, $serialized); + } + + /** + * Utility method, appends latitude/longitude pairs to city table labels, if that data + * exists for the city. + */ + private function setLatitudeLongitude(Piwik_DataTable $tableCity) + { + foreach ($tableCity->getRows() as $row) { + $label = $row->getColumn('label'); + if (isset($this->latLongForCities[$label])) { + // get lat/long for city + list($lat, $long) = $this->latLongForCities[$label]; + $lat = round($lat, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION); + $long = round($long, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION); + + // set latitude + longitude metadata + $row->setMetadata('lat', $lat); + $row->setMetadata('long', $long); + } + } + } + + public function archivePeriod() + { + $dataTableToSum = array( + self::COUNTRY_RECORD_NAME, + self::REGION_RECORD_NAME, + self::CITY_RECORD_NAME, + ); + + $nameToCount = $this->getProcessor()->aggregateDataTableReports($dataTableToSum); + $this->getProcessor()->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, + $nameToCount[self::COUNTRY_RECORD_NAME]['level0']); + } + +}
\ No newline at end of file diff --git a/plugins/UserCountry/UserCountry.php b/plugins/UserCountry/UserCountry.php index a054dae31c..1e69e344e4 100644 --- a/plugins/UserCountry/UserCountry.php +++ b/plugins/UserCountry/UserCountry.php @@ -20,14 +20,6 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/GeoIPAutoUpdater.php'; */ class Piwik_UserCountry extends Piwik_Plugin { - const VISITS_BY_COUNTRY_RECORD_NAME = 'UserCountry_country'; - const VISITS_BY_REGION_RECORD_NAME = 'UserCountry_region'; - const VISITS_BY_CITY_RECORD_NAME = 'UserCountry_city'; - - const DISTINCT_COUNTRIES_METRIC = 'UserCountry_distinctCountries'; - - // separate region, city & country info in stored report labels - const LOCATION_SEPARATOR = '|'; public function getInformation() { @@ -299,187 +291,28 @@ class Piwik_UserCountry extends Piwik_Plugin */ function archivePeriod($notification) { - /** - * @param Piwik_ArchiveProcessing_Period $archiveProcessing - */ - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $dataTableToSum = array( - self::VISITS_BY_COUNTRY_RECORD_NAME, - self::VISITS_BY_REGION_RECORD_NAME, - self::VISITS_BY_CITY_RECORD_NAME, - ); + $archiveProcessor = $notification->getNotificationObject(); - $nameToCount = $archiveProcessing->archiveDataTable($dataTableToSum); - $archiveProcessing->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, - $nameToCount[self::VISITS_BY_COUNTRY_RECORD_NAME]['level0']); + $archiving = new Piwik_UserCountry_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); + } } - private $interestTables = null; - private $latLongForCities = null; - /** * @param Piwik_Event_Notification $notification notification object * @return mixed */ function archiveDay($notification) { - /** - * @var Piwik_ArchiveProcessing - */ - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->interestTables = array('location_country' => array(), - 'location_region' => array(), - 'location_city' => array()); - $this->latLongForCities = array(); - - $this->archiveDayAggregateVisits($archiveProcessing); - $this->archiveDayAggregateGoals($archiveProcessing); - $this->archiveDayRecordInDatabase($archiveProcessing); - - unset($this->interestTables); - unset($this->latLongForCities); - } - - /** - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - */ - protected function archiveDayAggregateVisits($archiveProcessing) - { - $dimensions = array_keys($this->interestTables); - $query = $archiveProcessing->queryVisitsByDimension( - $dimensions, - $where = '', - $metrics = false, - $orderBy = false, - $rankingQuery = null, - $addSelect = 'MAX(log_visit.location_latitude) as location_latitude, - MAX(log_visit.location_longitude) as location_longitude' - ); - - if ($query === false) { - return; - } - - while ($row = $query->fetch()) { - // get latitude/longitude if there's a city - $lat = $long = false; - if (!empty($row['location_city'])) { - if (!empty($row['location_latitude'])) { - $lat = $row['location_latitude']; - } - if (!empty($row['location_longitude'])) { - $long = $row['location_longitude']; - } - } - - // make sure regions & cities w/ the same name don't get merged - $this->setLongCityRegionId($row); - - // store latitude/longitude, if we should - if ($lat !== false && $long !== false) { - $this->latLongForCities[$row['location_city']] = array($lat, $long); - } + $archiveProcessor = $notification->getNotificationObject(); - // add the stats to each dimension's table - foreach ($this->interestTables as $dimension => &$table) { - $label = (string)$row[$dimension]; - - if (!isset($table[$label])) { - $table[$label] = $archiveProcessing->getNewInterestRow(); - } - $archiveProcessing->updateInterestStats($row, $table[$label]); - } + $archiving = new Piwik_UserCountry_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); } } - /** - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - */ - protected function archiveDayAggregateGoals($archiveProcessing) - { - $dimensions = array_keys($this->interestTables); - $query = $archiveProcessing->queryConversionsByDimension($dimensions); - - if ($query === false) { - return; - } - - while ($row = $query->fetch()) { - // make sure regions & cities w/ the same name don't get merged - $this->setLongCityRegionId($row); - - $idGoal = $row['idgoal']; - foreach ($this->interestTables as $dimension => &$table) { - $label = (string)$row[$dimension]; - - if (!isset($table[$label][Piwik_Archive::INDEX_GOALS][$idGoal])) { - $table[$label][Piwik_Archive::INDEX_GOALS][$idGoal] = $archiveProcessing->getNewGoalRow($idGoal); - } - - $archiveProcessing->updateGoalStats($row, $table[$label][Piwik_Archive::INDEX_GOALS][$idGoal]); - } - } - - foreach ($this->interestTables as &$table) { - $archiveProcessing->enrichConversionsByLabelArray($table); - } - } - - /** - * @param Piwik_ArchiveProcessing_Day $archiveProcessing - */ - protected function archiveDayRecordInDatabase($archiveProcessing) - { - $maximumRows = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard']; - - $tableCountry = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_country']); - $archiveProcessing->insertBlobRecord(self::VISITS_BY_COUNTRY_RECORD_NAME, $tableCountry->getSerialized()); - $archiveProcessing->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, $tableCountry->getRowsCount()); - destroy($tableCountry); - - $tableRegion = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_region']); - $serialized = $tableRegion->getSerialized($maximumRows, $maximumRows, Piwik_Archive::INDEX_NB_VISITS); - $archiveProcessing->insertBlobRecord(self::VISITS_BY_REGION_RECORD_NAME, $serialized); - destroy($tableRegion); - - $tableCity = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_city']); - $this->setLatitudeLongitude($tableCity); - $serialized = $tableCity->getSerialized($maximumRows, $maximumRows, Piwik_Archive::INDEX_NB_VISITS); - $archiveProcessing->insertBlobRecord(self::VISITS_BY_CITY_RECORD_NAME, $serialized); - destroy($tableCity); - } - - /** - * Makes sure the region and city of a query row are unique. - * - * @param array $row - */ - private function setLongCityRegionId(&$row) - { - static $locationColumns = array('location_region', 'location_country', 'location_city'); - - // to be on the safe side, remove the location separator from the region/city/country we - // get from the query - foreach ($locationColumns as $column) { - $row[$column] = str_replace(self::LOCATION_SEPARATOR, '', $row[$column]); - } - - if (!empty($row['location_region'])) // do not differentiate between unknown regions - { - $row['location_region'] = $row['location_region'] . self::LOCATION_SEPARATOR . $row['location_country']; - } - - if (!empty($row['location_city'])) // do not differentiate between unknown cities - { - $row['location_city'] = $row['location_city'] . self::LOCATION_SEPARATOR . $row['location_region']; - } - } /** * Returns a list of country codes for a given continent code. @@ -500,24 +333,4 @@ class Piwik_UserCountry extends Piwik_Plugin 'bind' => '-'); // HACK: SegmentExpression requires a $bind, even if there's nothing to bind } - /** - * Utility method, appends latitude/longitude pairs to city table labels, if that data - * exists for the city. - */ - private function setLatitudeLongitude($tableCity) - { - foreach ($tableCity->getRows() as $row) { - $label = $row->getColumn('label'); - if (isset($this->latLongForCities[$label])) { - // get lat/long for city - list($lat, $long) = $this->latLongForCities[$label]; - $lat = round($lat, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION); - $long = round($long, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION); - - // set latitude + longitude metadata - $row->setMetadata('lat', $lat); - $row->setMetadata('long', $long); - } - } - } } diff --git a/plugins/UserCountry/functions.php b/plugins/UserCountry/functions.php index debe9166b2..9db8a85d78 100644 --- a/plugins/UserCountry/functions.php +++ b/plugins/UserCountry/functions.php @@ -92,7 +92,7 @@ function Piwik_UserCountry_getRegionName($label) return Piwik_Translate('General_Unknown'); } - list($regionCode, $countryCode) = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label); + list($regionCode, $countryCode) = explode(Piwik_UserCountry_Archiver::LOCATION_SEPARATOR, $label); return Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode); } @@ -114,7 +114,7 @@ function Piwik_UserCountry_getPrettyRegionName($label) return Piwik_Translate('General_Unknown'); } - list($regionCode, $countryCode) = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label); + list($regionCode, $countryCode) = explode(Piwik_UserCountry_Archiver::LOCATION_SEPARATOR, $label); $result = Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode); if ($countryCode != Piwik_Tracker_Visit::UNKNOWN_CODE && $countryCode != '') { @@ -143,7 +143,7 @@ function Piwik_UserCountry_getPrettyCityName($label) } // get city name, region code & country code - $parts = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label); + $parts = explode(Piwik_UserCountry_Archiver::LOCATION_SEPARATOR, $label); $cityName = $parts[0]; $regionCode = $parts[1]; $countryCode = $parts[2]; diff --git a/plugins/UserCountry/javascripts/userCountry.js b/plugins/UserCountry/javascripts/userCountry.js index 68e9165e1c..953c6c0c49 100755 --- a/plugins/UserCountry/javascripts/userCountry.js +++ b/plugins/UserCountry/javascripts/userCountry.js @@ -68,7 +68,6 @@ $(document).ready(function () { var data = { module: 'UserCountry', action: action, - token_auth: piwik.token_auth, 'continue': cont ? 1 : 0 }; for (var k in extraData) { @@ -190,4 +189,58 @@ $(document).ready(function () { ajaxRequest.setCallback(updateGeoIPSuccess); ajaxRequest.send(false); }); + }); + + $('body').on('click', '#update-geoip-links', function () { + $('#geoipdb-update-info-error').hide(); + + var currentDownloading = null, + updateGeoIPSuccess = function (response) { + if (response && response.error) { + $('#geoip-progressbar-container').hide(); + $('#geoipdb-update-info-error').html(response.error).show(); + } + else if (response && response.to_download) { + var continuing = currentDownloading == response.to_download; + currentDownloading = response.to_download; + + // show progress bar w/ message + $('#geoip-updater-progressbar').progressbar('option', 'value', 1); + $('#geoip-updater-progressbar-label').html(response.to_download_label); + $('#geoip-progressbar-container').show(); + + // start/continue download + downloadNextChunk( + 'downloadMissingGeoIpDb', 'geoipdb-update-info', 'geoip-updater-progressbar', + continuing, {key: response.to_download}, updateGeoIPSuccess); + } + else { + $('#geoipdb-update-info-error').hide(); + $('#geoip-updater-progressbar-label').html(''); + $('#geoip-progressbar-container').hide(); + + // fade in/out Done message + $('#done-updating-updater').fadeIn(1000, function () { + setTimeout(function () { + $('#done-updating-updater').fadeOut(1000); + }, 3000); + }); + } + }; + + // setup the auto-updater + var ajaxRequest = new ajaxHelper(); + ajaxRequest.addParams({ + period: $('#geoip-update-period-cell').find('>input:checked').val() + }, 'get'); + ajaxRequest.addParams({ + module: 'UserCountry', + action: 'updateGeoIPLinks', + loc_db: $('#geoip-location-db').val(), + isp_db: $('#geoip-isp-db').val(), + org_db: $('#geoip-org-db').val() + }, 'post'); + ajaxRequest.setCallback(updateGeoIPSuccess); + ajaxRequest.send(false); + }); }); diff --git a/plugins/UserCountryMap/Controller.php b/plugins/UserCountryMap/Controller.php index d6ef194faa..078c1745bf 100644 --- a/plugins/UserCountryMap/Controller.php +++ b/plugins/UserCountryMap/Controller.php @@ -66,18 +66,15 @@ class Piwik_UserCountryMap_Controller extends Piwik_Controller 'no_data' => Piwik_Translate('CoreHome_ThereIsNoDataForThisReport') )); - // template for ajax requests - $view->reqParamsJSON = Piwik_Common::json_encode(array( - 'period' => $period, - 'idSite' => $idSite, - 'date' => $date, - 'token_auth' => $token_auth, - 'format' => 'json', - 'segment' => Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')), - 'showRawMetrics' => 1, - 'enable_filter_excludelowpop' => 1, - 'filter_excludelowpop_value' => -1 - )); + $view->reqParamsJSON = $this->getEnrichedRequest($params = array( + 'period' => $period, + 'idSite' => $idSite, + 'date' => $date, + 'token_auth' => $token_auth, + 'enable_filter_excludelowpop' => 1, + 'filter_excludelowpop_value' => -1 + )); + $view->metrics = $config['metrics'] = $this->getMetrics($idSite, $period, $date, $token_auth); $config['svgBasePath'] = 'plugins/UserCountryMap/svg/'; $config['mapCssPath'] = 'plugins/UserCountryMap/stylesheets/map.css'; @@ -138,19 +135,29 @@ class Piwik_UserCountryMap_Controller extends Piwik_Controller 'goal_conversions' => Piwik_Translate('UserCountryMap_GoalConversions'), )); - $view->reqParamsJSON = json_encode(array( + $view->reqParamsJSON = $this->getEnrichedRequest(array( 'period' => 'range', 'idSite' => $idSite, 'date' => self::REAL_TIME_WINDOW, 'token_auth' => $token_auth, - 'format' => 'json', - 'segment' => Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')), - 'showRawMetrics' => 1 )); echo $view->render(); } + private function getEnrichedRequest($params) + { + $params['format'] = 'json'; + $params['showRawMetrics'] = 1; + $segment = Piwik_ViewDataTable::getRawSegmentFromRequest(); + if(!empty($segment)) { + $params['segment'] = $segment; + } + + return Piwik_Common::json_encode($params); + } + + private function checkUserCountryPluginEnabled() { if (!Piwik_PluginsManager::getInstance()->isPluginActivated('UserCountry')) { @@ -192,7 +199,7 @@ class Piwik_UserCountryMap_Controller extends Piwik_Controller . "&period=" . $period . "&date=" . $date . "&token_auth=" . $token_auth - . "&segment=" . Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')) + . "&segment=" . Piwik_ViewDataTable::getRawSegmentFromRequest() . "&enable_filter_excludelowpop=1" . "&showRawMetrics=1"; diff --git a/plugins/UserCountryMap/javascripts/realtime-map.js b/plugins/UserCountryMap/javascripts/realtime-map.js index 431b749089..54d5cdfdc3 100644 --- a/plugins/UserCountryMap/javascripts/realtime-map.js +++ b/plugins/UserCountryMap/javascripts/realtime-map.js @@ -215,7 +215,7 @@ * that corresponds to a visit on the map */ function highlightVisit(r) { - $('#visitsLive li#' + r.idVisit + ' .datetime') + $('#visitsLive').find('li#' + r.idVisit + ' .datetime') .css('background', '#E4CD74'); } @@ -224,7 +224,7 @@ * the visit marker on the map */ function unhighlightVisit(r) { - $('#visitsLive li#' + r.idVisit + ' .datetime') + $('#visitsLive').find('li#' + r.idVisit + ' .datetime') .css({ background: '#E4E2D7' }); } diff --git a/plugins/UserCountryMap/javascripts/visitor-map.js b/plugins/UserCountryMap/javascripts/visitor-map.js index 0cd41f0164..5231865583 100644 --- a/plugins/UserCountryMap/javascripts/visitor-map.js +++ b/plugins/UserCountryMap/javascripts/visitor-map.js @@ -1167,7 +1167,7 @@ resize: function () { var ratio, w, h, map = this.map, - maxHeight = $(window).height() - (this.theWidget && this.theWidget.isMaximised ? 150 : 55); + maxHeight = $(window).height() - (this.theWidget && this.theWidget.isMaximised ? 150 : 79); ratio = map.viewAB.width / map.viewAB.height; w = map.container.width(); h = w / ratio; diff --git a/plugins/UserCountryMap/stylesheets/visitor-map.css b/plugins/UserCountryMap/stylesheets/visitor-map.css index cc3340d895..534db96fd5 100644 --- a/plugins/UserCountryMap/stylesheets/visitor-map.css +++ b/plugins/UserCountryMap/stylesheets/visitor-map.css @@ -13,7 +13,7 @@ top: 42% !important; right: 10px !important; left: 10px !important; - z-index: 999 !important; + z-index: 990 !important; display: block; font-size: 12px; color: #000; diff --git a/plugins/UserSettings/API.php b/plugins/UserSettings/API.php index e438b96b2f..1aa80230f9 100644 --- a/plugins/UserSettings/API.php +++ b/plugins/UserSettings/API.php @@ -37,7 +37,7 @@ class Piwik_UserSettings_API Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); $dataTable = $archive->getDataTable($name); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS)); + $dataTable->filter('Sort', array(Piwik_Metrics::INDEX_NB_VISITS)); $dataTable->queueFilter('ReplaceColumnNames'); $dataTable->queueFilter('ReplaceSummaryRowLabel'); return $dataTable; @@ -45,20 +45,20 @@ class Piwik_UserSettings_API public function getResolution($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('UserSettings_resolution', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::RESOLUTION_RECORD_NAME, $idSite, $period, $date, $segment); return $dataTable; } public function getConfiguration($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('UserSettings_configuration', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::CONFIGURATION_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getConfigurationLabel')); return $dataTable; } public function getOS($idSite, $period, $date, $segment = false, $addShortLabel = true) { - $dataTable = $this->getDataTable('UserSettings_os', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::OS_RECORD_NAME, $idSite, $period, $date, $segment); // these filters are applied directly so other API methods can use GroupBy on the result of this method $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getOSLogo')); if ($addShortLabel) { @@ -88,18 +88,29 @@ class Piwik_UserSettings_API { $dataTable = $this->getOS($idSite, $period, $date, $segment, $addShortLabel = false); $dataTable->filter('GroupBy', array('label', 'Piwik_UserSettings_getDeviceTypeFromOS')); + $this->ensureDefaultRowsInTable($dataTable); - // make sure the datatable has a row for mobile & desktop (if it has rows) - $dataTables = array($dataTable); - if ($dataTable instanceof Piwik_DataTable_Array) { - $dataTables = $dataTable->getArray(); - } + // set the logo metadata + $dataTable->queueFilter('MetadataCallbackReplace', + array('logo', 'Piwik_UserSettings_getDeviceTypeImg', null, array('label'))); + + // translate the labels + $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Translate')); + return $dataTable; + } + + protected function ensureDefaultRowsInTable($dataTable) + { $requiredRows = array( - 'General_Desktop' => Piwik_Archive::INDEX_NB_VISITS, - 'General_Mobile' => Piwik_Archive::INDEX_NB_VISITS + 'General_Desktop' => Piwik_Metrics::INDEX_NB_VISITS, + 'General_Mobile' => Piwik_Metrics::INDEX_NB_VISITS ); + $dataTables = array($dataTable); + if ($dataTable instanceof Piwik_DataTable_Array) { + $dataTables = $dataTable->getArray(); + } foreach ($dataTables AS $table) { if ($table->getRowsCount() == 0) { continue; @@ -113,22 +124,19 @@ class Piwik_UserSettings_API } } } + } - // set the logo metadata - $dataTable->queueFilter('MetadataCallbackReplace', - array('logo', 'Piwik_UserSettings_getDeviceTypeImg', null, array('label'))); - - // translate the labels - $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Translate')); - + public function getBrowserVersion($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getBrowserTable($idSite, $period, $date, $segment); + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'Piwik_getBrowserShortLabel')); return $dataTable; } - public function getBrowserVersion($idSite, $period, $date, $segment = false) + protected function getBrowserTable($idSite, $period, $date, $segment) { - $dataTable = $this->getDataTable('UserSettings_browser', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::BROWSER_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowsersLogo')); - $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'Piwik_getBrowserShortLabel')); $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel')); return $dataTable; } @@ -139,19 +147,14 @@ class Piwik_UserSettings_API */ public function getBrowser($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('UserSettings_browser', $idSite, $period, $date, $segment); - $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowsersLogo')); - $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel')); - - $getBrowserFromBrowserVersion = 'Piwik_UserSettings_getBrowserFromBrowserVersion'; - $dataTable->filter('GroupBy', array('label', $getBrowserFromBrowserVersion)); - + $dataTable = $this->getBrowserTable($idSite, $period, $date, $segment); + $dataTable->filter('GroupBy', array('label', 'Piwik_UserSettings_getBrowserFromBrowserVersion')); return $dataTable; } public function getBrowserType($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('UserSettings_browserType', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::BROWSER_TYPE_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'ucfirst')); $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserTypeLabel')); return $dataTable; @@ -159,7 +162,7 @@ class Piwik_UserSettings_API public function getWideScreen($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('UserSettings_wideScreen', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::SCREEN_TYPE_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getScreensLogo')); $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'ucfirst')); return $dataTable; @@ -168,10 +171,10 @@ class Piwik_UserSettings_API public function getPlugin($idSite, $period, $date, $segment = false) { // fetch all archive data required - $dataTable = $this->getDataTable('UserSettings_plugin', $idSite, $period, $date, $segment); - $browserTypes = $this->getDataTable('UserSettings_browserType', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::PLUGIN_RECORD_NAME, $idSite, $period, $date, $segment); + $browserTypes = $this->getDataTable(Piwik_UserSettings_Archiver::BROWSER_TYPE_RECORD_NAME, $idSite, $period, $date, $segment); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - $visitsSums = $archive->getNumeric('nb_visits'); + $visitsSums = $archive->getDataTableFromNumeric('nb_visits'); // check whether given tables are arrays if ($dataTable instanceof Piwik_DataTable_Array) { @@ -179,14 +182,13 @@ class Piwik_UserSettings_API $browserTypesArray = $browserTypes->getArray(); $visitSumsArray = $visitsSums->getArray(); } else { - $tableArray = Array($dataTable); - $browserTypesArray = Array($browserTypes); - $visitSumsArray = Array($visitsSums); + $tableArray = array($dataTable); + $browserTypesArray = array($browserTypes); + $visitSumsArray = array($visitsSums); } // walk through the results and calculate the percentage foreach ($tableArray as $key => $table) { - // get according browserType table foreach ($browserTypesArray AS $k => $browsers) { if ($k == $key) { @@ -198,7 +200,11 @@ class Piwik_UserSettings_API foreach ($visitSumsArray AS $k => $visits) { if ($k == $key) { if (is_object($visits)) { - $visitsSumTotal = (float)$visits->getFirstRow()->getColumn(0); + if ($visits->getRowsCount() == 0) { + $visitsSumTotal = 0; + } else { + $visitsSumTotal = (float)$visits->getFirstRow()->getColumn('nb_visits'); + } } else { $visitsSumTotal = (float)$visits; } @@ -210,7 +216,7 @@ class Piwik_UserSettings_API $ieStats = $browserType->getRowFromLabel('ie'); if ($ieStats !== false) { - $ieVisits = $ieStats->getColumn(Piwik_Archive::INDEX_NB_VISITS); + $ieVisits = $ieStats->getColumn(Piwik_Metrics::INDEX_NB_VISITS); } $visitsSum = $visitsSumTotal - $ieVisits; @@ -222,7 +228,7 @@ class Piwik_UserSettings_API // The filter must be applied now so that the new column can // be sorted by the generic filters (applied right after this loop exits) - $table->filter('ColumnCallbackAddColumnPercentage', array('nb_visits_percentage', Piwik_Archive::INDEX_NB_VISITS, $visitsSum, 1)); + $table->filter('ColumnCallbackAddColumnPercentage', array('nb_visits_percentage', Piwik_Metrics::INDEX_NB_VISITS, $visitsSum, 1)); $table->filter('RangeCheck', array('nb_visits_percentage')); } @@ -234,7 +240,7 @@ class Piwik_UserSettings_API public function getLanguage($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('UserSettings_language', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_UserSettings_Archiver::LANGUAGE_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_LanguageTranslate')); $dataTable->filter('ReplaceColumnNames'); return $dataTable; diff --git a/plugins/UserSettings/Archiver.php b/plugins/UserSettings/Archiver.php new file mode 100644 index 0000000000..d6ba9e32b2 --- /dev/null +++ b/plugins/UserSettings/Archiver.php @@ -0,0 +1,152 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_UserSettings + */ + +require_once PIWIK_INCLUDE_PATH . '/plugins/UserSettings/functions.php'; + +class Piwik_UserSettings_Archiver extends Piwik_PluginsArchiver +{ + const LANGUAGE_RECORD_NAME = 'UserSettings_language'; + const PLUGIN_RECORD_NAME = 'UserSettings_plugin'; + const SCREEN_TYPE_RECORD_NAME = 'UserSettings_wideScreen'; + const RESOLUTION_RECORD_NAME = 'UserSettings_resolution'; + const BROWSER_RECORD_NAME = 'UserSettings_browser'; + const BROWSER_TYPE_RECORD_NAME = 'UserSettings_browserType'; + const OS_RECORD_NAME = 'UserSettings_os'; + const CONFIGURATION_RECORD_NAME = 'UserSettings_configuration'; + + const LANGUAGE_DIMENSION = "log_visit.location_browser_lang"; + const RESOLUTION_DIMENSION = "log_visit.config_resolution"; + const BROWSER_VERSION_DIMENSION = "CONCAT(log_visit.config_browser_name, ';', log_visit.config_browser_version)"; + const OS_DIMENSION = "log_visit.config_os"; + const CONFIGURATION_DIMENSION = "CONCAT(log_visit.config_os, ';', log_visit.config_browser_name, ';', log_visit.config_resolution)"; + + public function archiveDay() + { + $this->aggregateByConfiguration(); + $this->aggregateByOs(); + $this->aggregateByBrowser(); + $this->aggregateByResolutionAndScreenType(); + $this->aggregateByPlugin(); + $this->aggregateByLanguage(); + } + + protected function aggregateByConfiguration() + { + $metrics = $this->getProcessor()->getMetricsForDimension(self::CONFIGURATION_DIMENSION); + $table = $this->getProcessor()->getDataTableFromDataArray($metrics); + $this->insertTable(self::CONFIGURATION_RECORD_NAME, $table); + } + + protected function aggregateByOs() + { + $metrics = $this->getProcessor()->getMetricsForDimension(self::OS_DIMENSION); + $table = $this->getProcessor()->getDataTableFromDataArray($metrics); + $this->insertTable(self::OS_RECORD_NAME, $table); + } + + protected function aggregateByBrowser() + { + $tableBrowser = $this->aggregateByBrowserVersion(); + $this->aggregateByBrowserType($tableBrowser); + } + + protected function aggregateByBrowserVersion() + { + $metrics = $this->getProcessor()->getMetricsForDimension(self::BROWSER_VERSION_DIMENSION); + $tableBrowser = $this->getProcessor()->getDataTableFromDataArray($metrics); + $this->insertTable(self::BROWSER_RECORD_NAME, $tableBrowser); + return $tableBrowser; + } + + protected function aggregateByBrowserType(Piwik_DataTable $tableBrowser) + { + $tableBrowser->filter('GroupBy', array('label', 'Piwik_getBrowserFamily')); + $this->insertTable(self::BROWSER_TYPE_RECORD_NAME, $tableBrowser); + } + + protected function aggregateByResolutionAndScreenType() + { + $resolutions = $this->aggregateByResolution(); + $this->aggregateByScreenType($resolutions); + } + + protected function aggregateByResolution() + { + $metrics = $this->getProcessor()->getMetricsForDimension(self::RESOLUTION_DIMENSION); + $table = $this->getProcessor()->getDataTableFromDataArray($metrics); + $table->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_UserSettings_keepStrlenGreater')); + $this->insertTable(self::RESOLUTION_RECORD_NAME, $table); + return $table; + } + + protected function aggregateByScreenType(Piwik_DataTable $resolutions) + { + $resolutions->filter('GroupBy', array('label', 'Piwik_getScreenTypeFromResolution')); + $this->insertTable(self::SCREEN_TYPE_RECORD_NAME, $resolutions); + } + + protected function aggregateByPlugin() + { + $selects = array( + "sum(case log_visit.config_pdf when 1 then 1 else 0 end) as pdf", + "sum(case log_visit.config_flash when 1 then 1 else 0 end) as flash", + "sum(case log_visit.config_java when 1 then 1 else 0 end) as java", + "sum(case log_visit.config_director when 1 then 1 else 0 end) as director", + "sum(case log_visit.config_quicktime when 1 then 1 else 0 end) as quicktime", + "sum(case log_visit.config_realplayer when 1 then 1 else 0 end) as realplayer", + "sum(case log_visit.config_windowsmedia when 1 then 1 else 0 end) as windowsmedia", + "sum(case log_visit.config_gears when 1 then 1 else 0 end) as gears", + "sum(case log_visit.config_silverlight when 1 then 1 else 0 end) as silverlight", + "sum(case log_visit.config_cookie when 1 then 1 else 0 end) as cookie" + ); + + $query = $this->getLogAggregator()->queryVisitsByDimension(array(), false, $selects, $metrics = array()); + $data = $query->fetch(); + $cleanRow = Piwik_DataAccess_LogAggregator::makeArrayOneColumn($data, Piwik_Metrics::INDEX_NB_VISITS); + $table = Piwik_DataTable::makeFromIndexedArray($cleanRow); + $this->insertTable(self::PLUGIN_RECORD_NAME, $table); + } + + protected function aggregateByLanguage() + { + $query = $this->getLogAggregator()->queryVisitsByDimension( array("label" => self::LANGUAGE_DIMENSION) ); + $languageCodes = array_keys(Piwik_Common::getLanguagesList()); + $metricsByLanguage = new Piwik_DataArray(); + while ($row = $query->fetch()) { + $code = Piwik_Common::extractLanguageCodeFromBrowserLanguage($row['label'], $languageCodes); + $metricsByLanguage->sumMetricsVisits($code, $row); + } + + $tableLanguage = $this->getProcessor()->getDataTableFromDataArray($metricsByLanguage); + $this->insertTable(self::LANGUAGE_RECORD_NAME, $tableLanguage); + } + + protected function insertTable($recordName, Piwik_DataTable $table) + { + return $this->getProcessor()->insertBlobRecord($recordName, $table->getSerialized($this->maximumRows, null, Piwik_Metrics::INDEX_NB_VISITS)); + } + + public function archivePeriod() + { + $dataTableToSum = array( + self::CONFIGURATION_RECORD_NAME, + self::OS_RECORD_NAME, + self::BROWSER_RECORD_NAME, + self::BROWSER_TYPE_RECORD_NAME, + self::RESOLUTION_RECORD_NAME, + self::SCREEN_TYPE_RECORD_NAME, + self::PLUGIN_RECORD_NAME, + self::LANGUAGE_RECORD_NAME, + ); + $this->getProcessor()->aggregateDataTableReports($dataTableToSum, $this->maximumRows); + } +} + diff --git a/plugins/UserSettings/UserSettings.php b/plugins/UserSettings/UserSettings.php index 90be882806..19b5252360 100644 --- a/plugins/UserSettings/UserSettings.php +++ b/plugins/UserSettings/UserSettings.php @@ -232,7 +232,7 @@ class Piwik_UserSettings extends Piwik_Plugin if (empty($segment)) continue; $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => $columnName, 'segment' => $segment, 'acceptedValues' => $acceptedValues, @@ -269,70 +269,18 @@ class Piwik_UserSettings extends Piwik_Plugin * are superset of an existing report (eg. Browser family is built from the Browser report) * * @param Piwik_Event_Notification $notification notification object - * @return void */ function archiveDay($notification) { - require_once PIWIK_INCLUDE_PATH . '/plugins/UserSettings/functions.php'; - $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard']; - $columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS; - - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->archiveProcessing = $archiveProcessing; - - $recordName = 'UserSettings_configuration'; - $labelSQL = "CONCAT(log_visit.config_os, ';', log_visit.config_browser_name, ';', log_visit.config_resolution)"; - $interestByConfiguration = $archiveProcessing->getArrayInterestForLabel($labelSQL); - - $tableConfiguration = $archiveProcessing->getDataTableFromArray($interestByConfiguration); - $archiveProcessing->insertBlobRecord($recordName, $tableConfiguration->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation)); - destroy($tableConfiguration); - - $recordName = 'UserSettings_os'; - $labelSQL = "log_visit.config_os"; - $interestByOs = $archiveProcessing->getArrayInterestForLabel($labelSQL); - $tableOs = $archiveProcessing->getDataTableFromArray($interestByOs); - $archiveProcessing->insertBlobRecord($recordName, $tableOs->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation)); - destroy($tableOs); + $archiveProcessor = $notification->getNotificationObject(); - $recordName = 'UserSettings_browser'; - $labelSQL = "CONCAT(log_visit.config_browser_name, ';', log_visit.config_browser_version)"; - $interestByBrowser = $archiveProcessing->getArrayInterestForLabel($labelSQL); - $tableBrowser = $archiveProcessing->getDataTableFromArray($interestByBrowser); - $archiveProcessing->insertBlobRecord($recordName, $tableBrowser->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation)); - - $recordName = 'UserSettings_browserType'; - $tableBrowserType = $this->getTableBrowserByType($tableBrowser); - $archiveProcessing->insertBlobRecord($recordName, $tableBrowserType->getSerialized()); - destroy($tableBrowser); - destroy($tableBrowserType); - - $recordName = 'UserSettings_resolution'; - $labelSQL = "log_visit.config_resolution"; - $interestByResolution = $archiveProcessing->getArrayInterestForLabel($labelSQL); - $tableResolution = $archiveProcessing->getDataTableFromArray($interestByResolution); - $tableResolution->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_UserSettings_keepStrlenGreater')); - $archiveProcessing->insertBlobRecord($recordName, $tableResolution->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation)); - - $recordName = 'UserSettings_wideScreen'; - $tableWideScreen = $this->getTableWideScreen($tableResolution); - $archiveProcessing->insertBlobRecord($recordName, $tableWideScreen->getSerialized()); - destroy($tableResolution); - destroy($tableWideScreen); - - $recordName = 'UserSettings_plugin'; - $tablePlugin = $this->getDataTablePlugin(); - $archiveProcessing->insertBlobRecord($recordName, $tablePlugin->getSerialized()); - destroy($tablePlugin); - - $recordName = 'UserSettings_language'; - $tableLanguage = $this->getDataTableLanguages(); - $archiveProcessing->insertBlobRecord($recordName, $tableLanguage->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation)); + $archiving = new Piwik_UserSettings_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); + } } + /** * Period archiving: simply sums up daily archives * @@ -341,121 +289,11 @@ class Piwik_UserSettings extends Piwik_Plugin */ function archivePeriod($notification) { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard']; - - $dataTableToSum = array( - 'UserSettings_configuration', - 'UserSettings_os', - 'UserSettings_browser', - 'UserSettings_browserType', - 'UserSettings_resolution', - 'UserSettings_wideScreen', - 'UserSettings_plugin', - 'UserSettings_language', - ); - - $archiveProcessing->archiveDataTable($dataTableToSum, null, $maximumRowsInDataTable); - } + $archiveProcessor = $notification->getNotificationObject(); - /** - * Returns the report Visits by Screen type given the Resolution table - * - * @param Piwik_DataTable $tableResolution - * @return Piwik_DataTable - */ - protected function getTableWideScreen(Piwik_DataTable $tableResolution) - { - $nameToRow = array(); - foreach ($tableResolution->getRows() as $row) { - $resolution = $row->getColumn('label'); - $name = Piwik_getScreenTypeFromResolution($resolution); - if (!isset($nameToRow[$name])) { - $nameToRow[$name] = new Piwik_DataTable_Row(); - $nameToRow[$name]->addColumn('label', $name); - } - - $nameToRow[$name]->sumRow($row); - } - $tableWideScreen = new Piwik_DataTable(); - $tableWideScreen->addRowsFromArray($nameToRow); - - return $tableWideScreen; - } - - /** - * Returns the report Visits by Browser family given the Browser report - * - * @param Piwik_DataTable $tableBrowser - * @return Piwik_DataTable - */ - protected function getTableBrowserByType(Piwik_DataTable $tableBrowser) - { - $nameToRow = array(); - foreach ($tableBrowser->getRows() as $row) { - $browserLabel = $row->getColumn('label'); - $familyNameToUse = Piwik_getBrowserFamily($browserLabel); - if (!isset($nameToRow[$familyNameToUse])) { - $nameToRow[$familyNameToUse] = new Piwik_DataTable_Row(); - $nameToRow[$familyNameToUse]->addColumn('label', $familyNameToUse); - } - $nameToRow[$familyNameToUse]->sumRow($row); - } - - $tableBrowserType = new Piwik_DataTable(); - $tableBrowserType->addRowsFromArray($nameToRow); - return $tableBrowserType; - } - - /** - * Returns SQL that processes stats for Plugins - * - * @return Piwik_DataTable_Simple - */ - protected function getDataTablePlugin() - { - $toSelect = "sum(case log_visit.config_pdf when 1 then 1 else 0 end) as pdf, - sum(case log_visit.config_flash when 1 then 1 else 0 end) as flash, - sum(case log_visit.config_java when 1 then 1 else 0 end) as java, - sum(case log_visit.config_director when 1 then 1 else 0 end) as director, - sum(case log_visit.config_quicktime when 1 then 1 else 0 end) as quicktime, - sum(case log_visit.config_realplayer when 1 then 1 else 0 end) as realplayer, - sum(case log_visit.config_windowsmedia when 1 then 1 else 0 end) as windowsmedia, - sum(case log_visit.config_gears when 1 then 1 else 0 end) as gears, - sum(case log_visit.config_silverlight when 1 then 1 else 0 end) as silverlight, - sum(case log_visit.config_cookie when 1 then 1 else 0 end) as cookie "; - return $this->archiveProcessing->getSimpleDataTableFromSelect($toSelect, Piwik_Archive::INDEX_NB_VISITS); - } - - protected function getDataTableLanguages() - { - $labelSQL = "log_visit.location_browser_lang"; - $interestByLanguage = $this->archiveProcessing->getArrayInterestForLabel($labelSQL); - - $languageCodes = array_keys(Piwik_Common::getLanguagesList()); - - foreach ($interestByLanguage as $lang => $count) { - // get clean language code - $code = Piwik_Common::extractLanguageCodeFromBrowserLanguage($lang, $languageCodes); - if ($code != $lang) { - if (!array_key_exists($code, $interestByLanguage)) { - $interestByLanguage[$code] = array(); - } - // Add the values to the primary language - foreach ($count as $key => $value) { - if (array_key_exists($key, $interestByLanguage[$code])) { - $interestByLanguage[$code][$key] += $value; - } else { - $interestByLanguage[$code][$key] = $value; - } - } - unset($interestByLanguage[$lang]); - } + $archiving = new Piwik_UserSettings_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); } - $tableLanguage = $this->archiveProcessing->getDataTableFromArray($interestByLanguage); - return $tableLanguage; } } diff --git a/plugins/UserSettings/functions.php b/plugins/UserSettings/functions.php index e1c5579608..80a63a589e 100644 --- a/plugins/UserSettings/functions.php +++ b/plugins/UserSettings/functions.php @@ -153,6 +153,16 @@ function Piwik_getBrowserVersion($str) return substr($str, strpos($str, ';') + 1); } +function Piwik_getLogoImageFromId($dir, $id) +{ + $path = $dir.'/'.$id.'.gif'; + if (file_exists(PIWIK_INCLUDE_PATH . '/' . $path)) { + return $path; + } else { + return $dir.'/UNK.gif'; + } +} + function Piwik_getBrowsersLogo($label) { $id = Piwik_getBrowserId($label); @@ -160,7 +170,7 @@ function Piwik_getBrowsersLogo($label) if (empty($id)) { $id = 'UNK'; } - return 'plugins/UserSettings/images/browsers/' . $id . '.gif'; + return Piwik_getLogoImageFromId('plugins/UserSettings/images/browsers', $id); } function Piwik_getOSLogo($label) @@ -169,8 +179,7 @@ function Piwik_getOSLogo($label) if (empty($label)) { $label = 'UNK'; } - $path = 'plugins/UserSettings/images/os/' . $label . '.gif'; - return $path; + return Piwik_getLogoImageFromId('plugins/UserSettings/images/os', $label); } function Piwik_getScreensLogo($label) diff --git a/plugins/UserSettings/images/browsers/CM.gif b/plugins/UserSettings/images/browsers/CM.gif Binary files differnew file mode 100644 index 0000000000..793e0fad3b --- /dev/null +++ b/plugins/UserSettings/images/browsers/CM.gif diff --git a/plugins/UserSettings/images/browsers/NB.gif b/plugins/UserSettings/images/browsers/NB.gif Binary files differnew file mode 100644 index 0000000000..3d87e9f4bc --- /dev/null +++ b/plugins/UserSettings/images/browsers/NB.gif diff --git a/plugins/UserSettings/images/browsers/UN.gif b/plugins/UserSettings/images/browsers/UN.gif Binary files differnew file mode 100644 index 0000000000..2c44083422 --- /dev/null +++ b/plugins/UserSettings/images/browsers/UN.gif diff --git a/plugins/UserSettings/images/os/COS.gif b/plugins/UserSettings/images/os/COS.gif Binary files differnew file mode 100644 index 0000000000..793e0fad3b --- /dev/null +++ b/plugins/UserSettings/images/os/COS.gif diff --git a/plugins/UserSettings/images/os/GTV.gif b/plugins/UserSettings/images/os/GTV.gif Binary files differnew file mode 100644 index 0000000000..bdc4aefd88 --- /dev/null +++ b/plugins/UserSettings/images/os/GTV.gif diff --git a/plugins/UserSettings/images/os/IOS.gif b/plugins/UserSettings/images/os/IOS.gif Binary files differnew file mode 100644 index 0000000000..a3b970ae1b --- /dev/null +++ b/plugins/UserSettings/images/os/IOS.gif diff --git a/plugins/UserSettings/images/os/WIN.gif b/plugins/UserSettings/images/os/WIN.gif Binary files differnew file mode 100644 index 0000000000..486f78064a --- /dev/null +++ b/plugins/UserSettings/images/os/WIN.gif diff --git a/plugins/UsersManager/javascripts/usersManager.js b/plugins/UsersManager/javascripts/usersManager.js index 0db663d7c2..c24cbf73e6 100644 --- a/plugins/UsersManager/javascripts/usersManager.js +++ b/plugins/UsersManager/javascripts/usersManager.js @@ -196,7 +196,7 @@ $(document).ready(function () { piwikHelper.hideAjaxError(); var idRow = $(this).attr('id'); var loginToDelete = $(this).parent().parent().find('#userLogin').html(); - $('#confirmUserRemove h2').text(sprintf(_pk_translate('UsersManager_DeleteConfirm_js'), '"' + loginToDelete + '"')); + $('#confirmUserRemove').find('h2').text(sprintf(_pk_translate('UsersManager_DeleteConfirm_js'), '"' + loginToDelete + '"')); piwikHelper.modalConfirm('#confirmUserRemove', {yes: function () { sendDeleteUserAJAX(loginToDelete); }}); } ); diff --git a/plugins/UsersManager/javascripts/usersSettings.js b/plugins/UsersManager/javascripts/usersSettings.js index 0bfdf3d8bb..5708ca7a72 100644 --- a/plugins/UsersManager/javascripts/usersSettings.js +++ b/plugins/UsersManager/javascripts/usersSettings.js @@ -23,7 +23,7 @@ function sendUserSettingsAJAX() { var passwordBis = $('#passwordBis').val(); var defaultReport = $('input[name=defaultReport]:checked').val(); if (defaultReport == 1) { - defaultReport = $('#defaultReportSiteSelector .custom_select_main_link').attr('siteid'); + defaultReport = $('#defaultReportSiteSelector').find('.custom_select_main_link').attr('siteid'); } var postParams = {}; postParams.alias = alias; @@ -52,7 +52,7 @@ function sendUserSettingsAJAX() { function sendAnonymousUserSettingsAJAX() { var anonymousDefaultReport = $('input[name=anonymousDefaultReport]:checked').val(); if (anonymousDefaultReport == 1) { - anonymousDefaultReport = $('#anonymousDefaultReportWebsite option:selected').val(); + anonymousDefaultReport = $('#anonymousDefaultReportWebsite').find('option:selected').val(); } var anonymousDefaultDate = $('input[name=anonymousDefaultDate]:checked').val(); @@ -81,7 +81,7 @@ $(document).ready(function () { } }); - $('#userSettingsTable input').keypress(function (e) { + $('#userSettingsTable').find('input').keypress(function (e) { var key = e.keyCode || e.which; if (key == 13) { $('#userSettingsSubmit').click(); diff --git a/plugins/VisitFrequency/API.php b/plugins/VisitFrequency/API.php index d712b67831..986489f8eb 100644 --- a/plugins/VisitFrequency/API.php +++ b/plugins/VisitFrequency/API.php @@ -15,8 +15,10 @@ */ class Piwik_VisitFrequency_API { - static private $instance = null; + const RETURNING_VISITOR_SEGMENT = "visitorType==returning"; + const COLUMN_SUFFIX = "_returning"; + static private $instance = null; static public function getInstance() { if (self::$instance == null) { @@ -27,113 +29,48 @@ class Piwik_VisitFrequency_API public function get($idSite, $period, $date, $segment = false, $columns = false) { - Piwik::checkUserHasViewAccess($idSite); - $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - - // array values are comma separated - $columns = Piwik::getArrayFromApiParameter($columns); - $tempColumns = array(); - - $bounceRateReturningRequested = $averageVisitDurationReturningRequested = $actionsPerVisitReturningRequested = false; - if (!empty($columns)) { - // make sure base metrics are there for processed metrics - if (false !== ($bounceRateReturningRequested = array_search('bounce_rate_returning', $columns))) { - if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning'; - if (!in_array('bounce_count_returning', $columns)) $tempColumns[] = 'bounce_count_returning'; - unset($columns[$bounceRateReturningRequested]); - } - if (false !== ($actionsPerVisitReturningRequested = array_search('nb_actions_per_visit_returning', $columns))) { - if (!in_array('nb_actions_returning', $columns)) $tempColumns[] = 'nb_actions_returning'; - if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning'; - unset($columns[$actionsPerVisitReturningRequested]); - } - if (false !== ($averageVisitDurationReturningRequested = array_search('avg_time_on_site_returning', $columns))) { - if (!in_array('sum_visit_length_returning', $columns)) $tempColumns[] = 'sum_visit_length_returning'; - if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning'; - unset($columns[$averageVisitDurationReturningRequested]); - } - - $tempColumns = array_unique($tempColumns); - $columns = array_merge($columns, $tempColumns); - } else { - $bounceRateReturningRequested = $averageVisitDurationReturningRequested = $actionsPerVisitReturningRequested = true; - $columns = array( - 'nb_visits_returning', - 'nb_actions_returning', - 'max_actions_returning', - 'sum_visit_length_returning', - 'bounce_count_returning', - 'nb_visits_converted_returning', - ); - - if ($period == 'day') { - $columns = array_merge(array('nb_uniq_visitors_returning'), $columns); - } - } - $dataTable = $archive->getDataTableFromNumeric($columns); - - // Process ratio metrics - if ($bounceRateReturningRequested !== false) { - $dataTable->filter('ColumnCallbackAddColumnPercentage', array('bounce_rate_returning', 'bounce_count_returning', 'nb_visits_returning', 0)); - } - if ($actionsPerVisitReturningRequested !== false) { - $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_actions_per_visit_returning', 'nb_actions_returning', 'nb_visits_returning', 1)); - } - if ($averageVisitDurationReturningRequested !== false) { - $dataTable->filter('ColumnCallbackAddColumnQuotient', array('avg_time_on_site_returning', 'sum_visit_length_returning', 'nb_visits_returning', 0)); - } - - // remove temporary metrics that were used to compute processed metrics - $dataTable->deleteColumns($tempColumns); - - return $dataTable; + $segment = $this->appendReturningVisitorSegment($segment); + + $this->unprefixColumns($columns); + $params = array( + 'idSite' => $idSite, + 'period' => $period, + 'date' => $date, + 'segment' => $segment, + 'columns' => implode(',', $columns), + 'format' => 'original', + 'serialize' => 0 // tests set this to 1 + ); + $table = Piwik_API_Request::processRequest('VisitsSummary.get', $params); + $this->prefixColumns($table, $period); + return $table; } - protected function getNumeric($idSite, $period, $date, $toFetch) + protected function appendReturningVisitorSegment($segment) { - Piwik::checkUserHasViewAccess($idSite); - $archive = Piwik_Archive::build($idSite, $period, $date); - $dataTable = $archive->getNumeric($toFetch); - return $dataTable; - } - - /** - * @ignore - */ - public function getVisitsReturning($idSite, $period, $date) - { - return $this->getNumeric($idSite, $period, $date, 'nb_visits_returning'); - } - - /** - * @ignore - */ - public function getActionsReturning($idSite, $period, $date) - { - return $this->getNumeric($idSite, $period, $date, 'nb_actions_returning'); - } - - /** - * @ignore - */ - public function getSumVisitsLengthReturning($idSite, $period, $date) - { - return $this->getNumeric($idSite, $period, $date, 'sum_visit_length_returning'); + if (empty($segment)) { + $segment = ''; + } else { + $segment .= Piwik_SegmentExpression::AND_DELIMITER; + } + $segment .= self::RETURNING_VISITOR_SEGMENT; + return $segment; } - /** - * @ignore - */ - public function getBounceCountReturning($idSite, $period, $date) + protected function unprefixColumns(&$columns) { - return $this->getNumeric($idSite, $period, $date, 'bounce_count_returning'); + $columns = Piwik::getArrayFromApiParameter($columns); + foreach ($columns as &$column) { + $column = str_replace(self::COLUMN_SUFFIX, "", $column); + } } - /** - * @ignore - */ - public function getConvertedVisitsReturning($idSite, $period, $date) + protected function prefixColumns($table, $period) { - return $this->getNumeric($idSite, $period, $date, 'nb_visits_converted_returning'); + $rename = array(); + foreach (Piwik_VisitsSummary_API::getInstance()->getColumns($period) as $oldColumn) { + $rename[$oldColumn] = $oldColumn . self::COLUMN_SUFFIX; + } + $table->filter('ReplaceColumnNames', array($rename)); } } diff --git a/plugins/VisitFrequency/VisitFrequency.php b/plugins/VisitFrequency/VisitFrequency.php index 4659feee91..717e3f8eae 100644 --- a/plugins/VisitFrequency/VisitFrequency.php +++ b/plugins/VisitFrequency/VisitFrequency.php @@ -29,8 +29,6 @@ class Piwik_VisitFrequency extends Piwik_Plugin function getListHooksRegistered() { $hooks = array( - 'ArchiveProcessing_Day.compute' => 'archiveDay', - 'ArchiveProcessing_Period.compute' => 'archivePeriod', 'WidgetsList.add' => 'addWidgets', 'Menu.add' => 'addMenu', 'API.getReportMetadata' => 'getReportMetadata', @@ -77,73 +75,5 @@ class Piwik_VisitFrequency extends Piwik_Plugin { Piwik_AddMenu('General_Visitors', 'VisitFrequency_SubmenuFrequency', array('module' => 'VisitFrequency', 'action' => 'index')); } - - /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ - function archivePeriod($notification) - { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $numericToSum = array( - 'nb_visits_returning', - 'nb_actions_returning', - 'sum_visit_length_returning', - 'bounce_count_returning', - 'nb_visits_converted_returning', - ); - $archiveProcessing->archiveNumericValuesSum($numericToSum); - $archiveProcessing->archiveNumericValuesMax('max_actions_returning'); - } - - /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ - function archiveDay($notification) - { - /* @var $archiveProcessing Piwik_ArchiveProcessing */ - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $select = "count(distinct log_visit.idvisitor) as nb_uniq_visitors_returning, - count(*) as nb_visits_returning, - sum(log_visit.visit_total_actions) as nb_actions_returning, - max(log_visit.visit_total_actions) as max_actions_returning, - sum(log_visit.visit_total_time) as sum_visit_length_returning, - sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as bounce_count_returning, - sum(case log_visit.visit_goal_converted when 1 then 1 else 0 end) as nb_visits_converted_returning"; - - $from = "log_visit"; - - $where = "log_visit.visit_last_action_time >= ? - AND log_visit.visit_last_action_time <= ? - AND log_visit.idsite = ? - AND log_visit.visitor_returning >= 1"; - - $bind = array($archiveProcessing->getStartDatetimeUTC(), - $archiveProcessing->getEndDatetimeUTC(), $archiveProcessing->idsite); - - $query = $archiveProcessing->getSegment()->getSelectQuery($select, $from, $where, $bind); - - $row = $archiveProcessing->db->fetchRow($query['sql'], $query['bind']); - - if ($row === false || $row === null) { - $row['nb_visits_returning'] = 0; - $row['nb_actions_returning'] = 0; - $row['max_actions_returning'] = 0; - $row['sum_visit_length_returning'] = 0; - $row['bounce_count_returning'] = 0; - $row['nb_visits_converted_returning'] = 0; - } - - foreach ($row as $name => $value) { - $archiveProcessing->insertNumericRecord($name, $value); - } - } } diff --git a/plugins/VisitTime/API.php b/plugins/VisitTime/API.php index 3da0646fb7..2f4aaee06b 100644 --- a/plugins/VisitTime/API.php +++ b/plugins/VisitTime/API.php @@ -39,12 +39,12 @@ class Piwik_VisitTime_API public function getVisitInformationPerLocalTime($idSite, $period, $date, $segment = false) { - return $this->getDataTable('VisitTime_localTime', $idSite, $period, $date, $segment); + return $this->getDataTable(Piwik_VisitTime_Archiver::LOCAL_TIME_RECORD_NAME, $idSite, $period, $date, $segment); } public function getVisitInformationPerServerTime($idSite, $period, $date, $segment = false, $hideFutureHoursWhenToday = false) { - $table = $this->getDataTable('VisitTime_serverTime', $idSite, $period, $date, $segment); + $table = $this->getDataTable(Piwik_VisitTime_Archiver::SERVER_TIME_RECORD_NAME, $idSite, $period, $date, $segment); if ($hideFutureHoursWhenToday) { $table = $this->removeHoursInFuture($table, $idSite, $period, $date); } @@ -62,26 +62,28 @@ class Piwik_VisitTime_API */ public function getByDayOfWeek($idSite, $period, $date, $segment = false) { - Piwik::checkUserHasViewAccess($idSite); - // disabled for multiple sites/dates - if (Piwik_Archive::isMultipleSites($idSite)) { - throw new Exception("VisitTime.getByDayOfWeek does not support multiple sites."); - } + Piwik::checkUserHasViewAccess($idSite); - if (Piwik_Archive::isMultiplePeriod($date, $period)) { + // metrics to query + $metrics = Piwik_Metrics::getVisitsMetricNames(); + unset($metrics[Piwik_Metrics::INDEX_MAX_ACTIONS]); + + // disabled for multiple dates + if (Piwik_Period::isMultiplePeriod($date, $period)) { throw new Exception("VisitTime.getByDayOfWeek does not support multiple dates."); } - // metrics to query - $metrics = Piwik_ArchiveProcessing::getCoreMetrics(); - // get metric data for every day within the supplied period - $oSite = new Piwik_Site($idSite); - $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $date); + $oPeriod = Piwik_Period::makePeriodFromQueryParams(Piwik_Site::getTimezoneFor($idSite), $period, $date); $dateRange = $oPeriod->getDateStart()->toString() . ',' . $oPeriod->getDateEnd()->toString(); - $archive = Piwik_Archive::build($idSite, 'day', $dateRange, $segment); + + // disabled for multiple sites + if (count($archive->getParams()->getIdSites()) > 1) { + throw new Exception("VisitTime.getByDayOfWeek does not support multiple sites."); + } + $dataTable = $archive->getDataTableFromNumeric($metrics)->mergeChildren(); // if there's no data for this report, don't bother w/ anything else @@ -97,7 +99,6 @@ class Piwik_VisitTime_API foreach (array(1, 2, 3, 4, 5, 6, 7) as $day) { $rows[] = array('label' => $day, 'nb_visits' => 0); } - $result = new Piwik_DataTable(); $result->addRowsFromSimpleArray($rows); $result->addDataTable($dataTable); diff --git a/plugins/VisitTime/Archiver.php b/plugins/VisitTime/Archiver.php new file mode 100644 index 0000000000..b695ba4558 --- /dev/null +++ b/plugins/VisitTime/Archiver.php @@ -0,0 +1,80 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_VisitTime + */ + +class Piwik_VisitTime_Archiver extends Piwik_PluginsArchiver +{ + const SERVER_TIME_RECORD_NAME = 'VisitTime_serverTime'; + const LOCAL_TIME_RECORD_NAME = 'VisitTime_localTime'; + + public function archiveDay() + { + $this->aggregateByLocalTime(); + $this->aggregateByServerTime(); + } + + protected function aggregateByServerTime() + { + $array = $this->getProcessor()->getMetricsForDimension( array("label" => "HOUR(log_visit.visit_last_action_time)" )) ; + $query = $this->getLogAggregator()->queryConversionsByDimension( array("label" => "HOUR(log_conversion.server_time)") ); + if ($query === false) { + return; + } + + while ($row = $query->fetch()) { + $array->sumMetricsGoals($row['label'], $row); + } + $array->enrichMetricsWithConversions(); + $array = $this->convertTimeToLocalTimezone($array); + $this->ensureAllHoursAreSet($array); + $this->getProcessor()->insertBlobRecord(self::SERVER_TIME_RECORD_NAME, $this->getProcessor()->getDataTableFromDataArray($array)->getSerialized()); + } + + protected function aggregateByLocalTime() + { + $array = $this->getProcessor()->getMetricsForDimension("HOUR(log_visit.visitor_localtime)"); + $this->ensureAllHoursAreSet($array); + $this->getProcessor()->insertBlobRecord(self::LOCAL_TIME_RECORD_NAME, $this->getProcessor()->getDataTableFromDataArray($array)->getSerialized()); + } + + protected function convertTimeToLocalTimezone(Piwik_DataArray &$array) + { + $date = Piwik_Date::factory($this->getProcessor()->getDateStart()->getDateStartUTC())->toString(); + $timezone = $this->getProcessor()->getSite()->getTimezone(); + + $converted = array(); + foreach ($array->getDataArray() as $hour => $stats) { + $datetime = $date . ' ' . $hour . ':00:00'; + $hourInTz = (int)Piwik_Date::factory($datetime, $timezone)->toString('H'); + $converted[$hourInTz] = $stats; + } + return new Piwik_DataArray($converted); + } + + + private function ensureAllHoursAreSet( Piwik_DataArray &$array) + { + $data = $array->getDataArray(); + for ($i = 0; $i <= 23; $i++) { + if (empty($data[$i])) { + $array->sumMetricsVisits( $i, Piwik_DataArray::makeEmptyRow()); + } + } + } + + public function archivePeriod() + { + $dataTableToSum = array( + self::LOCAL_TIME_RECORD_NAME, + self::SERVER_TIME_RECORD_NAME, + ); + $this->getProcessor()->aggregateDataTableReports($dataTableToSum); + } +}
\ No newline at end of file diff --git a/plugins/VisitTime/Controller.php b/plugins/VisitTime/Controller.php index 76e758270f..769c102761 100644 --- a/plugins/VisitTime/Controller.php +++ b/plugins/VisitTime/Controller.php @@ -58,15 +58,22 @@ class Piwik_VisitTime_Controller extends Piwik_Controller if ($view instanceof Piwik_ViewDataTable_GenerateGraphHTML) { $view->showAllTicks(); } + $dateRange = $this->getRangeDate(); + $view->setFooterMessage(Piwik_Translate('General_ReportGeneratedFrom', $dateRange)); + + return $this->renderView($view, $fetch); + } + + protected function getRangeDate() + { // get query params - $idsite = Piwik_Common::getRequestVar('idSite'); + $idSite = Piwik_Common::getRequestVar('idSite'); $date = Piwik_Common::getRequestVar('date'); $period = Piwik_Common::getRequestVar('period'); // create a period instance - $oSite = new Piwik_Site($idsite); - $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $date); + $oPeriod = Piwik_Period::makePeriodFromQueryParams(Piwik_Site::getTimezoneFor($idSite), $period, $date); // set the footer message using the period start & end date $start = $oPeriod->getDateStart()->toString(); @@ -76,10 +83,7 @@ class Piwik_VisitTime_Controller extends Piwik_Controller } else { $dateRange = $start . " – " . $end; } - - $view->setFooterMessage(Piwik_Translate('General_ReportGeneratedFrom', $dateRange)); - - return $this->renderView($view, $fetch); + return $dateRange; } private function getGraph($controllerMethod, $apiMethod, $labelTranslation, $limit = 24) diff --git a/plugins/VisitTime/VisitTime.php b/plugins/VisitTime/VisitTime.php index 5b05342bed..b74a48f59e 100644 --- a/plugins/VisitTime/VisitTime.php +++ b/plugins/VisitTime/VisitTime.php @@ -114,7 +114,7 @@ class Piwik_VisitTime extends Piwik_Plugin $acceptedValues = "0, 1, 2, 3, ..., 20, 21, 22, 23"; $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => Piwik_Translate('VisitTime_ColumnServerTime'), 'segment' => 'visitServerHour', 'sqlSegment' => 'HOUR(log_visit.visit_last_action_time)', @@ -122,7 +122,7 @@ class Piwik_VisitTime extends Piwik_Plugin ); $segments[] = array( 'type' => 'dimension', - 'category' => 'Visit', + 'category' => Piwik_Translate('General_Visit'), 'name' => Piwik_Translate('VisitTime_ColumnLocalTime'), 'segment' => 'visitLocalHour', 'sqlSegment' => 'HOUR(log_visit.visitor_localtime)', @@ -130,96 +130,25 @@ class Piwik_VisitTime extends Piwik_Plugin ); } - /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ function archivePeriod($notification) { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $dataTableToSum = array( - 'VisitTime_localTime', - 'VisitTime_serverTime', - ); - $archiveProcessing->archiveDataTable($dataTableToSum); - } - - /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ - public function archiveDay($notification) - { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->archiveDayAggregateVisits($archiveProcessing); - $this->archiveDayAggregateGoals($archiveProcessing); - $this->archiveDayRecordInDatabase($archiveProcessing); - } - - protected function archiveDayAggregateVisits($archiveProcessing) - { - $labelSQL = "HOUR(log_visit.visitor_localtime)"; - $this->interestByLocalTime = $archiveProcessing->getArrayInterestForLabel($labelSQL); - - $labelSQL = "HOUR(log_visit.visit_last_action_time)"; - $this->interestByServerTime = $archiveProcessing->getArrayInterestForLabel($labelSQL); - } - - protected function convertServerTimeToLocalTimezone($interestByServerTime, $archiveProcessing) - { - $date = Piwik_Date::factory($archiveProcessing->getStartDatetimeUTC())->toString(); - $timezone = $archiveProcessing->site->getTimezone(); - $visitsByHourTz = array(); - foreach ($interestByServerTime as $hour => $stats) { - $datetime = $date . ' ' . $hour . ':00:00'; - $hourInTz = (int)Piwik_Date::factory($datetime, $timezone)->toString('H'); - $visitsByHourTz[$hourInTz] = $stats; - } - return $visitsByHourTz; - } - - protected function archiveDayAggregateGoals($archiveProcessing) - { - $query = $archiveProcessing->queryConversionsByDimension("HOUR(log_conversion.server_time)"); + $archiveProcessor = $notification->getNotificationObject(); - if ($query === false) return; - - while ($row = $query->fetch()) { - if (!isset($this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']); - $archiveProcessing->updateGoalStats($row, $this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]); + $archiving = new Piwik_VisitTime_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); } - $goalByServerTime = $this->convertServerTimeToLocalTimezone($this->interestByServerTime, $archiveProcessing); - $archiveProcessing->enrichConversionsByLabelArray($this->interestByServerTime); } - protected function archiveDayRecordInDatabase($archiveProcessing) - { - $tableLocalTime = $archiveProcessing->getDataTableFromArray($this->interestByLocalTime); - $this->makeSureAllHoursAreSet($tableLocalTime, $archiveProcessing); - $archiveProcessing->insertBlobRecord('VisitTime_localTime', $tableLocalTime->getSerialized()); - destroy($tableLocalTime); - - $this->interestByServerTime = $this->convertServerTimeToLocalTimezone($this->interestByServerTime, $archiveProcessing); - $tableServerTime = $archiveProcessing->getDataTableFromArray($this->interestByServerTime); - $this->makeSureAllHoursAreSet($tableServerTime, $archiveProcessing); - $archiveProcessing->insertBlobRecord('VisitTime_serverTime', $tableServerTime->getSerialized()); - destroy($tableServerTime); - } - private function makeSureAllHoursAreSet($table, $archiveProcessing) + public function archiveDay($notification) { - for ($i = 0; $i <= 23; $i++) { - if ($table->getRowFromLabel($i) === false) { - $row = $archiveProcessing->getNewInterestRowLabeled($i); - $table->addRow($row); - } + $archiveProcessor = $notification->getNotificationObject(); + $archiving = new Piwik_VisitTime_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); } } + } diff --git a/plugins/VisitorGenerator/Controller.php b/plugins/VisitorGenerator/Controller.php index 62825bdbd1..e93e99dd25 100644 --- a/plugins/VisitorGenerator/Controller.php +++ b/plugins/VisitorGenerator/Controller.php @@ -76,7 +76,7 @@ class Piwik_VisitorGenerator_Controller extends Piwik_Controller_Admin $api = Piwik_CoreAdminHome_API::getInstance(); $api->invalidateArchivedReports($idSite, implode($dates, ",")); - $browserArchiving = Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled(); + $browserArchiving = Piwik_ArchiveProcessor_Rules::isBrowserTriggerEnabled(); // Init view $view = new Piwik_View('@VisitorGenerator/generate'); diff --git a/plugins/VisitorInterest/API.php b/plugins/VisitorInterest/API.php index 7adfd7ca4e..deaec5a68e 100644 --- a/plugins/VisitorInterest/API.php +++ b/plugins/VisitorInterest/API.php @@ -27,7 +27,7 @@ class Piwik_VisitorInterest_API return self::$instance; } - protected function getDataTable($name, $idSite, $period, $date, $segment, $column = Piwik_Archive::INDEX_NB_VISITS) + protected function getDataTable($name, $idSite, $period, $date, $segment, $column = Piwik_Metrics::INDEX_NB_VISITS) { Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); @@ -38,7 +38,7 @@ class Piwik_VisitorInterest_API public function getNumberOfVisitsPerVisitDuration($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('VisitorInterest_timeGap', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_VisitorInterest_Archiver::TIME_SPENT_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->queueFilter('Sort', array('label', 'asc', true)); $dataTable->queueFilter('BeautifyTimeRangeLabels', array( Piwik_Translate('VisitorInterest_BetweenXYSeconds'), @@ -49,7 +49,7 @@ class Piwik_VisitorInterest_API public function getNumberOfVisitsPerPage($idSite, $period, $date, $segment = false) { - $dataTable = $this->getDataTable('VisitorInterest_pageGap', $idSite, $period, $date, $segment); + $dataTable = $this->getDataTable(Piwik_VisitorInterest_Archiver::PAGES_VIEWED_RECORD_NAME, $idSite, $period, $date, $segment); $dataTable->queueFilter('Sort', array('label', 'asc', true)); $dataTable->queueFilter('BeautifyRangeLabels', array( Piwik_Translate('VisitorInterest_OnePage'), @@ -69,9 +69,8 @@ class Piwik_VisitorInterest_API */ public function getNumberOfVisitsByDaysSinceLast($idSite, $period, $date, $segment = false) { - $recordName = 'VisitorInterest_daysSinceLastVisit'; $dataTable = $this->getDataTable( - $recordName, $idSite, $period, $date, $segment, Piwik_Archive::INDEX_NB_VISITS); + Piwik_VisitorInterest_Archiver::DAYS_SINCE_LAST_RECORD_NAME, $idSite, $period, $date, $segment, Piwik_Metrics::INDEX_NB_VISITS); $dataTable->queueFilter('BeautifyRangeLabels', array( Piwik_Translate('General_OneDay'), Piwik_Translate('General_NDays'))); @@ -92,7 +91,7 @@ class Piwik_VisitorInterest_API public function getNumberOfVisitsByVisitCount($idSite, $period, $date, $segment = false) { $dataTable = $this->getDataTable( - 'VisitorInterest_visitsByVisitCount', $idSite, $period, $date, $segment, Piwik_Archive::INDEX_NB_VISITS); + Piwik_VisitorInterest_Archiver::VISITS_COUNT_RECORD_NAME, $idSite, $period, $date, $segment, Piwik_Metrics::INDEX_NB_VISITS); $dataTable->queueFilter('BeautifyRangeLabels', array( Piwik_Translate('General_OneVisit'), Piwik_Translate('General_NVisits'))); @@ -117,7 +116,7 @@ class Piwik_VisitorInterest_API self::addVisitsPercentColumn($table); } } else { - $totalVisits = array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS)); + $totalVisits = array_sum($dataTable->getColumn(Piwik_Metrics::INDEX_NB_VISITS)); $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array( 'nb_visits_percentage', 'nb_visits', $totalVisits)); } diff --git a/plugins/VisitorInterest/Archiver.php b/plugins/VisitorInterest/Archiver.php new file mode 100644 index 0000000000..1720589a7f --- /dev/null +++ b/plugins/VisitorInterest/Archiver.php @@ -0,0 +1,146 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Piwik_VisitorInterest + */ + +class Piwik_VisitorInterest_Archiver extends Piwik_PluginsArchiver +{ + // third element is unit (s for seconds, default is munutes) + const TIME_SPENT_RECORD_NAME = 'VisitorInterest_timeGap'; + const PAGES_VIEWED_RECORD_NAME = 'VisitorInterest_pageGap'; + const VISITS_COUNT_RECORD_NAME = 'VisitorInterest_visitsByVisitCount'; + const DAYS_SINCE_LAST_RECORD_NAME = 'VisitorInterest_daysSinceLastVisit'; + + protected static $timeGap = array( + array(0, 10, 's'), + array(11, 30, 's'), + array(31, 60, 's'), + array(1, 2), + array(2, 4), + array(4, 7), + array(7, 10), + array(10, 15), + array(15, 30), + array(30) + ); + protected static $pageGap = array( + array(1, 1), + array(2, 2), + array(3, 3), + array(4, 4), + array(5, 5), + array(6, 7), + array(8, 10), + array(11, 14), + array(15, 20), + array(20) + ); + /** + * The set of ranges used when calculating the 'visitors who visited at least N times' report. + */ + protected static $visitNumberGap = array( + array(1, 1), + array(2, 2), + array(3, 3), + array(4, 4), + array(5, 5), + array(6, 6), + array(7, 7), + array(8, 8), + array(9, 14), + array(15, 25), + array(26, 50), + array(51, 100), + array(101, 200), + array(200) + ); + /** + * The set of ranges used when calculating the 'days since last visit' report. + */ + protected static $daysSinceLastVisitGap = array( + array(0, 0), + array(1, 1), + array(2, 2), + array(3, 3), + array(4, 4), + array(5, 5), + array(6, 6), + array(7, 7), + array(8, 14), + array(15, 30), + array(31, 60), + array(61, 120), + array(121, 364), + array(364) + ); + + public function archiveDay() + { + // these prefixes are prepended to the 'SELECT as' parts of each SELECT expression. detecting + // these prefixes allows us to get all the data in one query. + $prefixes = array( + self::TIME_SPENT_RECORD_NAME => 'tg', + self::PAGES_VIEWED_RECORD_NAME => 'pg', + self::VISITS_COUNT_RECORD_NAME => 'vbvn', + self::DAYS_SINCE_LAST_RECORD_NAME => 'dslv', + ); + + $aggregatesMetadata = array( + array('visit_total_time', self::getSecondsGap(), 'log_visit', $prefixes[self::TIME_SPENT_RECORD_NAME]), + array('visit_total_actions', self::$pageGap, 'log_visit', $prefixes[self::PAGES_VIEWED_RECORD_NAME]), + array('visitor_count_visits', self::$visitNumberGap, 'log_visit', $prefixes[self::VISITS_COUNT_RECORD_NAME]), + array('visitor_days_since_last', self::$daysSinceLastVisitGap, 'log_visit', $prefixes[self::DAYS_SINCE_LAST_RECORD_NAME], + $i_am_your_nightmare_DELETE_ME = true + ), + ); + $selects = array(); + foreach($aggregatesMetadata as $aggregateMetadata) { + $selectsFromRangedColumn = Piwik_DataAccess_LogAggregator::getSelectsFromRangedColumn($aggregateMetadata); + $selects = array_merge( $selects, $selectsFromRangedColumn); + } + $query = $this->getLogAggregator()->queryVisitsByDimension(array(), $where = false, $selects, array()); + $row = $query->fetch(); + foreach($prefixes as $recordName => $selectAsPrefix) { + $cleanRow = Piwik_DataAccess_LogAggregator::makeArrayOneColumn($row, Piwik_Metrics::INDEX_NB_VISITS, $selectAsPrefix); + $dataTable = Piwik_DataTable::makeFromIndexedArray($cleanRow); + $this->getProcessor()->insertBlobRecord($recordName, $dataTable->getSerialized()); + } + } + + /** + * Transforms and returns the set of ranges used to calculate the 'visits by total time' + * report from ranges in minutes to equivalent ranges in seconds. + */ + protected static function getSecondsGap() + { + $secondsGap = array(); + foreach (self::$timeGap as $gap) { + if (count($gap) == 3 && $gap[2] == 's') // if the units are already in seconds, just assign them + { + $secondsGap[] = array($gap[0], $gap[1]); + } else if (count($gap) == 2) { + $secondsGap[] = array($gap[0] * 60, $gap[1] * 60); + } else { + $secondsGap[] = array($gap[0] * 60); + } + } + return $secondsGap; + } + + public function archivePeriod() + { + $dataTableToSum = array( + self::TIME_SPENT_RECORD_NAME, + self::PAGES_VIEWED_RECORD_NAME, + self::VISITS_COUNT_RECORD_NAME, + self::DAYS_SINCE_LAST_RECORD_NAME + ); + $this->getProcessor()->aggregateDataTableReports($dataTableToSum); + } +}
\ No newline at end of file diff --git a/plugins/VisitorInterest/VisitorInterest.php b/plugins/VisitorInterest/VisitorInterest.php index 00689e5c2c..c2a46f1f9e 100644 --- a/plugins/VisitorInterest/VisitorInterest.php +++ b/plugins/VisitorInterest/VisitorInterest.php @@ -123,192 +123,26 @@ class Piwik_VisitorInterest extends Piwik_Plugin Piwik_AddAction('template_footerVisitsFrequency', array('Piwik_VisitorInterest', 'footerVisitsFrequency')); } - // third element is unit (s for seconds, default is munutes) - protected static $timeGap = array( - array(0, 10, 's'), - array(11, 30, 's'), - array(31, 60, 's'), - array(1, 2), - array(2, 4), - array(4, 7), - array(7, 10), - array(10, 15), - array(15, 30), - array(30) - ); - - protected static $pageGap = array( - array(1, 1), - array(2, 2), - array(3, 3), - array(4, 4), - array(5, 5), - array(6, 7), - array(8, 10), - array(11, 14), - array(15, 20), - array(20) - ); - - /** - * The set of ranges used when calculating the 'visitors who visited at least N times' report. - */ - protected static $visitNumberGap = array( - array(1, 1), - array(2, 2), - array(3, 3), - array(4, 4), - array(5, 5), - array(6, 6), - array(7, 7), - array(8, 8), - array(9, 14), - array(15, 25), - array(26, 50), - array(51, 100), - array(101, 200), - array(200) - ); - - /** - * The set of ranges used when calculating the 'days since last visit' report. - */ - protected static $daysSinceLastVisitGap = array( - array(0, 0), - array(1, 1), - array(2, 2), - array(3, 3), - array(4, 4), - array(5, 5), - array(6, 6), - array(7, 7), - array(8, 14), - array(15, 30), - array(31, 60), - array(61, 120), - array(121, 364), - array(364) - ); - - /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ function archivePeriod($notification) { - $archiveProcessing = $notification->getNotificationObject(); - - if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; + $archiveProcessor = $notification->getNotificationObject(); - $dataTableToSum = array( - 'VisitorInterest_timeGap', - 'VisitorInterest_pageGap', - 'VisitorInterest_visitsByVisitCount', - 'VisitorInterest_daysSinceLastVisit' - ); - $archiveProcessing->archiveDataTable($dataTableToSum); + $archiving = new Piwik_VisitorInterest_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archivePeriod(); + } } - /** - * @param Piwik_Event_Notification $notification notification object - * @return mixed - */ public function archiveDay($notification) { - $this->archiveProcessing = $notification->getNotificationObject(); - - if (!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - // these prefixes are prepended to the 'SELECT as' parts of each SELECT expression. detecting - // these prefixes allows us to get all the data in one query. - $timeGapPrefix = 'tg'; - $pageGapPrefix = 'pg'; - $visitsByVisitNumPrefix = 'vbvn'; - $daysSinceLastVisitPrefix = 'dslv'; + $archiveProcessor = $notification->getNotificationObject(); - // extra condition for the SQL SELECT that makes sure only returning visits are counted - // when creating the 'days since last visit' report. the SELECT expression below it - // is used to count all new visits. - $daysSinceLastExtraCondition = 'and log_visit.visitor_returning = 1'; - $selectAs = $daysSinceLastVisitPrefix . 'General_NewVisits'; - $newVisitCountSelect = "sum(case when log_visit.visitor_returning = 0 then 1 else 0 end) as `$selectAs`"; - - // create the select expressions to use - $timeGapSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect( - 'visit_total_time', self::getSecondsGap(), 'log_visit', $timeGapPrefix); - $pageGapSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect( - 'visit_total_actions', self::$pageGap, 'log_visit', $pageGapPrefix); - $visitsByVisitNumSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect( - 'visitor_count_visits', self::$visitNumberGap, 'log_visit', $visitsByVisitNumPrefix); - - $daysSinceLastVisitSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect( - 'visitor_days_since_last', self::$daysSinceLastVisitGap, 'log_visit', $daysSinceLastVisitPrefix, - $daysSinceLastExtraCondition); - array_unshift($daysSinceLastVisitSelects, $newVisitCountSelect); - - $selects = array_merge( - $timeGapSelects, $pageGapSelects, $visitsByVisitNumSelects, $daysSinceLastVisitSelects); - - // select data for every report - $row = $this->archiveProcessing->queryVisitsSimple(implode(',', $selects)); - - // archive visits by total time report - $recordName = 'VisitorInterest_timeGap'; - $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $timeGapPrefix); - - // archive visits by total actions report - $recordName = 'VisitorInterest_pageGap'; - $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $pageGapPrefix); - - // archive visits by visit number report - $recordName = 'VisitorInterest_visitsByVisitCount'; - $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $visitsByVisitNumPrefix); - - // archive days since last visit report - $recordName = 'VisitorInterest_daysSinceLastVisit'; - $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $daysSinceLastVisitPrefix); - } - - /** - * Transforms and returns the set of ranges used to calculate the 'visits by total time' - * report from ranges in minutes to equivalent ranges in seconds. - */ - protected static function getSecondsGap() - { - $secondsGap = array(); - foreach (self::$timeGap as $gap) { - if (count($gap) == 3 && $gap[2] == 's') // if the units are already in seconds, just assign them - { - $secondsGap[] = array($gap[0], $gap[1]); - } else if (count($gap) == 2) { - $secondsGap[] = array($gap[0] * 60, $gap[1] * 60); - } else { - $secondsGap[] = array($gap[0] * 60); - } + $archiving = new Piwik_VisitorInterest_Archiver($archiveProcessor); + if($archiving->shouldArchive()) { + $archiving->archiveDay(); } - return $secondsGap; } - /** - * Creates and archives a DataTable from some (or all) elements of a supplied database - * row. - * - * @param string $recordName The record name to use when inserting the new archive. - * @param array $row The database row to use. - * @param string $selectAsPrefix The string to look for as the prefix of SELECT as - * expressions. Elements in $row that have a SELECT as - * with this string as a prefix are used in creating - * the DataTable.' - */ - protected function archiveRangeStats($recordName, $row, $index, $selectAsPrefix) - { - // create the DataTable from parts of the result row - $dataTable = $this->archiveProcessing->getSimpleDataTableFromRow($row, $index, $selectAsPrefix); - - // insert the data table as a blob archive - $this->archiveProcessing->insertBlobRecord($recordName, $dataTable->getSerialized()); - destroy($dataTable); - } /** * @param Piwik_Event_Notification $notification notification object diff --git a/plugins/VisitsSummary/API.php b/plugins/VisitsSummary/API.php index 0864c517b5..3205edaf61 100644 --- a/plugins/VisitsSummary/API.php +++ b/plugins/VisitsSummary/API.php @@ -62,19 +62,7 @@ class Piwik_VisitsSummary_API $columns = array_merge($columns, $tempColumns); } else { $bounceRateRequested = $actionsPerVisitRequested = $averageVisitDurationRequested = true; - $columns = array( - 'nb_visits', - 'nb_actions', - 'nb_visits_converted', - 'bounce_count', - 'sum_visit_length', - 'max_actions' - ); - if (Piwik::isUniqueVisitorsEnabled($period)) { - $columns = array_merge(array('nb_uniq_visitors'), $columns); - } - // Force reindex from 0 to N otherwise the SQL bind will fail - $columns = array_values($columns); + $columns = $this->getCoreColumns($period); } $dataTable = $archive->getDataTableFromNumeric($columns); @@ -96,11 +84,38 @@ class Piwik_VisitsSummary_API return $dataTable; } + /** + * @ignore + */ + public function getColumns($period) + { + $columns = $this->getCoreColumns($period); + $columns = array_merge($columns, array('bounce_rate', 'nb_actions_per_visit', 'avg_time_on_site')); + return $columns; + } + + protected function getCoreColumns($period) + { + $columns = array( + 'nb_visits', + 'nb_actions', + 'nb_visits_converted', + 'bounce_count', + 'sum_visit_length', + 'max_actions' + ); + if (Piwik::isUniqueVisitorsEnabled($period)) { + $columns = array_merge(array('nb_uniq_visitors'), $columns); + } + $columns = array_values($columns); + return $columns; + } + protected function getNumeric($idSite, $period, $date, $segment, $toFetch) { Piwik::checkUserHasViewAccess($idSite); $archive = Piwik_Archive::build($idSite, $period, $date, $segment); - $dataTable = $archive->getNumeric($toFetch); + $dataTable = $archive->getDataTableFromNumeric($toFetch); return $dataTable; } @@ -142,8 +157,9 @@ class Piwik_VisitsSummary_API public function getSumVisitsLengthPretty($idSite, $period, $date, $segment = false) { $table = $this->getSumVisitsLength($idSite, $period, $date, $segment); - if ($table instanceof Piwik_DataTable_Array) { - $table->filter('ColumnCallbackReplace', array(0, array('Piwik', 'getPrettyTimeFromSeconds'))); + if (is_object($table)) { + $table->filter('ColumnCallbackReplace', + array('sum_visit_length', array('Piwik', 'getPrettyTimeFromSeconds'))); } else { $table = Piwik::getPrettyTimeFromSeconds($table); } diff --git a/plugins/VisitsSummary/Controller.php b/plugins/VisitsSummary/Controller.php index dcd1313da3..8eaa3f21e3 100644 --- a/plugins/VisitsSummary/Controller.php +++ b/plugins/VisitsSummary/Controller.php @@ -129,7 +129,7 @@ class Piwik_VisitsSummary_Controller extends Piwik_Controller $dataTableVisit = self::getVisitsSummary(); $dataRow = $dataTableVisit->getRowsCount() == 0 ? new Piwik_DataTable_Row() : $dataTableVisit->getFirstRow(); - $dataTableActions = Piwik_Actions_API::getInstance()->get($idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), Piwik_Common::getRequestVar('segment', false)); + $dataTableActions = Piwik_Actions_API::getInstance()->get($idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), Piwik_ViewDataTable::getRawSegmentFromRequest()); $dataActionsRow = $dataTableActions->getRowsCount() == 0 ? new Piwik_DataTable_Row() : $dataTableActions->getFirstRow(); diff --git a/plugins/VisitsSummary/VisitsSummary.php b/plugins/VisitsSummary/VisitsSummary.php index 87b320c947..0744b0424a 100644 --- a/plugins/VisitsSummary/VisitsSummary.php +++ b/plugins/VisitsSummary/VisitsSummary.php @@ -12,7 +12,7 @@ /** * Note: This plugin does not hook on Daily and Period Archiving like other Plugins because it reports the * very core metrics (visits, actions, visit duration, etc.) which are processed in the Core - * Piwik_ArchiveProcessing_Day class directly. + * Piwik_ArchiveProcessor_Day class directly. * These metrics can be used by other Plugins so they need to be processed up front. * * @package Piwik_VisitsSummary diff --git a/plugins/Widgetize/Widgetize.php b/plugins/Widgetize/Widgetize.php index 2ae9d74e11..24c50c280f 100644 --- a/plugins/Widgetize/Widgetize.php +++ b/plugins/Widgetize/Widgetize.php @@ -39,7 +39,7 @@ class Piwik_Widgetize extends Piwik_Plugin public function addTopMenu() { $tooltip = Piwik_Translate('Widgetize_TopLinkTooltip'); - $urlParams = array('module' => 'Widgetize', 'action' => 'index'); + $urlParams = array('module' => 'Widgetize', 'action' => 'index', 'segment' => false); Piwik_AddTopMenu('General_Widgets', $urlParams, true, 5, $isHTML = false, $tooltip); } diff --git a/plugins/Zeitgeist/javascripts/ajaxHelper.js b/plugins/Zeitgeist/javascripts/ajaxHelper.js index accc3501c0..7f6349429a 100644 --- a/plugins/Zeitgeist/javascripts/ajaxHelper.js +++ b/plugins/Zeitgeist/javascripts/ajaxHelper.js @@ -56,7 +56,7 @@ function ajaxHelper() { this.format = 'json'; /** - * Should ajax request be synchronous + * Should ajax request be asynchronous * @type {Boolean} */ this.async = true; @@ -66,11 +66,11 @@ function ajaxHelper() { */ this.callback = function () {}; - /** - * Use this.callback if an error is returned - * @type {Boolean} - */ - this.useRegularCallbackInCaseOfError = false; + /** + * Use this.callback if an error is returned + * @type {Boolean} + */ + this.useRegularCallbackInCaseOfError = false; /** * Callback function to be executed on error @@ -118,18 +118,12 @@ function ajaxHelper() { * @return {void} */ this.addParams = function (params, type) { - switch (type.toLowerCase()) { - - case 'get': - for (var key in params) { - this.getParams[key] = params[key]; - } - break; - case 'post': - for (var key in params) { - this.postParams[key] = params[key]; - } - break; + for (var key in params) { + if(type.toLowerCase() == 'get') { + this.getParams[key] = params[key]; + } else if(type.toLowerCase() == 'post') { + this.postParams[key] = params[key]; + } } }; @@ -138,18 +132,17 @@ function ajaxHelper() { * function is a single request to use. */ this.setBulkRequests = function () { - var urls = []; - for (var i = 0; i != arguments.length; ++i) - { - urls.push($.param(arguments[i])); - } - - this.addParams({ - module: 'API', - method: 'API.getBulkRequest', - urls: urls, - format: 'json' - }, 'post'); + var urls = []; + for (var i = 0; i != arguments.length; ++i) { + urls.push($.param(arguments[i])); + } + + this.addParams({ + module: 'API', + method: 'API.getBulkRequest', + urls: urls, + format: 'json' + }, 'post'); }; /** @@ -162,15 +155,13 @@ function ajaxHelper() { this.callback = callback; }; - /** - * Set that the callback passed to setCallback() should be used if an application error (i.e. an - * Exception in PHP) is returned. - * - * @param {void} - */ - this.useCallbackInCaseOfError = function () { - this.useRegularCallbackInCaseOfError = true; - }; + /** + * Set that the callback passed to setCallback() should be used if an application error (i.e. an + * Exception in PHP) is returned. + */ + this.useCallbackInCaseOfError = function () { + this.useRegularCallbackInCaseOfError = true; + }; /** * Set callback to redirect on success handler @@ -306,10 +297,24 @@ function ajaxHelper() { this._buildAjaxCall = function () { var that = this; + var parameters = this._mixinDefaultGetParams(this.getParams); + + var url = 'index.php?'; + + // we took care of encoding &segment properly already, so we don't use $.param for it ($.param URL encodes the values) + if(parameters['segment']) { + url += 'segment=' + parameters['segment'] + '&'; + delete parameters['segment']; + } + if(parameters['date']) { + url += 'date=' + decodeURIComponent(parameters['date']) + '&'; + delete parameters['date']; + } + url += $.param(parameters); var ajaxCall = { type: 'POST', async: this.async !== false, - url: 'index.php?' + $.param(this._mixinDefaultGetParams(this.getParams)), + url: url, dataType: this.format || 'json', error: this.errorCallback, success: function (response) { @@ -369,7 +374,7 @@ function ajaxHelper() { var defaultParams = { idSite: piwik.idSite || broadcast.getValueFromUrl('idSite'), period: piwik.period || broadcast.getValueFromUrl('period'), - segment: broadcast.getValueFromHash('segment', window.location.href) + segment: broadcast.getValueFromHash('segment', window.location.href.split('#')[1]) }; // never append token_auth to url @@ -379,13 +384,13 @@ function ajaxHelper() { } for (var key in defaultParams) { - if (!params[key] && defaultParams[key]) { + if (!params[key] && !this.postParams[key] && defaultParams[key]) { params[key] = defaultParams[key]; } } // handle default date & period if not already set - if (!params.date) { + if (!params.date && !this.postParams.date) { params.date = piwik.currentDateString || broadcast.getValueFromUrl('date'); if (params.period == 'range' && piwik.currentDateString) { params.date = piwik.startDateString + ',' + params.date; diff --git a/plugins/Zeitgeist/javascripts/piwikHelper.js b/plugins/Zeitgeist/javascripts/piwikHelper.js index 2e84968968..a2ade4c846 100644 --- a/plugins/Zeitgeist/javascripts/piwikHelper.js +++ b/plugins/Zeitgeist/javascripts/piwikHelper.js @@ -113,6 +113,32 @@ var piwikHelper = { }); }, + getQueryStringWithParametersModified: function (queryString, newParameters) { + if (queryString != '') { + var r, i, keyvalue, keysvalues = newParameters.split('&'); + var appendUrl = ''; + for (i = 0; i < keysvalues.length; i++) { + keyvalue = keysvalues[i].split('='); + r = new RegExp('(^|[?&])' + keyvalue[0] + '=[^&]*'); + queryString = queryString.replace(r, ''); + + // empty value, eg. &segment=, we remove the parameter from URL entirely + if (keyvalue[1].length == 0) { + continue; + } + appendUrl += '&' + keyvalue[0] + '=' + keyvalue[1]; + } + queryString += appendUrl; + if (queryString[0] == '&') { + queryString = '?' + queryString.substring(1); + } + } else { + queryString = '?' + newParameters; + } + + return queryString; + }, + /** * Returns the current query string with the given parameters modified * @param {object} newparams parameters to be modified @@ -120,24 +146,13 @@ var piwikHelper = { */ getCurrentQueryStringWithParametersModified: function(newparams) { - var parameters = String(window.location.search); - if(newparams) { - if(parameters != '') { - var r, i, keyvalue, keysvalues = newparams.split('&'); - for(i = 0; i < keysvalues.length; i++) { - keyvalue = keysvalues[i].split('='); - r = new RegExp('(^|[?&])'+keyvalue[0]+'=[^&]*'); - parameters = parameters.replace(r, ''); - } - parameters += '&' + newparams; - if(parameters[0] == '&') { - parameters = '?' + parameters.substring(1); - } - } else { - parameters = '?' + newparams; - } + var queryString = String(window.location.search); + if (newparams) { + queryString = this.getQueryStringWithParametersModified(queryString, newparams); } - return String(window.location.pathname) + parameters; + var value = String(window.location.pathname) + queryString; + + return value; }, /** @@ -365,6 +380,9 @@ var piwikHelper = { */ getApiFormatTextarea: function (textareaContent) { + if(typeof textareaContent == 'undefined') { + return ''; + } return textareaContent.trim().split("\n").join(','); } diff --git a/plugins/Zeitgeist/stylesheets/common.css b/plugins/Zeitgeist/stylesheets/common.css index 2d8db49e8b..feb94ad7d6 100644 --- a/plugins/Zeitgeist/stylesheets/common.css +++ b/plugins/Zeitgeist/stylesheets/common.css @@ -78,6 +78,15 @@ a { .loadingPiwik img { margin-right:5px; } + +.loadingSegment { + color: grey; + display: block; + font-size: 10pt; + margin-left: 28px; + display:none; +} + #loadingError { font-weight: bold; font-size: 1.1em; @@ -105,7 +114,7 @@ a { } #periodString { - display:block; + display:block; color:#444; font-size:14px; border: 1px solid #e4e5e4; @@ -118,14 +127,24 @@ a { z-index:999; position:absolute; background: url("../images/icon-calendar.gif") no-repeat scroll right 9px center #F7F7F7; + background-color: #f7f7f7; } #periodString:hover { background-color:#f1f0eb; border-color:#a9a399; } +#periodString .calendar-icon { + width: 13px; + height: 15px; + display:inline-block; + position:absolute; + right:9px; + top:7px; + background: url("images/icon-calendar.gif") no-repeat scroll; + cursor:pointer; +} #periodString #date{ - cursor:pointer; - display:inline-block; + cursor:pointer; padding:5px 10px 6px 10px; margin:-5px -10px -6px -10px; |